一、Redis介紹
什么是Redis?
redis是一個(gè)key-value存儲(chǔ)系統(tǒng)。和Memcached類似,它支持存儲(chǔ)的value類型相對(duì)更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數(shù)據(jù)類型都支持push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎(chǔ)上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數(shù)據(jù)都是緩存在內(nèi)存中。區(qū)別的是redis會(huì)周期性的把更新的數(shù)據(jù)寫(xiě)入磁盤(pán)或者把修改操作寫(xiě)入追加的記錄文件,并且在此基礎(chǔ)上實(shí)現(xiàn)了master-slave(主從)同步。
它有什么特點(diǎn)?
(1)Redis數(shù)據(jù)庫(kù)完全在內(nèi)存中,使用磁盤(pán)僅用于持久性。
(2)相比許多鍵值數(shù)據(jù)存儲(chǔ),Redis擁有一套較為豐富的數(shù)據(jù)類型。
(3)Redis可以將數(shù)據(jù)復(fù)制到任意數(shù)量的從服務(wù)器。
Redis 優(yōu)勢(shì)?
(1)異??焖伲篟edis的速度非常快,每秒能執(zhí)行約11萬(wàn)集合,每秒約81000+條記錄。
(2)支持豐富的數(shù)據(jù)類型:Redis支持最大多數(shù)開(kāi)發(fā)人員已經(jīng)知道像列表,集合,有序集合,散列數(shù)據(jù)類型。這使得它非常容易解決各種各樣的問(wèn)題,因?yàn)槲覀冎滥男﹩?wèn)題是可以處理通過(guò)它的數(shù)據(jù)類型更好。
(3)操作都是原子性:所有Redis操作是原子的,這保證了如果兩個(gè)客戶端同時(shí)訪問(wèn)的Redis服務(wù)器將獲得更新后的值。
(4)多功能實(shí)用工具:Redis是一個(gè)多實(shí)用的工具,可以在多個(gè)用例如緩存,消息,隊(duì)列使用(Redis原生支持發(fā)布/訂閱),任何短暫的數(shù)據(jù),應(yīng)用程序,如Web應(yīng)用程序會(huì)話,網(wǎng)頁(yè)命中計(jì)數(shù)等。
Redis 缺點(diǎn)?
(1)單線程
(2)耗內(nèi)存
二、使用實(shí)例
本文使用maven+eclipse+sping
1、引入jar包
1
|
2
3
4
5
6
7
8
9
10
11
12
|
<!--Redis start --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version> 1.6 . 1 .RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version> 2.7 . 3 </version> </dependency> <!--Redis end --> |
2、配置bean
在application.xml加入如下配置
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
<!-- jedis 配置 --> < bean id = "poolConfig" class = "redis.clients.jedis.JedisPoolConfig" > < property name = "maxIdle" value = "${redis.maxIdle}" /> < property name = "maxWaitMillis" value = "${redis.maxWait}" /> < property name = "testOnBorrow" value = "${redis.testOnBorrow}" /> </ bean > <!-- redis服務(wù)器中心 --> < bean id = "connectionFactory" class = "org.springframework.data.redis.connection.jedis.JedisConnectionFactory" > < property name = "poolConfig" ref = "poolConfig" /> < property name = "port" value = "${redis.port}" /> < property name = "hostName" value = "${redis.host}" /> < property name = "password" value = "${redis.password}" /> < property name = "timeout" value = "${redis.timeout}" ></ property > </ bean > < bean id = "redisTemplate" class = "org.springframework.data.redis.core.RedisTemplate" > < property name = "connectionFactory" ref = "connectionFactory" /> < property name = "keySerializer" > < bean class = "org.springframework.data.redis.serializer.StringRedisSerializer" /> </ property > < property name = "valueSerializer" > < bean class = "org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> </ property > </ bean > <!-- cache配置 --> < bean id = "methodCacheInterceptor" class = "com.mucfc.msm.common.MethodCacheInterceptor" > < property name = "redisUtil" ref = "redisUtil" /> </ bean > < bean id = "redisUtil" class = "com.mucfc.msm.common.RedisUtil" > < property name = "redisTemplate" ref = "redisTemplate" /> </ bean > |
其中配置文件redis一些配置數(shù)據(jù)redis.properties如下:
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#redis中心 redis.host= 10.75 . 202.11 redis.port= 6379 redis.password= 123456 redis.maxIdle= 100 redis.maxActive= 300 redis.maxWait= 1000 redis.testOnBorrow= true redis.timeout= 100000 # 不需要加入緩存的類 targetNames=xxxRecordManager,xxxSetRecordManager,xxxStatisticsIdentificationManager # 不需要緩存的方法 methodNames= #設(shè)置緩存失效時(shí)間 com.service.impl.xxxRecordManager= 60 com.service.impl.xxxSetRecordManager= 60 defaultCacheExpireTime= 3600 fep.local.cache.capacity = 10000 |
要掃這些properties文件,在application.xml加入如下配置
1
|
2
3
4
5
6
7
8
9
|
<!-- 引入properties配置文件 --> < bean id = "propertyConfigurer" class = "org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" > < property name = "locations" > < list > < value >classpath:properties/*.properties</ value > <!--要是有多個(gè)配置文件,只需在這里繼續(xù)添加即可 --> </ list > </ property > </ bean > |
3、一些工具類
(1)RedisUtil
上面的bean中,RedisUtil是用來(lái)緩存和去除數(shù)據(jù)的實(shí)例
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
package com.mucfc.msm.common; import java.io.Serializable; import java.util.Set; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; /** * redis cache 工具類 * */ public final class RedisUtil { private Logger logger = Logger.getLogger(RedisUtil. class ); private RedisTemplate<Serializable, Object> redisTemplate; /** * 批量刪除對(duì)應(yīng)的value * * @param keys */ public void remove( final String... keys) { for (String key : keys) { remove(key); } } /** * 批量刪除key * * @param pattern */ public void removePattern( final String pattern) { Set<Serializable> keys = redisTemplate.keys(pattern); if (keys.size() > 0 ) redisTemplate.delete(keys); } /** * 刪除對(duì)應(yīng)的value * * @param key */ public void remove( final String key) { if (exists(key)) { redisTemplate.delete(key); } } /** * 判斷緩存中是否有對(duì)應(yīng)的value * * @param key * @return */ public boolean exists( final String key) { return redisTemplate.hasKey(key); } /** * 讀取緩存 * * @param key * @return */ public Object get( final String key) { Object result = null ; ValueOperations<Serializable, Object> operations = redisTemplate .opsForValue(); result = operations.get(key); return result; } /** * 寫(xiě)入緩存 * * @param key * @param value * @return */ public boolean set( final String key, Object value) { boolean result = false ; try { ValueOperations<Serializable, Object> operations = redisTemplate .opsForValue(); operations.set(key, value); result = true ; } catch (Exception e) { e.printStackTrace(); } return result; } /** * 寫(xiě)入緩存 * * @param key * @param value * @return */ public boolean set( final String key, Object value, Long expireTime) { boolean result = false ; try { ValueOperations<Serializable, Object> operations = redisTemplate .opsForValue(); operations.set(key, value); redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); result = true ; } catch (Exception e) { e.printStackTrace(); } return result; } public void setRedisTemplate( RedisTemplate<Serializable, Object> redisTemplate) { this .redisTemplate = redisTemplate; } } |
(2)MethodCacheInterceptor
切面MethodCacheInterceptor,這是用來(lái)給不同的方法來(lái)加入判斷如果緩存存在數(shù)據(jù),從緩存取數(shù)據(jù)。否則第一次從數(shù)據(jù)庫(kù)取,并將結(jié)果保存到緩存 中去。
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
package com.mucfc.msm.common; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.log4j.Logger; public class MethodCacheInterceptor implements MethodInterceptor { private Logger logger = Logger.getLogger(MethodCacheInterceptor. class ); private RedisUtil redisUtil; private List<String> targetNamesList; // 不加入緩存的service名稱 private List<String> methodNamesList; // 不加入緩存的方法名稱 private Long defaultCacheExpireTime; // 緩存默認(rèn)的過(guò)期時(shí)間 private Long xxxRecordManagerTime; // private Long xxxSetRecordManagerTime; // /** * 初始化讀取不需要加入緩存的類名和方法名稱 */ public MethodCacheInterceptor() { try { File f = new File( "D:lunaJee-workspacemsmmsm_coresrcmainjavacommucfcmsmcommoncacheConf.properties" ); //配置文件位置直接被寫(xiě)死,有需要自己修改下 InputStream in = new FileInputStream(f); // InputStream in = getClass().getClassLoader().getResourceAsStream( // "D:lunaJee-workspacemsmmsm_coresrcmainjavacommucfcmsmcommoncacheConf.properties"); Properties p = new Properties(); p.load(in); // 分割字符串 String[] targetNames = p.getProperty( "targetNames" ).split( "," ); String[] methodNames = p.getProperty( "methodNames" ).split( "," ); // 加載過(guò)期時(shí)間設(shè)置 defaultCacheExpireTime = Long.valueOf(p.getProperty( "defaultCacheExpireTime" )); xxxRecordManagerTime = Long.valueOf(p.getProperty( "com.service.impl.xxxRecordManager" )); xxxSetRecordManagerTime = Long.valueOf(p.getProperty( "com.service.impl.xxxSetRecordManager" )); // 創(chuàng)建list targetNamesList = new ArrayList<String>(targetNames.length); methodNamesList = new ArrayList<String>(methodNames.length); Integer maxLen = targetNames.length > methodNames.length ? targetNames.length : methodNames.length; // 將不需要緩存的類名和方法名添加到list中 for ( int i = 0 ; i < maxLen; i++) { if (i < targetNames.length) { targetNamesList.add(targetNames[i]); } if (i < methodNames.length) { methodNamesList.add(methodNames[i]); } } } catch (Exception e) { e.printStackTrace(); } } @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object value = null ; String targetName = invocation.getThis().getClass().getName(); String methodName = invocation.getMethod().getName(); // 不需要緩存的內(nèi)容 //if (!isAddCache(StringUtil.subStrForLastDot(targetName), methodName)) { if (!isAddCache(targetName, methodName)) { // 執(zhí)行方法返回結(jié)果 return invocation.proceed(); } Object[] arguments = invocation.getArguments(); String key = getCacheKey(targetName, methodName, arguments); System.out.println(key); try { // 判斷是否有緩存 if (redisUtil.exists(key)) { return redisUtil.get(key); } // 寫(xiě)入緩存 value = invocation.proceed(); if (value != null ) { final String tkey = key; final Object tvalue = value; new Thread( new Runnable() { @Override public void run() { if (tkey.startsWith( "com.service.impl.xxxRecordManager" )) { redisUtil.set(tkey, tvalue, xxxRecordManagerTime); } else if (tkey.startsWith( "com.service.impl.xxxSetRecordManager" )) { redisUtil.set(tkey, tvalue, xxxSetRecordManagerTime); } else { redisUtil.set(tkey, tvalue, defaultCacheExpireTime); } } }).start(); } } catch (Exception e) { e.printStackTrace(); if (value == null ) { return invocation.proceed(); } } return value; } /** * 是否加入緩存 * * @return */ private boolean isAddCache(String targetName, String methodName) { boolean flag = true ; if (targetNamesList.contains(targetName) || methodNamesList.contains(methodName)) { flag = false ; } return flag; } /** * 創(chuàng)建緩存key * * @param targetName * @param methodName * @param arguments */ private String getCacheKey(String targetName, String methodName, Object[] arguments) { StringBuffer sbu = new StringBuffer(); sbu.append(targetName).append( "_" ).append(methodName); if ((arguments != null ) && (arguments.length != 0 )) { for ( int i = 0 ; i < arguments.length; i++) { sbu.append( "_" ).append(arguments[i]); } } return sbu.toString(); } public void setRedisUtil(RedisUtil redisUtil) { this .redisUtil = redisUtil; } } |
4、配置需要緩存的類或方法
在application.xml加入如下配置,有多個(gè)類或方法可以配置多個(gè)
1
|
2
3
4
5
6
7
8
9
10
11
12
|
<!-- 需要加入緩存的類或方法 --> < bean id = "methodCachePointCut" class = "org.springframework.aop.support.RegexpMethodPointcutAdvisor" > < property name = "advice" > < ref local = "methodCacheInterceptor" /> </ property > < property name = "patterns" > < list > <!-- 確定正則表達(dá)式列表 --> < value >com.mucfc.msm.service.impl...*ServiceImpl.*</ value > </ list > </ property > </ bean > |
5、執(zhí)行結(jié)果:
寫(xiě)了一個(gè)簡(jiǎn)單的單元測(cè)試如下:
1
|
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@Test public void getSettUnitBySettUnitIdTest() { String systemId = "CES" ; String merchantId = "133" ; SettUnit configSettUnit = settUnitService.getSettUnitBySettUnitId(systemId, merchantId, "ESP" ); SettUnit configSettUnit1 = settUnitService.getSettUnitBySettUnitId(systemId, merchantId, "ESP" ); boolean flag= (configSettUnit == configSettUnit1); System.out.println(configSettUnit); logger.info( "查找結(jié)果" + configSettUnit.getBusinessType()); // localSecondFIFOCache.put("configSettUnit", configSettUnit.getBusinessType()); // String string = localSecondFIFOCache.get("configSettUnit"); logger.info( "查找結(jié)果" + string); } |
這是第一次執(zhí)行單元測(cè)試的過(guò)程:
MethodCacheInterceptor這個(gè)類中打了斷點(diǎn),然后每次查詢前都會(huì)先進(jìn)入這個(gè)方法
依次運(yùn)行,發(fā)現(xiàn)沒(méi)有緩存,所以會(huì)直接去查數(shù)據(jù)庫(kù)
打印了出來(lái)的SQL語(yǔ)句:
第二次執(zhí)行:
因?yàn)榈谝淮螆?zhí)行時(shí),已經(jīng)寫(xiě)入緩存了。所以第二次直接從緩存中取數(shù)據(jù)
3、取兩次的結(jié)果進(jìn)行地址的對(duì)比:
發(fā)現(xiàn)兩個(gè)不是同一個(gè)對(duì)象,沒(méi)錯(cuò),是對(duì)的。如果是使用ehcache的話,那么二者的內(nèi)存地址會(huì)是一樣的。那是因?yàn)閞edis和ehcache使用的緩存機(jī)制是不一樣的。ehcache是基于本地電腦的內(nèi)存使用緩存,所以使用緩存取數(shù)據(jù)時(shí)直接在本地電腦上取。轉(zhuǎn)換成java對(duì)象就會(huì)是同一個(gè)內(nèi)存地址,而redis它是在裝有redis服務(wù)的電腦上(一般是另一臺(tái)電腦),所以取數(shù)據(jù)時(shí)經(jīng)過(guò)傳輸?shù)奖镜兀瑫?huì)對(duì)應(yīng)到不同的內(nèi)存地址,所以用==來(lái)比較會(huì)返回false。但是它確實(shí)是從緩存中去取的,這點(diǎn)我們從上面的斷點(diǎn)可以看到。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。