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

GregRhea 8年前發布 | 31K 次閱讀 Redis Spring JEE框架

來自: http://www.cnblogs.com/xumanbu/p/5160765.html

一、寫在前面

緩存作為系統性能優化的一大殺手锏,幾乎在每個系統或多或少的用到緩存。有的使用本地內存作為緩存,有的使用本地硬盤作為緩存,有的使用緩存服務器。但是無論使用哪種緩存,接口中的方法都是差不多。筆者最近的項目使用的是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);

}</pre>

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

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

}</pre>

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

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

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 }
</div>

 本文由用戶 GregRhea 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!