一、写在前面
缓存作为系统性能优化的一大杀手锏,几乎在每个系统或多或少的用到缓存。有的使用本地内存作为缓存,有的使用本地硬盘作为缓存,有的使用缓存服务器。但是无论使用哪种缓存,接口中的方法都是差不多。笔者最近的项目使用的是memcached作为缓存服务器,由于memcached的一些限制,现在想换redis作为缓存服务器。思路就是把memached的客户端换成redis客户端,接口依然是原来的接口,这样对系统可以无损替换,接口不变,功能不变,只是客户端变了。本文不介绍缓存的用法,不介绍redis使用方法,不介绍memcached与redis有何区别。只是实现一个redis客户端,用了jedis作为第三方连接工具。
二、一些想法
首先贴一下现项目中同事编写的缓存接口:
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
/** * @ClassName: DispersedCachClient * @Description: 分布式缓存接口,每个方法:key最大长度128字符,valueObject最大1Mb,默认超时时间30天 * @date 2015-4-14 上午11:51:18 * */ public interface DispersedCachClient { /** * add(要设置缓存中的对象(value),) * * @Title: add * @Description: 要设置缓存中的对象(value),如果没有则插入,有就不操作。 * @param key 键 * @param valueObject 缓存对象 * @return Boolean true 成功,false 失败 */ public Boolean add(String key, Object valueObject); /** * add(要设置缓存中的对象(value),指定保存有效时长) * * @Title: add * @Description: 要设置缓存中的对象(value),指定有效时长,如果没有则插入,有就不操作。 * @param key 键 * @param valuObject 缓存对象 * @param keepTimeInteger 有效时长(秒) * @return Boolean true 成功,false 失败 */ public Boolean add(String key, Object valueObject, Integer keepTimeInteger); /** * * add(要设置缓存中的对象(value),指定有效时间点。) * * @Title: add * @Description: 要设置缓存中的对象(value),指定有效时间点,如果没有则插入,有就不操作。 * @date 2015-4-14 上午11:58:12 * @param key 键 * @param valuObject 缓存对象 * @param keepDate 时间点 * @return Boolean true 成功,false 失败 */ public Boolean add(String key, Object valueObject, Date keepDate); /** * * set(要设置缓存中的对象(value),) * * @Title: set * @Description: 如果没有则插入,如果有则修改 * @date 2015-4-14 下午01:44:22 * @param key 键 * @param valueObject 缓存对象 * @return Boolean true 成功,false 失败 */ public Boolean set(String key,Object valueObject) ; /** * * set(要设置缓存中的对象(value),指定有效时长) * * @Title: set * @Description: 指定有效时长,如果没有则插入,如果有则修改 * @date 2015-4-14 下午01:45:22 * @param key 键 * @param valueObject 缓存对象 * @param keepTimeInteger 保存时长(秒) * @return Boolean true 成功,false 失败 */ public Boolean set(String key, Object valueObject, Integer keepTimeInteger); /** * * set(要设置缓存中的对象(value),指定有效时间点) * * @Title: set * @Description: 指定有效时间点,如果没有则插入,如果有则修改 * @date 2015-4-14 下午01:45:55 * @param key 键 * @param valueObject 缓存对象 * @param keepDate 有效时间点 * @return Boolean true 成功,false 失败 */ public Boolean set(String key, Object valueObject, Date keepDate); /** * * replace(要设置缓存中的对象(value),有效) * * @Title: replace * @Description: 有效,如果没有则不操作,如果有则修改 * @date 2015-4-14 下午01:47:04 * @param key 键 * @param valueObject 缓存对象 * @return Boolean true 成功,false 失败 */ public Boolean replace(String key,Object valueObject) ; /** * * replace(要设置缓存中的对象(value),指定有效时长) * * @Title: replace * @Description: 指定有效时长,如果没有则不操作,如果有则修改 * @date 2015-4-14 下午01:47:30 * @param key 键 * @param valueObject 缓存对象 * @param keepTimeInteger 缓存时长(秒) * @return Boolean true 成功,false 失败 */ public Boolean replace(String key, Object valueObject, Integer keepTimeInteger); /** * * replace(要设置缓存中的对象(value),指定有效时间点) * * @Title: replace * @Description: 指定有效时间点,如果没有则不操作,如果有则修改 * @date 2015-4-14 下午01:48:09 * @param key 键值对 * @param valueObject 缓存对象 * @param keepDate 有效时间点 * @return Boolean true 成功,false 失败 */ public Boolean replace(String key, Object valueObject, Date keepDate); /** * * get(获得一个缓存对象) * * @Title: get * @Description: 获得一个缓存对象,响应超时时间默认 * @date 2015-4-14 下午04:18:16 * @param key 键 * @return Obeject */ public Object get( String key ); /** * * getMulti(获得Map形式的多个缓存对象) * * @Title: getMulti * @Description: 获得Map形式的多个缓存对象,响应超时时间默认 * @date 2015-4-14 下午04:53:07 * @param keys 键存入的string[] * return Map<String,Object> */ public Map<String,Object> getMulti( List<String> keys ); /** * * gets(获得一个带版本号的缓存对象) * * @Title: gets * @Description: 获得一个带版本号的缓存对象 * @date 2015-4-16 上午09:15:57 * @param key 键 * @return Object */ public Object gets(String key); /** * * getMultiArray(获得数组形式的多个缓存对象) * * @Title: getMultiArray * @Description: 获得数组形式的多个缓存对象 * @date 2015-4-16 上午09:27:29 * @param keys 键存入的string[] * @return Object[] * @throws */ public Object[] getMultiArray( List<String> keys ); /** * * cas(带版本号存缓存,与gets配合使用) * * @Title: cas * @Description: 带版本号存缓存,与gets配合使用,超时时间默认 * @date 2015-4-16 上午09:53:39 * @param key 键 * @param valueObject 缓存对象 * @param versionNo 版本号 * @return Boolean true 成功,false 失败 */ public boolean cas(String key, Object valueObject, long versionNo); /** cas(带版本号存缓存,与gets配合使用) * * @Title: cas * @Description: 带版本号存缓存,与gets配合使用,指定超时时长 * @date 2015-4-16 上午09:58:06 * @param key 键 * @param valueObject 缓存对象 * @param keepTimeInteger 超时时长 * @param versionNo 版本号 * @return Boolean true 成功,false 失败 */ public boolean cas(String key, Object valueObject, Integer keepTimeInteger, long versionNo); /** * * cas(带版本号存缓存,与gets配合使用) * * @Title: cas * @Description: 带版本号存缓存,与gets配合使用,指定超时时间点 * @date 2015-4-16 上午10:02:38 * @param key 键 * @param valueObject 缓存对象 * @param keepTime 超时时间点 * @param versionNo 版本号 * @return Boolean true 成功,false 失败 */ public boolean cas(String key, Object valueObject, Date keepDate, long versionNo); /** * * delete(删除缓存) * * @Title: delete * @Description: 删除缓存 * @date 2015-4-16 上午11:20:13 * @param key 键 */ public boolean delete(String key); } |
这个接口用起来总有一些别扭,我总结了一下:
1、接口名称命名为DispersedCachClient 这个命名含义是分布式缓存客户端(cache少了一个字母),其实这个接口跟分布式一点关系都没有,其实就是缓存接口;
2、接口方法太多了,实际在项目中并没有方法使用率只有20%左右,所有有精简的必要;
3、这个接口很多方法设计是套用memcached客户端设计的,也就是说换成redis后会不通用。
这里没有说这个接口写的不好,而是说还有优化的空间,其次也给自己提个醒,在设计一些使用公共的接口时有必要多花些心思,因为一旦设计后,后面改动的可能性比较小。
三、代码实现
使用jedis客户端需要使用连接池,使用连接后需要将连接放回连接池,失效的连接要放到失效的连接池,类似jdbc需要进行连接的处理,为了避免写重复恶心的代码,参照了spring的JdbcTemple模板设计方式。废话没有,直接上代码吧。
1、重新设计的缓存客户端接口,这个接口就一个特点“简单”,目的是为了做到通用,故命名为SimpleCache。
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
|
/** * @ClassName: DistributedCacheClient * @Description: 缓存接口 * @author 徐飞 * @date 2016年1月26日 上午11:41:27 * */ public interface SimpleCache { /** * @Title: add * @Description: 添加一个缓冲数据 * @param key 字符串的缓存key * @param value 缓冲的缓存数据 * @return * @author 徐飞 */ boolean add(String key, Object value); /** * @Title: add * @Description: 缓存一个数据,并指定缓存过期时间 * @param key * @param value * @param seconds * @return * @author 徐飞 */ boolean add(String key, Object value, int seconds); /** * @Title: get * @Description: 根据key获取到一直值 * @param key 字符串的缓存key * @return * @author 徐飞 */ Object get(String key); /** * @Title: delete * @Description: 删除一个数据问题 * @param key 字符串的缓存key * @return * @author 徐飞 */ long delete(String key); /** * @Title: exists * @Description: 判断指定key是否在缓存中已经存在 * @param key 字符串的缓存key * @return * @author 徐飞 */ boolean exists(String key); } |
2、JedisTemple :Jedis 操作模板类,请参照Spring的JdbcTemple封装重复但又必要的操作
1 /** 2 * @ClassName: JedisTemple 3 * @Description: Jedis 操作模板类,为啥要这个?请参照{@link JdbcTemple} 封装重复不必要的操作 4 * @author 徐飞 5 * @date 2016年1月26日 下午2:37:24 6 * 7 */ 8 public class JedisTemple { 9 10 /** 缓存客户端 **/ 11 private JedisPool jedisPool;// 非切片连接池 12 13 public JedisTemple(JedisPool jedisPool) { 14 this.jedisPool = jedisPool; 15 } 16 17 /** 18 * @Title: execute 19 * @Description: 执行{@link RedisPoolCallback#doInJedis(Jedis)}的方法 20 * @param action 21 * @return 22 * @author 徐飞 23 */ 24 public <T> T execute(RedisPoolCallback<T> action) { 25 T value = null; 26 Jedis jedis = null; 27 try { 28 jedis = jedisPool.getResource(); 29 return action.doInJedis(jedis); 30 } catch (Exception e) { 31 // 释放redis对象 32 jedisPool.returnBrokenResource(jedis); 33 e.printStackTrace(); 34 } finally { 35 // 返还到连接池 36 returnResource(jedisPool, jedis); 37 } 38 39 return value; 40 } 41 42 /** 43 * 返还到连接池 44 * @param pool 45 * @param redis 46 */ 47 private void returnResource(JedisPool pool, Jedis redis) { 48 // 如果redis为空不返回 49 if (redis != null) { 50 pool.returnResource(redis); 51 } 52 } 53 54 public JedisPool getJedisPool() { 55 return jedisPool; 56 } 57 58 public void setJedisPool(JedisPool jedisPool) { 59 this.jedisPool = jedisPool; 60 } 61 62 }
3、RedisPoolCallback:redis操作回调接口,此接口主要为JedisTemple模板使用
1 import redis.clients.jedis.Jedis; 2 3 /** 4 * @ClassName: RedisPoolCallback 5 * @Description: redis操作回调接口,此接口主要为JedisTemple模板使用 6 * @author 徐飞 7 * @date 2016年1月26日 下午2:35:41 8 * 9 * @param <T> 10 */ 11 public interface RedisPoolCallback<T> { 12 /** 13 * @Title: doInJedis 14 * @Description: 回调执行方法,需要重新此方法,一般推荐使用匿名内部类 15 * @param jedis 16 * @return 17 * @author 徐飞 18 */ 19 T doInJedis(Jedis jedis); 20 }
4、RedisCacheClient :redis客户端实现类
1 import redis.clients.jedis.Jedis; 2 import redis.clients.util.SafeEncoder; 3 4 import com.cxypub.baseframework.sdk.util.ObjectUtils; 5 6 /** 7 * @ClassName: RedisCacheClient 8 * @Description: redis缓存客户端 9 * @author 徐飞 10 * @date 2015-4-16 上午10:42:32 11 * 12 */ 13 public class RedisCacheClient implements SimpleCache { 14 15 private JedisTemple jedisTemple; 16 17 public RedisCacheClient(JedisTemple jedisTemple) { 18 this.jedisTemple = jedisTemple; 19 } 20 21 @Override 22 public boolean add(final String key, final Object valueObject) { 23 try { 24 jedisTemple.execute(new RedisPoolCallback<Boolean>() { 25 @Override 26 public Boolean doInJedis(Jedis jedis) { 27 jedis.set(SafeEncoder.encode(key), ObjectUtils.object2Byte(valueObject)); 28 return true; 29 } 30 31 }); 32 } catch (Exception e) { 33 e.printStackTrace(); 34 return false; 35 } 36 return true; 37 } 38 39 @Override 40 public Object get(final String key) { 41 42 return jedisTemple.execute(new RedisPoolCallback<Object>() { 43 @Override 44 public Object doInJedis(Jedis jedis) { 45 byte[] cacheValue = jedis.get(SafeEncoder.encode(key)); 46 if (cacheValue != null) { 47 return ObjectUtils.byte2Object(cacheValue); 48 } 49 return null; 50 } 51 52 }); 53 } 54 55 @Override 56 public long delete(final String key) { 57 return jedisTemple.execute(new RedisPoolCallback<Long>() { 58 @Override 59 public Long doInJedis(Jedis jedis) { 60 return jedis.del(key); 61 } 62 }); 63 } 64 65 @Override 66 public boolean add(final String key, Object value, final int seconds) { 67 try { 68 this.add(key, value); 69 jedisTemple.execute(new RedisPoolCallback<Long>() { 70 @Override 71 public Long doInJedis(Jedis jedis) { 72 return jedis.expire(key, seconds); 73 } 74 }); 75 } catch (Exception e) { 76 e.printStackTrace(); 77 return false; 78 } 79 return true; 80 } 81 82 @Override 83 public boolean exists(final String key) { 84 return jedisTemple.execute(new RedisPoolCallback<Boolean>() { 85 @Override 86 public Boolean doInJedis(Jedis jedis) { 87 return jedis.exists(key); 88 } 89 }); 90 } 91 92 }
5、实现了代码,下面就开始将客户端整合进spring就行了,上配置文件
redis.properties:
1 # Redis settings 2 redis.host=192.168.1.215 3 redis.port=6379 4 redis.pass= 5 6 # 控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取; 7 # 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。 8 redis.maxTotal=600 9 # 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。 10 redis.maxIdle=300 11 # 表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException; 12 redis.maxWaitMillis=1000 13 # 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的; 14 redis.testOnBorrow=true
applicationContext-redis.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd" default-autowire="autodetect" default-lazy-init="false"> <!-- jedis 配置 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${redis.maxIdle}" /> <property name="maxTotal" value="${redis.maxTotal}" /> <property name="maxWaitMillis" value="${redis.maxWaitMillis}" /> <property name="testOnBorrow" value="${redis.testOnBorrow}" /> </bean> <!-- jedis 连接池 --> <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <constructor-arg ref="jedisPoolConfig" /> <constructor-arg value="${redis.host}" /> <constructor-arg value="${redis.port}" type="java.lang.Integer" /> </bean> <!-- jedis 操作 temple --> <bean id="jedisTemple" class="com.cxypub.baseframework.sdk.cache.JedisTemple"> <constructor-arg ref="jedisPool" /> </bean> <!-- jedis 客户端,真正提供给系统使用的客户端,当然如果这个客户端的方法不满足,可以使用jedisTemple --> <bean id="jedisClient" class="com.cxypub.baseframework.sdk.cache.RedisCacheClient"> <constructor-arg ref="jedisTemple" /> </bean> </beans>
6、这样在项目中就可以将jedisClient 注入到任何类中了,我这里写了一个测试客户端,这个直接运行的,一同贴上。
package com.cxypub.baseframework.sdk.cache; import java.util.Date; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import com.cxypub.baseframework.sdk.dictionary.entity.Dictionary; public class RedisTest { public static void main(String[] args) { JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(500); config.setMaxIdle(5); config.setMaxWaitMillis(1000 * 100); config.setTestOnBorrow(true); JedisPool jedisPool = new JedisPool(config, "192.168.1.215", 6379); JedisTemple jedisTemple = new JedisTemple(jedisPool); RedisCacheClient client = new RedisCacheClient(jedisTemple); Dictionary dict = new Dictionary(); dict.setId("qwertryruyrtutyu"); dict.setDictChineseName("上海"); dict.setCreateTime(new Date()); client.add("xufei", dict); Dictionary dict2 = (Dictionary) client.get("xufei"); System.out.println(dict2); System.out.println(dict == dict2); } }
http://www.cnblogs.com/xumanbu/p/5160765.html