一、写在前面

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

    传送门:pid=4888">[HDU]4888 Redraw Beautiful Drawings 题目分析: 比赛的时候看出是个网络流,可是没有敲出来.各种反面样例推倒自己(究其原因 ...

  2. Gnu Linux--Ubuntu系统清理项整理

    /*********************************************************************  * Author  : Samson  * Date   ...

  3. Android的PackageManager的使用

    Android系统提供了很多服务管理的类,包括ActivityManager.PowerManager(电源管理).AudioManager(音频管理)以及PackageManager管理类.Pack ...

  4. 用XAML做网页!!—边栏与页脚

    原文:用XAML做网页!!-边栏与页脚 此次我们来设计边栏和页脚,首先从页脚开始,其代码很简单: <Border x:Name="Footer" BorderBrush=&q ...

  5. SVN的revert和update命令的区别

    svn中的revert和update 今天有人问到revert和update的问题. 刚开始还真被问住了. 因为感觉revert和update都可以将本地的copy更新到以前的一个版本,会有什么不同呢 ...

  6. jQuery上传插件Uploadify 3.2在.NET下的详细例子

    项目中要使用Uploadify 3.2来实现图片上传并生成缩略通的功能,特此记下来,以供各位参考! Uploadify下载地址:http://www.uploadify.com/download/ 下 ...

  7. 在TextView中加入图片

    TextView是一个非常强大的控件,有时须要在一个控件中同一时候显示图片和文字,使用TextView非常easy实现. 方法一: 聊天软件比方QQ一般都会有发送表情的功能,使用SpannableSt ...

  8. poj2348(博弈)

    poj2348 给定两个数a,b,大的数能减少小的数的倍数,不能是的数小于0,谁先使得数等于0,谁就赢了 有三种情况 ① a % b ==0  这个状态是必胜的 ② a - b < b  这个状 ...

  9. 关于扩展IP地址空间的几个方案的探讨

    摘  要:在IP地址紧缺的背景下,IPv6.NAPT.SuIP几种解决方案应运而生.分析.比较几种方案后,可知SuIP是最佳方案. 关键词:IPv6:NAPT:SuIP:IP地址空间扩展 The In ...

  10. linux 流量监控 ---iptraf的安装及使用

    一.安装iptraf 我用的是centos,切换到root用户,执行 yum install -y iptraf 二.使用 1.直接输入iptraf,进入软件,按任意键继续 2.我主要是第二项和第三项 ...