Redis整合Spring結(jié)合使用緩存實(shí)例
一、Redis介紹
什么是Redis?
redis是一個(gè)key-value存儲(chǔ)系統(tǒng)。和Memcached類似,它支持存儲(chǔ)的value類型相對(duì)更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數(shù)據(jù)類型都支持push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。在此基礎(chǔ)上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數(shù)據(jù)都是緩存在內(nèi)存中。區(qū)別的是redis會(huì)周期性的把更新的數(shù)據(jù)寫(xiě)入磁盤或者把修改操作寫(xiě)入追加的記錄文件,并且在此基礎(chǔ)上實(shí)現(xiàn)了master-slave(主從)同步。
它有什么特點(diǎn)?
(1)Redis數(shù)據(jù)庫(kù)完全在內(nèi)存中,使用磁盤僅用于持久性。
(2)相比許多鍵值數(shù)據(jù)存儲(chǔ),Redis擁有一套較為豐富的數(shù)據(jù)類型。
(3)Redis可以將數(shù)據(jù)復(fù)制到任意數(shù)量的從服務(wù)器。
Redis 優(yōu)勢(shì)?
(1)異常快速:Redis的速度非???,每秒能執(zhí)行約11萬(wàn)集合,每秒約81000+條記錄。
(2)支持豐富的數(shù)據(jù)類型:Redis支持最大多數(shù)開(kāi)發(fā)人員已經(jīng)知道像列表,集合,有序集合,散列數(shù)據(jù)類型。這使得它非常容易解決各種各樣的問(wèn)題,因?yàn)槲覀冎滥男﹩?wèn)題是可以處理通過(guò)它的數(shù)據(jù)類型更好。
(3)操作都是原子性:所有Redis操作是原子的,這保證了如果兩個(gè)客戶端同時(shí)訪問(wèn)的Redis服務(wù)器將獲得更新后的值。
(4)多功能實(shí)用工具:Redis是一個(gè)多實(shí)用的工具,可以在多個(gè)用例如緩存,消息,隊(duì)列使用(Redis原生支持發(fā)布/訂閱),任何短暫的數(shù)據(jù),應(yīng)用程序,如Web應(yīng)用程序會(huì)話,網(wǎng)頁(yè)命中計(jì)數(shù)等。
Redis 缺點(diǎn)?
(1)單線程
(2)耗內(nèi)存
二、使用實(shí)例
本文使用maven+eclipse+sping
1、引入jar包
<!--Redis start --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.1.RELEASE</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.3</version> </dependency> <!--Redis end -->
2、配置bean
在application.xml加入如下配置
<!-- jedis 配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig" >
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean >
<!-- redis服務(wù)器中心 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
<property name="poolConfig" ref="poolConfig" />
<property name="port" value="${redis.port}" />
<property name="hostName" value="${redis.host}" />
<property name="password" value="${redis.password}" />
<property name="timeout" value="${redis.timeout}" ></property>
</bean >
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" >
<property name="connectionFactory" ref="connectionFactory" />
<property name="keySerializer" >
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer" >
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
</bean >
<!-- cache配置 -->
<bean id="methodCacheInterceptor" class="com.mucfc.msm.common.MethodCacheInterceptor" >
<property name="redisUtil" ref="redisUtil" />
</bean >
<bean id="redisUtil" class="com.mucfc.msm.common.RedisUtil" >
<property name="redisTemplate" ref="redisTemplate" />
</bean >
其中配置文件redis一些配置數(shù)據(jù)redis.properties如下:
#redis中心 redis.host=10.75.202.11 redis.port=6379 redis.password=123456 redis.maxIdle=100 redis.maxActive=300 redis.maxWait=1000 redis.testOnBorrow=true redis.timeout=100000 # 不需要加入緩存的類 targetNames=xxxRecordManager,xxxSetRecordManager,xxxStatisticsIdentificationManager # 不需要緩存的方法 methodNames= #設(shè)置緩存失效時(shí)間 com.service.impl.xxxRecordManager= 60 com.service.impl.xxxSetRecordManager= 60 defaultCacheExpireTime=3600 fep.local.cache.capacity =10000
要掃這些properties文件,在application.xml加入如下配置
<!-- 引入properties配置文件 -->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:properties/*.properties</value>
<!--要是有多個(gè)配置文件,只需在這里繼續(xù)添加即可 -->
</list>
</property>
</bean>
3、一些工具類
(1)RedisUtil
上面的bean中,RedisUtil是用來(lái)緩存和去除數(shù)據(jù)的實(shí)例
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<Serializable, Object> redisTemplate;
/**
* 批量刪除對(duì)應(yīng)的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<Serializable> keys = redisTemplate.keys(pattern);
if (keys.size() > 0)
redisTemplate.delete(keys);
}
/**
* 刪除對(duì)應(yīng)的value
*
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判斷緩存中是否有對(duì)應(yīng)的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<Serializable, Object> operations = redisTemplate
.opsForValue();
result = operations.get(key);
return result;
}
/**
* 寫(xiě)入緩存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate
.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 寫(xiě)入緩存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value, Long expireTime) {
boolean result = false;
try {
ValueOperations<Serializable, Object> 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<Serializable, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
}
(2)MethodCacheInterceptor
切面MethodCacheInterceptor,這是用來(lái)給不同的方法來(lái)加入判斷如果緩存存在數(shù)據(jù),從緩存取數(shù)據(jù)。否則第一次從數(shù)據(jù)庫(kù)取,并將結(jié)果保存到緩存 中去。
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<String> targetNamesList; // 不加入緩存的service名稱
private List<String> methodNamesList; // 不加入緩存的方法名稱
private Long defaultCacheExpireTime; // 緩存默認(rèn)的過(guò)期時(shí)間
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");
//配置文件位置直接被寫(xiě)死,有需要自己修改下
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(",");
// 加載過(guò)期時(shí)間設(shè)置
defaultCacheExpireTime = Long.valueOf(p.getProperty("defaultCacheExpireTime"));
xxxRecordManagerTime = Long.valueOf(p.getProperty("com.service.impl.xxxRecordManager"));
xxxSetRecordManagerTime = Long.valueOf(p.getProperty("com.service.impl.xxxSetRecordManager"));
// 創(chuàng)建list
targetNamesList = new ArrayList<String>(targetNames.length);
methodNamesList = new ArrayList<String>(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();
}
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object value = null;
String targetName = invocation.getThis().getClass().getName();
String methodName = invocation.getMethod().getName();
// 不需要緩存的內(nèi)容
//if (!isAddCache(StringUtil.subStrForLastDot(targetName), methodName)) {
if (!isAddCache(targetName, methodName)) {
// 執(zhí)行方法返回結(jié)果
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);
}
// 寫(xiě)入緩存
value = invocation.proceed();
if (value != null) {
final String tkey = key;
final Object tvalue = value;
new Thread(new Runnable() {
@Override
public 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;
}
/**
* 創(chuàng)建緩存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加入如下配置,有多個(gè)類或方法可以配置多個(gè)
<!-- 需要加入緩存的類或方法 -->
<bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" >
<property name="advice" >
<ref local="methodCacheInterceptor" />
</property>
<property name="patterns" >
<list>
<!-- 確定正則表達(dá)式列表 -->
<value>com\.mucfc\.msm\.service\.impl\...*ServiceImpl.*</value >
</list>
</property>
</bean >
5、執(zhí)行結(jié)果:
寫(xiě)了一個(gè)簡(jiǎn)單的單元測(cè)試如下:
@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("查找結(jié)果" + configSettUnit.getBusinessType());
// localSecondFIFOCache.put("configSettUnit", configSettUnit.getBusinessType());
// String string = localSecondFIFOCache.get("configSettUnit");
logger.info("查找結(jié)果" + string);
}
這是第一次執(zhí)行單元測(cè)試的過(guò)程:
MethodCacheInterceptor這個(gè)類中打了斷點(diǎn),然后每次查詢前都會(huì)先進(jìn)入這個(gè)方法

依次運(yùn)行,發(fā)現(xiàn)沒(méi)有緩存,所以會(huì)直接去查數(shù)據(jù)庫(kù)

打印了出來(lái)的SQL語(yǔ)句:

第二次執(zhí)行:
因?yàn)榈谝淮螆?zhí)行時(shí),已經(jīng)寫(xiě)入緩存了。所以第二次直接從緩存中取數(shù)據(jù)

3、取兩次的結(jié)果進(jìn)行地址的對(duì)比:
發(fā)現(xiàn)兩個(gè)不是同一個(gè)對(duì)象,沒(méi)錯(cuò),是對(duì)的。如果是使用ehcache的話,那么二者的內(nèi)存地址會(huì)是一樣的。那是因?yàn)閞edis和ehcache使用的緩存機(jī)制是不一樣的。ehcache是基于本地電腦的內(nèi)存使用緩存,所以使用緩存取數(shù)據(jù)時(shí)直接在本地電腦上取。轉(zhuǎn)換成java對(duì)象就會(huì)是同一個(gè)內(nèi)存地址,而redis它是在裝有redis服務(wù)的電腦上(一般是另一臺(tái)電腦),所以取數(shù)據(jù)時(shí)經(jīng)過(guò)傳輸?shù)奖镜?,?huì)對(duì)應(yīng)到不同的內(nèi)存地址,所以用==來(lái)比較會(huì)返回false。但是它確實(shí)是從緩存中去取的,這點(diǎn)我們從上面的斷點(diǎn)可以看到。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
CentOS系統(tǒng)下Redis安裝和自啟動(dòng)配置的步驟
相信大家都知道Redis是一個(gè)C實(shí)現(xiàn)的基于內(nèi)存、可持久化的鍵值對(duì)數(shù)據(jù)庫(kù),在分布式服務(wù)中常作為緩存服務(wù)。所以這篇文章將詳細(xì)介紹在CentOS系統(tǒng)下如何從零開(kāi)始安裝到配置啟動(dòng)服務(wù)。有需要的可以參考借鑒。2016-09-09
Redis源碼環(huán)境構(gòu)建過(guò)程詳解
這篇文章主要介紹了Redis源碼環(huán)境構(gòu)建過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07
Redis實(shí)戰(zhàn)之百度首頁(yè)新聞熱榜的實(shí)現(xiàn)代碼
這篇文章主要介紹了Redis實(shí)戰(zhàn)之百度首頁(yè)新聞熱榜的實(shí)現(xiàn)代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
redis字符串類型_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了redis字符串類型的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
redis通過(guò)lua腳本,獲取滿足key pattern的所有值方式
這篇文章主要介紹了redis通過(guò)lua腳本,獲取滿足key pattern的所有值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-03-03
如何利用Redis分布式鎖實(shí)現(xiàn)控制并發(fā)操作
這篇文章主要介紹了如何利用Redis分布式鎖實(shí)現(xiàn)控制并發(fā)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
詳解RedisTemplate下Redis分布式鎖引發(fā)的系列問(wèn)題
這篇文章主要介紹了詳解RedisTemplate下Redis分布式鎖引發(fā)的系列問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03

