spring整合redis客戶端及緩存接口設計

1、寫在前面

緩存做爲系統性能優化的一大殺手鐗,幾乎在每一個系統或多或少的用到緩存。有的使用本地內存做爲緩存,有的使用本地硬盤做爲緩存,有的使用緩存服務器。可是不管使用哪一種緩存,接口中的方法都是差很少。筆者最近的項目使用的是memcached做爲緩存服務器,因爲memcached的一些限制,如今想換redis做爲緩存服務器。思路就是把memached的客戶端換成redis客戶端,接口依然是原來的接口,這樣對系統能夠無損替換,接口不變,功能不變,只是客戶端變了。本文不介紹緩存的用法,不介紹redis使用方法,不介紹memcached與redis有何區別。只是實現一個redis客戶端,用了jedis做爲第三方鏈接工具。java

2、一些想法

首先貼一下現項目中同事編寫的緩存接口:redis

/**
 * @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);
	
}

 

這個接口用起來總有一些彆扭,我總結了一下:spring

一、接口名稱命名爲DispersedCachClient 這個命名含義是分佈式緩存客戶端(cache少了一個字母),其實這個接口跟分佈式一點關係都沒有,其實就是緩存接口;數組

二、接口方法太多了,實際在項目中並無方法使用率只有20%左右,全部有精簡的必要;緩存

三、這個接口不少方法設計是套用memcached客戶端設計的,也就是說換成redis後會不通用。性能優化

這裏沒有說這個接口寫的很差,而是說還有優化的空間,其次也給本身提個醒,在設計一些使用公共的接口時有必要多花些心思,由於一旦設計後,後面改動的可能性比較小。 服務器

3、代碼實現

使用jedis客戶端須要使用鏈接池,使用鏈接後須要將鏈接放回鏈接池,失效的鏈接要放到失效的鏈接池,相似jdbc須要進行鏈接的處理,爲了不寫重複噁心的代碼,參照了spring的JdbcTemple模板設計方式。廢話沒有,直接上代碼吧。app

一、從新設計的緩存客戶端接口,這個接口就一個特色「簡單」,目的是爲了作到通用,故命名爲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);

}

  

二、JedisTemple :Jedis 操做模板類,請參照Spring的JdbcTemple封裝重複但又必要的操做ide

 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 }

三、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 }

 

四、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 }

 

 

五、實現了代碼,下面就開始將客戶端整合進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>

 

 

六、這樣在項目中就能夠將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);
    }
}
相關文章
相關標籤/搜索