一、写在前面

缓存作为系统性能优化的一大杀手锏,几乎在每个系统或多或少的用到缓存。有的使用本地内存作为缓存,有的使用本地硬盘作为缓存,有的使用缓存服务器。但是无论使用哪种缓存,接口中的方法都是差不多。笔者最近的项目使用的是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

spring整合redis客户端及缓存接口设计(转)的更多相关文章

  1. spring整合redis客户端及缓存接口设计

    一.写在前面 缓存作为系统性能优化的一大杀手锏,几乎在每个系统或多或少的用到缓存.有的使用本地内存作为缓存,有的使用本地硬盘作为缓存,有的使用缓存服务器.但是无论使用哪种缓存,接口中的方法都是差不多. ...

  2. 网站性能优化小结和spring整合redis

    现在越来越多的地方需要非关系型数据库了,最近网站优化,当然从页面到服务器做了相应的优化后,通过在线网站测试工具与之前没优化对比,发现有显著提升. 服务器优化目前主要优化tomcat,在tomcat目录 ...

  3. Spring整合Redis&JSON序列化&Spring/Web项目部署相关

    几种JSON框架用法和效率对比: https://blog.csdn.net/sisyphus_z/article/details/53333925 https://blog.csdn.net/wei ...

  4. spring整合redis之hello

    1.pom.xml文件 <dependencies> <!-- spring核心包 --> <dependency> <groupId>org.spri ...

  5. Spring整合Redis时报错:java.util.NoSuchElementException: Unable to validate object

    我在Spring整合Redis时报错,我是犯了一个很低级的错误! 我设置了Redis的访问密码,在Spring的配置文件却没有配置密码这一项,配置上密码后,终于不报错了!

  6. Redis的安装以及spring整合Redis时出现Could not get a resource from the pool

    Redis的下载与安装 在Linux上使用wget http://download.redis.io/releases/redis-5.0.0.tar.gz下载源码到指定位置 解压:tar -xvf ...

  7. Spring整合redis实现key过期事件监听

    打开redis服务的配置文件   添加notify-keyspace-events Ex  如果是注释了,就取消注释 这个是在以下基础上进行添加的 Spring整合redis:https://www. ...

  8. (转)Spring整合Redis作为缓存

           采用Redis作为Web系统的缓存.用Spring的Cache整合Redis. 一.关于redis的相关xml文件的写法 <?xml version="1.0" ...

  9. spring boot整合redis,以及设置缓存过期时间

    spring-boot 整合 redis 注:redis服务器要先开启 pom文件: <dependency> <groupId>org.springframework.boo ...

随机推荐

  1. web.xml的运行顺序

    整体上的顺序为 <context-param> <listener> <filter> <servlet> 往下依次运行. 当中,每一个类别内部都是按序 ...

  2. (Android+IOS)我们正在做一个新闻App,做几乎一样的,倾听您的建议 (画画)

    (Android+IOS)我们正在做一个新闻App,做几乎一样的,倾听您的建议! 新闻采访是做,前端展示APP界面感觉还不是非常好,还须要改进改进,希望公布(Android和IOS版本号)前听听大家的 ...

  3. mysql5.1,5.5,5.6做partition时支持的函数

    mysql5.1支持的partition函数(http://dev.mysql.com/doc/refman/5.1/en/partitioning-limitations-functions.htm ...

  4. 【架构之路之WCF全析(一)】--服务协定及消息模式

    上周微软开公布会说.NET支持全然跨平台和并开放Core源代码的新闻,让我们顿时感到.NET要迎来它的春天.尽管早在几年前.NET就能开发Android和IOS,可是这次的跨平台把Linux都放到了微 ...

  5. Windows Phone开发(45):推送通知大结局——Raw通知

    原文:Windows Phone开发(45):推送通知大结局--Raw通知 为什么叫大结局呢?因为推送通知服务就只有三种,前面扯了两种,就剩下一种--Raw通知. 前面我们通过两节的动手实验,相信大家 ...

  6. 整理QTP知识之1

    以下说明由网络其他文章整合而成. 一.关于QTP的说明 QTP是目前市场上占有率最高的一款自动化测试工具,也是每一位测试工作者最想掌握的工具之一,也是目前流行的基于GUI的功能自动化测试工具之一. Q ...

  7. [转]C#自定义开关按钮控件--附带第一个私活项目截图

    原文地址:http://www.cnblogs.com/feiyangqingyun/archive/2013/06/15/3137597.html 进入智能手机时代以来,各种各样的APP大行其道,手 ...

  8. 【Web优化】Yslow优化法则(四)启用Gzip压缩

    Yslow的第4个经验法则指出:启用gzip压缩功能,能够降低HTTP传输的数据和时间,从而降低client请求的响应时间. 本篇是Yslow法则的第四个,主要包含三个方面的内容: 1.      什 ...

  9. EasyUI基础searchbox&amp;progressbar(搜索框,进度条)

    easyui学习的基本组成部分(八个部分)硕果仅存searchbox和pargressbar.tooltip该,有一点兴奋.本文将偏向searchbox和pargressbar做一个探讨.鉴于双方的内 ...

  10. android集成apk对一些问题经常遇到系统

    1.集成APK必须确认是否release版本号,否则会导致CTS测试失败. 途径:反编译apk,视图manifest.xml文件,看<application>在那里debug属性:andr ...