緩存做爲系統性能優化的一大殺手鐗,幾乎在每一個系統或多或少的用到緩存。有的使用本地內存做爲緩存,有的使用本地硬盤做爲緩存,有的使用緩存服務器。可是不管使用哪一種緩存,接口中的方法都是差很少。筆者最近的項目使用的是memcached做爲緩存服務器,因爲memcached的一些限制,如今想換redis做爲緩存服務器。思路就是把memached的客戶端換成redis客戶端,接口依然是原來的接口,這樣對系統能夠無損替換,接口不變,功能不變,只是客戶端變了。本文不介紹緩存的用法,不介紹redis使用方法,不介紹memcached與redis有何區別。只是實現一個redis客戶端,用了jedis做爲第三方鏈接工具。java
首先貼一下現項目中同事編寫的緩存接口: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後會不通用。性能優化
這裏沒有說這個接口寫的很差,而是說還有優化的空間,其次也給本身提個醒,在設計一些使用公共的接口時有必要多花些心思,由於一旦設計後,後面改動的可能性比較小。 服務器
使用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); } }