spring整合redis客户端及缓存接口设计
2016-01-26 16:11
746 查看
一、写在前面
缓存作为系统性能优化的一大杀手锏,几乎在每个系统或多或少的用到缓存。有的使用本地内存作为缓存,有的使用本地硬盘作为缓存,有的使用缓存服务器。但是无论使用哪种缓存,接口中的方法都是差不多。笔者最近的项目使用的是memcached作为缓存服务器,由于memcached的一些限制,现在想换redis作为缓存服务器。思路就是把memached的客户端换成redis客户端,接口依然是原来的接口,这样对系统可以无损替换,接口不变,功能不变,只是客户端变了。本文不介绍缓存的用法,不介绍redis使用方法,不介绍memcached与redis有何区别。只是实现一个redis客户端,用了jedis作为第三方连接工具。二、一些想法
首先贴一下现项目中同事编写的缓存接口:/** * @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。
/** * @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封装重复但又必要的操作
/** * @ClassName: JedisTemple * @Description: Jedis 操作模板类,为啥要这个?请参照{@link JdbcTemple} 封装重复不必要的操作 * @author 徐飞 * @date 2016年1月26日 下午2:37:24 * */ public class JedisTemple { /** 缓存客户端 **/ private JedisPool jedisPool;// 非切片连接池 public JedisTemple(JedisPool jedisPool) { this.jedisPool = jedisPool; } /** * @Title: execute * @Description: 执行{@link RedisPoolCallback#doInJedis(Jedis)}的方法 * @param action * @return * @author 徐飞 */ public <T> T execute(RedisPoolCallback<T> action) { T value = null; Jedis jedis = null; try { jedis = jedisPool.getResource(); return action.doInJedis(jedis); } catch (Exception e) { // 释放redis对象 jedisPool.returnBrokenResource(jedis); e.printStackTrace(); } finally { // 返还到连接池 returnResource(jedisPool, jedis); } return value; } /** * 返还到连接池 * @param pool * @param redis */ private void returnResource(JedisPool pool, Jedis redis) { // 如果redis为空不返回 if (redis != null) { pool.returnResource(redis); } } public JedisPool getJedisPool() { return jedisPool; } public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool; } }
3、RedisPoolCallback:redis操作回调接口,此接口主要为JedisTemple模板使用
import redis.clients.jedis.Jedis; /** * @ClassName: RedisPoolCallback * @Description: redis操作回调接口,此接口主要为JedisTemple模板使用 * @author 徐飞 * @date 2016年1月26日 下午2:35:41 * * @param <T> */ public interface RedisPoolCallback<T> { /** * @Title: doInJedis * @Description: 回调执行方法,需要重新此方法,一般推荐使用匿名内部类 * @param jedis * @return * @author 徐飞 */ T doInJedis(Jedis jedis); }
4、RedisCacheClient :redis客户端实现类
import redis.clients.jedis.Jedis; import redis.clients.util.SafeEncoder; import com.cxypub.baseframework.sdk.util.ObjectUtils; /** * @ClassName: RedisCacheClient * @Description: redis缓存客户端 * @author 徐飞 * @date 2015-4-16 上午10:42:32 * */ public class RedisCacheClient implements SimpleCache { private JedisTemple jedisTemple; public RedisCacheClient(JedisTemple jedisTemple) { this.jedisTemple = jedisTemple; } @Override public boolean add(final String key, final Object valueObject) { try { jedisTemple.execute(new RedisPoolCallback<Boolean>() { @Override public Boolean doInJedis(Jedis jedis) { jedis.set(SafeEncoder.encode(key), ObjectUtils.object2Byte(valueObject)); return true; } }); } catch (Exception e) { e.printStackTrace(); return false; } return true; } @Override public Object get(final String key) { return jedisTemple.execute(new RedisPoolCallback<Object>() { @Override public Object doInJedis(Jedis jedis) { byte[] cacheValue = jedis.get(SafeEncoder.encode(key)); if (cacheValue != null) { return ObjectUtils.byte2Object(cacheValue); } return null; } }); } @Override public long delete(final String key) { return jedisTemple.execute(new RedisPoolCallback<Long>() { @Override public Long doInJedis(Jedis jedis) { return jedis.del(key); } }); } @Override public boolean add(final String key, Object value, final int seconds) { try { this.add(key, value); jedisTemple.execute(new RedisPoolCallback<Long>() { @Override public Long doInJedis(Jedis jedis) { return jedis.expire(key, seconds); } }); } catch (Exception e) { e.printStackTrace(); return false; } return true; } @Override public boolean exists(final String key) { return jedisTemple.execute(new RedisPoolCallback<Boolean>() { @Override public Boolean doInJedis(Jedis jedis) { return jedis.exists(key); } }); } }
5、实现了代码,下面就开始将客户端整合进spring就行了,上配置文件
redis.properties:
# Redis settings redis.host=192.168.1.215 redis.port=6379 redis.pass= # 控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取; # 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。 redis.maxTotal=600 # 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。 redis.maxIdle=300 # 表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException; redis.maxWaitMillis=1000 # 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的; 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); } }
相关文章推荐
- Redis EXISTS命令耗时过长case排查
- CentOS 6.5下Redis安装记录
- Redis批量删除key
- Redis和MySQL的结合方案
- Proxy-Style Redis集群设计
- python中redis的使用
- 基于nginx tomcat redis分布式web应用的session共享配置
- 利用redis同步登陆实现session共享
- redis中的事务、lua脚本和管道的使用场景
- 扩展阅读- redis 配置参数详解
- Redis install and config
- redis操作命令
- PHP-redis中文文档(转)
- Redis入门基础
- Redis实现Mybatis的二级缓存
- Redis Hash(哈希)
- redis的存取速度多快
- Redis实现分布式session功能的共享
- Redis学习笔记6--Redis事务
- Redis学习笔记5--Redis排序