Redis整合Spring結合使用緩衝執行個體
林炳文Evankaka原創作品。轉載請註明出處http://blog.csdn.net/evankaka
摘要:本文介紹了如何在Spring中配置redis,並通過Spring中AOP的思想,將緩衝的方法切入到有需要進入緩衝的類或方法前面。
一、Redis介紹
什麼是Redis?
redis是一個key-value儲存系統。和Memcached類似,它支援儲存的value類型相對更多,包括string(字串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(雜湊類型)。這些資料類型都支援push/pop、add/remove及取交集並集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎上,redis支援各種不同方式的排序。與memcached一樣,為了保證效率,資料都是緩衝在記憶體中。區別的是redis會周期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了master-slave(主從)同步。
它有什麼特點?
(1)Redis資料庫完全在記憶體中,使用磁碟僅用於持久性。
(2)相比許多索引值資料存放區,Redis擁有一套較為豐富的資料類型。
(3)Redis可以將資料複製到任意數量的從伺服器。
Redis 優勢?
(1)異常快速:Redis的速度非常快,每秒能執行約11萬集合,每秒約81000+條記錄。
(2)支援豐富的資料類型:Redis支援最大多數開發人員已經知道像列表,集合,有序集合,散列資料類型。這使得它非常容易解決各種各樣的問題,因為我們知道哪些問題是可以處理通過它的資料類型更好。
(3)操作都是原子性:所有Redis操作是原子的,這保證了如果兩個用戶端同時訪問的Redis伺服器將獲得更新後的值。
(4)多功能工具 + 生產力:Redis是一個多實用的工具,可以在多個用例如緩衝,訊息,隊列使用(Redis原生支援發布/訂閱),任何短暫的資料,應用程式,如Web應用程式工作階段,網頁命中計數等。
Redis 缺點?
(1)單線程
(2)耗記憶體
二、使用執行個體
本文使用maven+eclipse+sping
1、引入jar包
org.springframework.dataspring-data-redis1.6.1.RELEASEredis.clientsjedis2.7.3
2、配置bean
在application.xml加入如下配置
其中設定檔redis一些配置資料redis.properties如下:
#redis中心redis.host=10.75.202.11redis.port=6379redis.password=123456redis.maxIdle=100redis.maxActive=300redis.maxWait=1000redis.testOnBorrow=trueredis.timeout=100000# 不需要加入緩衝的類targetNames=xxxRecordManager,xxxSetRecordManager,xxxStatisticsIdentificationManager# 不需要緩衝的方法methodNames=#設定緩衝失效時間com.service.impl.xxxRecordManager= 60com.service.impl.xxxSetRecordManager= 60defaultCacheExpireTime=3600fep.local.cache.capacity =10000
要掃這些properties檔案,在application.xml加入如下配置
classpath:properties/*.properties
3、一些工具類
(1)RedisUtil
上面的bean中,RedisUtil是用來緩衝和去除資料的執行個體
package com.mucfc.msm.common;import java.io.Serializable;import java.util.Set;import java.util.concurrent.TimeUnit;import org.apache.log4j.Logger;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ValueOperations;/** * redis cache 工具類 * */public final class RedisUtil {private Logger logger = Logger.getLogger(RedisUtil.class);private RedisTemplate redisTemplate;/** * 大量刪除對應的value * * @param keys */public void remove(final String... keys) {for (String key : keys) {remove(key);}}/** * 大量刪除key * * @param pattern */public void removePattern(final String pattern) {Set keys = redisTemplate.keys(pattern);if (keys.size() > 0)redisTemplate.delete(keys);}/** * 刪除對應的value * * @param key */public void remove(final String key) {if (exists(key)) {redisTemplate.delete(key);}}/** * 判斷緩衝中是否有對應的value * * @param key * @return */public boolean exists(final String key) {return redisTemplate.hasKey(key);}/** * 讀取緩衝 * * @param key * @return */public Object get(final String key) {Object result = null;ValueOperations operations = redisTemplate.opsForValue();result = operations.get(key);return result;}/** * 寫入緩衝 * * @param key * @param value * @return */public boolean set(final String key, Object value) {boolean result = false;try {ValueOperations operations = redisTemplate.opsForValue();operations.set(key, value);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/** * 寫入緩衝 * * @param key * @param value * @return */public boolean set(final String key, Object value, Long expireTime) {boolean result = false;try {ValueOperations operations = redisTemplate.opsForValue();operations.set(key, value);redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);result = true;} catch (Exception e) {e.printStackTrace();}return result;}public void setRedisTemplate(RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}}(2)MethodCacheInterceptor
切面MethodCacheInterceptor,這是用來給不同的方法來加入判斷如果緩衝存在資料,從緩衝取資料。否則第一次從資料庫取,並將結果儲存到緩衝 中去。
package com.mucfc.msm.common;import java.io.File;import java.io.FileInputStream;import java.io.InputStream;import java.util.ArrayList;import java.util.List;import java.util.Properties;import org.aopalliance.intercept.MethodInterceptor;import org.aopalliance.intercept.MethodInvocation;import org.apache.log4j.Logger;public class MethodCacheInterceptor implements MethodInterceptor {private Logger logger = Logger.getLogger(MethodCacheInterceptor.class);private RedisUtil redisUtil;private List targetNamesList; // 不加入緩衝的service名稱private List methodNamesList; // 不加入緩衝的方法名稱private Long defaultCacheExpireTime; // 緩衝預設的到期時間private Long xxxRecordManagerTime; //private Long xxxSetRecordManagerTime; ///** * 初始化讀取不需要加入緩衝的類名和方法名稱 */public MethodCacheInterceptor() {try { File f = new File("D:\\lunaJee-workspace\\msm\\msm_core\\src\\main\\java\\com\\mucfc\\msm\\common\\cacheConf.properties"); //設定檔位置直接被寫死,有需要自己修改下 InputStream in = new FileInputStream(f); //InputStream in = getClass().getClassLoader().getResourceAsStream(//"D:\\lunaJee-workspace\\msm\\msm_core\\src\\main\\java\\com\\mucfc\\msm\\common\\cacheConf.properties");Properties p = new Properties();p.load(in);// 分割字串String[] targetNames = p.getProperty("targetNames").split(",");String[] methodNames = p.getProperty("methodNames").split(",");// 載入到期時間設定defaultCacheExpireTime = Long.valueOf(p.getProperty("defaultCacheExpireTime"));xxxRecordManagerTime = Long.valueOf(p.getProperty("com.service.impl.xxxRecordManager"));xxxSetRecordManagerTime = Long.valueOf(p.getProperty("com.service.impl.xxxSetRecordManager"));// 建立listtargetNamesList = new ArrayList(targetNames.length);methodNamesList = new ArrayList(methodNames.length);Integer maxLen = targetNames.length > methodNames.length ? targetNames.length: methodNames.length;// 將不需要緩衝的類名和方法名添加到list中for (int i = 0; i < maxLen; i++) {if (i < targetNames.length) {targetNamesList.add(targetNames[i]);}if (i < methodNames.length) {methodNamesList.add(methodNames[i]);}}} catch (Exception e) {e.printStackTrace();}}@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {Object value = null;String targetName = invocation.getThis().getClass().getName();String methodName = invocation.getMethod().getName();// 不需要緩衝的內容//if (!isAddCache(StringUtil.subStrForLastDot(targetName), methodName)) {if (!isAddCache(targetName, methodName)) {// 執行方法返回結果return invocation.proceed();}Object[] arguments = invocation.getArguments();String key = getCacheKey(targetName, methodName, arguments);System.out.println(key);try {// 判斷是否有緩衝if (redisUtil.exists(key)) {return redisUtil.get(key);}// 寫入緩衝value = invocation.proceed();if (value != null) {final String tkey = key;final Object tvalue = value;new Thread(new Runnable() {@Overridepublic void run() {if (tkey.startsWith("com.service.impl.xxxRecordManager")) {redisUtil.set(tkey, tvalue, xxxRecordManagerTime);} else if (tkey.startsWith("com.service.impl.xxxSetRecordManager")) {redisUtil.set(tkey, tvalue, xxxSetRecordManagerTime);} else {redisUtil.set(tkey, tvalue, defaultCacheExpireTime);}}}).start();}} catch (Exception e) {e.printStackTrace();if (value == null) {return invocation.proceed();}}return value;}/** * 是否加入緩衝 * * @return */private boolean isAddCache(String targetName, String methodName) {boolean flag = true;if (targetNamesList.contains(targetName)|| methodNamesList.contains(methodName)) {flag = false;}return flag;}/** * 建立緩衝key * * @param targetName * @param methodName * @param arguments */private String getCacheKey(String targetName, String methodName,Object[] arguments) {StringBuffer sbu = new StringBuffer();sbu.append(targetName).append("_").append(methodName);if ((arguments != null) && (arguments.length != 0)) {for (int i = 0; i < arguments.length; i++) {sbu.append("_").append(arguments[i]);}}return sbu.toString();}public void setRedisUtil(RedisUtil redisUtil) {this.redisUtil = redisUtil;}}
4、配置需要緩衝的類或方法
在application.xml加入如下配置,有多個類或方法可以配置多個
com\.mucfc\.msm\.service\.impl\...*ServiceImpl.*
5、執行結果:
寫了一個簡單的單元測試如下:
@Test public void getSettUnitBySettUnitIdTest() { String systemId = "CES"; String merchantId = "133"; SettUnit configSettUnit = settUnitService.getSettUnitBySettUnitId(systemId, merchantId, "ESP"); SettUnit configSettUnit1 = settUnitService.getSettUnitBySettUnitId(systemId, merchantId, "ESP"); boolean flag= (configSettUnit == configSettUnit1); System.out.println(configSettUnit); logger.info("尋找結果" + configSettUnit.getBusinessType()); // localSecondFIFOCache.put("configSettUnit", configSettUnit.getBusinessType()); // String string = localSecondFIFOCache.get("configSettUnit");// logger.info("尋找結果" + string); }這是第一次執行單元測試的過程:
MethodCacheInterceptor這個類中打了斷點,然後每次查詢前都會先進入這個方法
依次運行,發現沒有緩衝,所以會直接去查資料庫
列印了出來的SQL語句:
第二次執行:
因為第一次執行時,已經寫入緩衝了。所以第二次直接從緩衝中取資料
3、取兩次的結果進行地址的對比:
發現兩個不是同一個對象,沒錯,是對的。如果是使用ehcache的話,那麼二者的記憶體位址會是一樣的。那是因為redis和ehcache使用的緩衝機制是不一樣的。ehcache是基於本地電腦的記憶體使用量緩衝,所以使用緩衝取資料時直接在本地電腦上取。轉換成java對象就會是同一個記憶體位址,而redis它是在裝有redis服務的電腦上(一般是另一台電腦),所以取資料時經過傳輸到本地,會對應到不同的記憶體位址,所以用==來比較會返回false。但是它確實是從緩衝中去取的,這點我們從上面的斷點可以看到。