spring整合redis客户端及缓存接口设计
一、写在前面
缓存作为系统性能优化的一大杀手锏,几乎在每个系统或多或少的用到缓存。有的使用本地内存作为缓存,有的使用本地硬盘作为缓存,有的使用缓存服务器。但是无论使用哪种缓存,接口中的方法都是差不多。笔者最近的项目使用的是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);
}
}
spring整合redis客户端及缓存接口设计的更多相关文章
- spring整合redis客户端及缓存接口设计(转)
一.写在前面 缓存作为系统性能优化的一大杀手锏,几乎在每个系统或多或少的用到缓存.有的使用本地内存作为缓存,有的使用本地硬盘作为缓存,有的使用缓存服务器.但是无论使用哪种缓存,接口中的方法都是差不多. ...
- 网站性能优化小结和spring整合redis
现在越来越多的地方需要非关系型数据库了,最近网站优化,当然从页面到服务器做了相应的优化后,通过在线网站测试工具与之前没优化对比,发现有显著提升. 服务器优化目前主要优化tomcat,在tomcat目录 ...
- Spring整合Redis&JSON序列化&Spring/Web项目部署相关
几种JSON框架用法和效率对比: https://blog.csdn.net/sisyphus_z/article/details/53333925 https://blog.csdn.net/wei ...
- spring整合redis之hello
1.pom.xml文件 <dependencies> <!-- spring核心包 --> <dependency> <groupId>org.spri ...
- Spring整合Redis时报错:java.util.NoSuchElementException: Unable to validate object
我在Spring整合Redis时报错,我是犯了一个很低级的错误! 我设置了Redis的访问密码,在Spring的配置文件却没有配置密码这一项,配置上密码后,终于不报错了!
- 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 ...
- Spring整合redis实现key过期事件监听
打开redis服务的配置文件 添加notify-keyspace-events Ex 如果是注释了,就取消注释 这个是在以下基础上进行添加的 Spring整合redis:https://www. ...
- (转)Spring整合Redis作为缓存
采用Redis作为Web系统的缓存.用Spring的Cache整合Redis. 一.关于redis的相关xml文件的写法 <?xml version="1.0" ...
- spring boot整合redis,以及设置缓存过期时间
spring-boot 整合 redis 注:redis服务器要先开启 pom文件: <dependency> <groupId>org.springframework.boo ...
随机推荐
- cin输入过慢用scanf???现在才知道cin可以加速
今天才发现可以加速原帖 只需要两行放在main开头即可 ios_base::sync_with_stdio(0); cin.tie(NULL);
- String s = new String("xyz");创建了几个对象?
两个或一个都有可能 . ”xyz”对应一个对象,这个对象放在字符串常量池,常量”xyz”不管出现多少遍,都是常量池中的那一个. new String每写一遍,就创建一个新的对象,它使用常量”xyz”对 ...
- Linux自动化之基于http的pxe安装服务
PXE: Preboot Excution Environment 预启动执行环境 Intel公司研发 基于Client/Server的网络模式,支持远程主机通过网络从远端服务 ...
- Python3 编写登陆接口
题目选自 Alex Python自动化开发之路的题目,我是从C++转学Python的,编写的水平有限,轻喷. 输入用户名密码 认证成功后显示欢迎信息 输错三次后锁定 首先应该有2个txt文件,包含用户 ...
- 【郑轻邀请赛 C】DOBRI
[题目链接]:https://acm.zzuli.edu.cn/zzuliacm/problem.php?id=2129 [题意] [题解] NMB 直接说i-1,i-2,i-3不就好了- [Numb ...
- 【codeforces 796D】Police Stations
[题目链接]:http://codeforces.com/contest/796/problem/D [题意] 在一棵树上,保证每个点在距离d之内都有一个警察局; 让你删掉最多的边,使得剩下的森林仍然 ...
- jenkins简单持续构建
一.安装jenkins 二.将需要持续构建的java project打包成jar文件 1.选择导出需要运行的main方法所在java类
- 清北学堂模拟赛d2t3 逆序对(pair)
题目描述LYK最近在研究逆序对.这个问题是这样的.一开始LYK有一个2^n长度的数组ai.LYK有Q次操作,每次操作都有一个参数k.表示每连续2^k长度作为一个小组.假设n=4,k=2,则a[1],a ...
- python库文件路径
python中import语句导入库文件路径可通过sys.path查看.写一个简单的小程序: import sys print sys.path 运行它,本机上得到的结果如下: ['', '/usr/ ...
- 洛谷——P2676 超级书架
https://www.luogu.org/problem/show?pid=2676#sub 题目描述 Farmer John最近为奶牛们的图书馆添置了一个巨大的书架,尽管它是如此的大,但它还是几乎 ...