一、写在前面
缓存作为系统性能优化的一大杀手锏,几乎在每个系统或多或少的用到缓存。有的使用本地内存作为缓存,有的使用本地硬盘作为缓存,有的使用缓存服务器。但是无论使用哪种缓存,接口中的方法都是差不多。笔者最近的项目使用的是memcached作为缓存服务器,由于memcached的一些限制,现在想换redis作为缓存服务器。思路就是把memached的客户端换成redis客户端,接口依然是原来的接口,这样对系统可以无损替换,接口不变,功能不变,只是客户端变了。本文不介绍缓存的用法,不介绍redis使用方法,不介绍memcached与redis有何区别。只是实现一个redis客户端,用了jedis作为第三方连接工具。
二、一些想法
首先贴一下现项目中同事编写的缓存接口:
|
这个接口用起来总有一些别扭,我总结了一下:
1、接口名称命名为DispersedCachClient 这个命名含义是分布式缓存客户端(cache少了一个字母),其实这个接口跟分布式一点关系都没有,其实就是缓存接口;
2、接口方法太多了,实际在项目中并没有方法使用率只有20%左右,所有有精简的必要;
3、这个接口很多方法设计是套用memcached客户端设计的,也就是说换成redis后会不通用。
这里没有说这个接口写的不好,而是说还有优化的空间,其次也给自己提个醒,在设计一些使用公共的接口时有必要多花些心思,因为一旦设计后,后面改动的可能性比较小。
三、代码实现
使用jedis客户端需要使用连接池,使用连接后需要将连接放回连接池,失效的连接要放到失效的连接池,类似jdbc需要进行连接的处理,为了避免写重复恶心的代码,参照了spring的JdbcTemple模板设计方式。废话没有,直接上代码吧。
1、重新设计的缓存客户端接口,这个接口就一个特点“简单”,目的是为了做到通用,故命名为SimpleCache。
|
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);
}
}