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封装重复但又必要的操作

 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.jedis.JedisPool;
  3 import redis.clients.jedis.JedisPoolConfig;
  4 import redis.clients.util.SafeEncoder;
  5
  6 import com.cxypub.baseframework.sdk.util.ObjectUtils;
  7
  8 /**
  9  * @ClassName: RedisCacheClient
 10  * @Description: redis缓存客户端
 11  * @author 徐飞
 12  * @date 2015-4-16 上午10:42:32
 13  *
 14  */
 15 public class RedisCacheClient implements SimpleCache {
 16
 17     private JedisTemple jedisTemple;
 18
 19     public RedisCacheClient(JedisPoolConfig config, String host, Integer port) {
 20         this.initialPool(config, host, port);
 21     }
 22
 23     /**
 24      * 初始化非切片池
 25      */
 26     private void initialPool(JedisPoolConfig config, String host, Integer port) {
 27         JedisPool jedisPool = new JedisPool(config, host, port);
 28         this.jedisTemple = new JedisTemple(jedisPool);
 29     }
 30
 31     @Override
 32     public boolean add(final String key, final Object valueObject) {
 33         try {
 34             jedisTemple.execute(new RedisPoolCallback<Boolean>() {
 35                 @Override
 36                 public Boolean doInJedis(Jedis jedis) {
 37                     jedis.set(SafeEncoder.encode(key), ObjectUtils.object2Byte(valueObject));
 38                     return true;
 39                 }
 40
 41             });
 42         } catch (Exception e) {
 43             e.printStackTrace();
 44             return false;
 45         }
 46         return true;
 47     }
 48
 49     @Override
 50     public Object get(final String key) {
 51
 52         return jedisTemple.execute(new RedisPoolCallback<Object>() {
 53             @Override
 54             public Object doInJedis(Jedis jedis) {
 55                 byte[] cacheValue = jedis.get(SafeEncoder.encode(key));
 56                 if (cacheValue != null) {
 57                     return ObjectUtils.byte2Object(cacheValue);
 58                 }
 59                 return null;
 60             }
 61
 62         });
 63     }
 64
 65     @Override
 66     public long delete(final String key) {
 67         return jedisTemple.execute(new RedisPoolCallback<Long>() {
 68             @Override
 69             public Long doInJedis(Jedis jedis) {
 70                 return jedis.del(key);
 71             }
 72         });
 73     }
 74
 75     @Override
 76     public boolean add(final String key, Object value, final int seconds) {
 77         try {
 78             this.add(key, value);
 79             jedisTemple.execute(new RedisPoolCallback<Long>() {
 80                 @Override
 81                 public Long doInJedis(Jedis jedis) {
 82                     return jedis.expire(key, seconds);
 83                 }
 84             });
 85         } catch (Exception e) {
 86             e.printStackTrace();
 87             return false;
 88         }
 89         return true;
 90     }
 91
 92     @Override
 93     public boolean exists(final String key) {
 94         return jedisTemple.execute(new RedisPoolCallback<Boolean>() {
 95             @Override
 96             public Boolean doInJedis(Jedis jedis) {
 97                 return jedis.exists(key);
 98             }
 99         });
100     }
101
102 }

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">

    <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>

    <bean id="jedisClient" class="com.cxypub.baseframework.sdk.cache.RedisCacheClient">
        <constructor-arg ref="jedisPoolConfig" />
        <constructor-arg value="${redis.host}" />
        <constructor-arg value="${redis.port}" type="java.lang.Integer" />
    </bean>

</beans>

6、这样在项目中就可以将jedisClient 注入到任何类中了,我这里写了一个测试客户端,这个直接运行的,一同贴上。

 1 public class RedisTest {
 2     public static void main(String[] args) {
 3         JedisPoolConfig config = new JedisPoolConfig();
 4         config.setMaxTotal(500);
 5         config.setMaxIdle(5);
 6         config.setMaxWaitMillis(1000 * 100);
 7         config.setTestOnBorrow(true);
 8         RedisCacheClient client = new RedisCacheClient(config, "192.168.1.215", 6379);
 9         Dictionary dict = new Dictionary();
10         dict.setId("qwertryruyrtutyu");
11         dict.setDictChineseName("上海");
12         dict.setCreateTime(new Date());
13         client.add("xufei", dict);
14         Dictionary dict2 = (Dictionary) client.get("xufei");
15         System.out.println(dict2);
16         System.out.println(dict == dict2);
17     }
18 }
时间: 2024-10-14 19:40:07

spring整合redis客户端及缓存接口设计的相关文章

spring整合redis客户端及缓存接口设计(转)

一.写在前面 缓存作为系统性能优化的一大杀手锏,几乎在每个系统或多或少的用到缓存.有的使用本地内存作为缓存,有的使用本地硬盘作为缓存,有的使用缓存服务器.但是无论使用哪种缓存,接口中的方法都是差不多.笔者最近的项目使用的是memcached作为缓存服务器,由于memcached的一些限制,现在想换redis作为缓存服务器.思路就是把memached的客户端换成redis客户端,接口依然是原来的接口,这样对系统可以无损替换,接口不变,功能不变,只是客户端变了.本文不介绍缓存的用法,不介绍redis

Spring整合Redis做数据缓存(Windows环境)

当我们一个项目的数据量很大的时候,就需要做一些缓存机制来减轻数据库的压力,提升应用程序的性能,对于java项目来说,最常用的缓存组件有Redis.Ehcache和Memcached. Ehcache是用java开发的缓存组件,和java结合良好,直接在jvm虚拟机中运行,不需要额外安装什么东西,效率也很高:但是由于和java结合的太紧密了,导致缓存共享麻烦,分布式集群应用不方便,所以比较适合单个部署的应用. Redis需要额外单独安装,是通过socket访问到缓存服务,效率比Ehcache低,但

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

现在越来越多的地方需要非关系型数据库了,最近网站优化,当然从页面到服务器做了相应的优化后,通过在线网站测试工具与之前没优化对比,发现有显著提升. 服务器优化目前主要优化tomcat,在tomcat目录下的server.xml文件配置如下内容: <Connector port="1818" protocol="HTTP/1.1" maxHttpHeaderSize="8192" maxThreads="1000" minS

mybatis整合Redis实现二级缓存

Mybatis整合ehcache实现二级缓存 导入相关依赖 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <!--mybatis与ehcache整合--

(转)Spring整合Redis作为缓存

采用Redis作为Web系统的缓存.用Spring的Cache整合Redis. 一.关于redis的相关xml文件的写法 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:cache="http://www.springframework.org/schema/c

使用Spring Cache + Redis + Jackson Serializer缓存数据库查询结果中序列化问题的解决

应用场景 我们希望通过缓存来减少对关系型数据库的查询次数,减轻数据库压力.在执行DAO类的select***(), query***()方法时,先从Redis中查询有没有缓存数据,如果有则直接从Redis拿到结果,如果没有再向数据库发起查询请求取数据. 序列化问题 要把domain object做为key-valuec对保存在redis中,就必须要解决对象的序列化问题.Spring Data Redis给我们提供了一些现成的方案: JdkSerializationRedisSerializer.

spring 集成redis客户端jedis(java)

jedis是redis的java客户端,spring将redis连接池作为一个bean配置. "redis.clients.jedis.JedisPool",这是单机环境适用的redis连接池. 1.maven导入相关包: <!-- redis依赖包 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> &l

【原】Spring整合Redis(第一篇)—SDR简述

1.SDR说明 Spring Data Redis(SDR),是SpringFramework提供的一套简化访问Redis的API,是对Jedis的又一层封装. SDR集成了Jedis,JRedis,SRP,Lettuce这四种开源的Redis Connector,这些Connector都是针对于Redis的开源Java库.其中,JRedis和SRP从spring-data-redis1.7开始,就不支持了. 2.RedisTemplate说明 RedisTemplate是SDR的一个核心Hel

【原】Spring整合Redis(第三篇)—SDR搭建中易出现的错误

易错点01:Spring版本过低导致的错误[环境参数]Redis版本:redis-2.4.5-win32-win64Spring原来的版本:4.1.7.RELEASESpring修改后的版本:4.2.6.RELEASE [障碍描述]Question:NoSuchMethodErrorInvocation of init method failed; nested exception is java.lang.NoSuchMethodError: org.springframework.core.