Spring整合Redis做数据缓存(Windows环境)
當(dāng)我們一個(gè)項(xiàng)目的數(shù)據(jù)量很大的時(shí)候,就需要做一些緩存機(jī)制來減輕數(shù)據(jù)庫的壓力,提升應(yīng)用程序的性能,對于java項(xiàng)目來說,最常用的緩存組件有Redis、Ehcache和Memcached。
Ehcache是用java開發(fā)的緩存組件,和java結(jié)合良好,直接在jvm虛擬機(jī)中運(yùn)行,不需要額外安裝什么東西,效率也很高;但是由于和java結(jié)合的太緊密了,導(dǎo)致緩存共享麻煩,分布式集群應(yīng)用不方便,所以比較適合單個(gè)部署的應(yīng)用。
Redis需要額外單獨(dú)安裝,是通過socket訪問到緩存服務(wù),效率比Ehcache低,但比數(shù)據(jù)庫要快很多很多,而且處理集群和分布式緩存方便,有成熟的方案,比較適合分布式集群部署的項(xiàng)目;也有很多的應(yīng)用將Ehcache和Redis結(jié)合使用,做成二級緩存。
至于Memcached嘛和Redis很類似,功能方面嘛理論上來說沒有Redis強(qiáng)大(但對于我們來說也完全足夠了),因此我們這里先不講,后面如果有時(shí)間在寫一篇關(guān)于Memcached的文章;由于我們后面會(huì)涉及到Tomcat的集群部署,所以這里就先講講Redis的應(yīng)用,好為后面的文章打個(gè)基礎(chǔ)~~下面正式開始!
?
代碼URL:http://git.oschina.net/tian5017/UserDemoRedis
?
一、Redis環(huán)境準(zhǔn)備
Redis有中文官方網(wǎng)站,地址為http://www.redis.cn/
Redis沒有官方的Windows版本,但是微軟開源技術(shù)團(tuán)隊(duì)(Microsoft Open Tech group)開發(fā)和維護(hù)著一個(gè) Win64 的版本,下載地址為
https://github.com/MicrosoftArchive/redis/releases
截止文章完成之時(shí),Redis的最新win64版本為3.2.100,我們這里就是使用的此版本;下載安裝好之后,打開安裝目錄,如下
上圖紅框中標(biāo)出的redis-cli.exe就是我們用來操作Redis的客戶端,要操作Redis,先要啟動(dòng)Redis服務(wù),在windows中將Redis作為服務(wù)啟動(dòng)的方法是,在上圖的安裝目錄下,打開命令行窗口,輸入命令
redis-server --service-install redis.windows.conf --loglevel verbose
這樣就將Redis作為Windows的服務(wù)啟動(dòng)了
然后再在此安裝目錄下打開命令行窗口,輸入redis-cli.exe回車,如下
?可以看到已經(jīng)連接到了Redis,地址是127.0.0.1(本機(jī)),默認(rèn)的端口為6379,至于操作Redis的命令,大家可以自己百度,常用的也就那么幾個(gè),很簡單,我們來簡單演示下
Redis是采用key-value鍵值對來存儲數(shù)據(jù)的,使用命令 set "haha" "123456",就表示將值(value)"123456"賦給鍵(key)"haha",再用 get "haha",就可以查看到值,好了關(guān)于Redis的別的東西,大家自己去玩,我們繼續(xù)我們的正題。
二、Spring集成
1、我們都知道,要在java中使用一個(gè)組件或者框架什么的,第一步都是加載它的jar包,java中操作Redis的jar包叫做jedis,這里我們還是利用上一篇文章《Spring+Mybatis+SpringMVC整合》所建立的UserDemo(項(xiàng)目連接:https://git.oschina.net/tian5017/UserDemo)
2、利用Maven來加載jedis的jar包,在UserDemo中的pom.xml的dependencies中添加如下代碼
<!-- redis相關(guān) --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.8.1</version></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId><version>1.7.10.RELEASE</version></dependency>?
?除了jedis之外,還需要另外一個(gè)jar包spring-data-redis,這是讓Spring管理Redis用的;我們先來改造我們的代碼,試試往Redis里面存點(diǎn)數(shù)據(jù),再來查詢。
3、在/resources/下新建redis.properties文件
配置項(xiàng)如下
##Redis連接信息配置 #redis地址 cache.redis.host=127.0.0.1 #redis端口號 cache.redis.port=6379 #redis密碼 cache.redis.password= #redis使用的數(shù)據(jù)庫(Redis內(nèi)置18個(gè)數(shù)據(jù)庫,編號為0-17,默認(rèn)使用0) cache.redis.db=0 #redis鏈接超時(shí)時(shí)間 cache.redis.timeout=2000 #redis鏈接池中最大空閑數(shù) cache.redis.maxIdle=5 #redis鏈接池中最大連接數(shù) cache.redis.maxActive=20 #建立連接最長等待時(shí)間 cache.redis.maxWait=1000?
4、在/resources/springConfig/下新建applicationContext-redis.xml文件作為Spring集成Redis的配置文件
配置代碼如下
<?xml version="1.0" encoding="UTF-8"?> <!-- 緩存redis相關(guān)配置 --> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"><bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"><property name="maxTotal" value="${cache.redis.maxActive}"/><property name="maxIdle" value="${cache.redis.maxIdle}"/><property name="maxWaitMillis" value="${cache.redis.maxWait}"/><property name="testOnBorrow" value="${cache.redis.testOnBorrow}"/></bean><bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"><property name="usePool" value="true"/><property name="hostName" value="${cache.redis.host}"/><property name="port" value="${cache.redis.port}"/><property name="password" value="${cache.redis.password}"/><property name="timeout" value="${cache.redis.timeout}"/><property name="database" value="${cache.redis.db}"/><constructor-arg index="0" ref="jedisPoolConfig"/></bean><bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" /><bean id="redisCache" class="org.springframework.data.redis.core.RedisTemplate"><property name="connectionFactory" ref="redisConnectionFactory"/><property name="keySerializer" ref="stringRedisSerializer"/><property name="valueSerializer" ref="stringRedisSerializer"/><property name="hashKeySerializer" ref="stringRedisSerializer"/><property name="hashValueSerializer" ref="stringRedisSerializer"/></bean> </beans>?
?5、到這里,我們的準(zhǔn)備工作已經(jīng)全部完成,接下來就是在代碼中使用redis了,我們修改UserServiceImpl.java
修改思路為,查詢數(shù)據(jù)的時(shí)候,先查詢Redis緩存,如果查到了就直接返回?cái)?shù)據(jù),如果沒查到數(shù)據(jù),就去數(shù)據(jù)庫中查詢,查到了數(shù)據(jù)先緩存進(jìn)Redis再返回,代碼如下:
package com.user.demo.service.impl;import com.alibaba.fastjson.JSON; import com.user.demo.dao.UserDao; import com.user.demo.entity.User; import com.user.demo.service.IUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils;import javax.annotation.Resource; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set;/*** Service接口實(shí)現(xiàn)類*/ @Service public class UserServiceImpl implements IUserService {@Resourceprivate UserDao userDao;@Autowiredprivate RedisTemplate<String, String> redisCache;@Overridepublic List<User> findAll() {List<User> users = new ArrayList<User>();//先從redis緩存中獲取數(shù)據(jù),如果緩存中沒有,去數(shù)據(jù)庫中查詢數(shù)據(jù),查到后在寫入緩存Set<String> sets = redisCache.keys("USER*");if(sets==null || sets.isEmpty()){users = userDao.findAll();if(!CollectionUtils.isEmpty(users)){for(User user : users){redisCache.opsForValue().set("USER"+user.getUserId(), JSON.toJSONString(user));}}}else{Iterator<String> it = sets.iterator();while (it.hasNext()){String item = it.next();String value = redisCache.opsForValue().get(item);users.add(JSON.parseObject(value, User.class));}}return users;}@Override@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) //事物public void saveUser(User user) {userDao.saveUser(user);} }?
6、接下來我們來驗(yàn)證緩存是否生效,編譯,運(yùn)行項(xiàng)目,如下圖
打開redis-cli,輸入命令KEYS * (查看所有的key),如下
可以看到我們的緩存已經(jīng)加入進(jìn)去了,key就是我們在UserServiceImpl.java中設(shè)置的(redisCache.opsForValue().set("USER"+user.getUserId(), JSON.toJSONString(user))),以USER開頭加上userId構(gòu)成,為了進(jìn)一步驗(yàn)證有了緩存之后,查詢的數(shù)據(jù)優(yōu)先來源于緩存,我們在數(shù)據(jù)庫中刪除一條數(shù)據(jù),就刪除userId為1的那條數(shù)據(jù)
??
刪除之后,由于我們代碼中檢測緩存的邏輯只是檢測了能否查詢到緩存,而沒有檢測緩存數(shù)量是否和數(shù)據(jù)庫數(shù)據(jù)量一致,所以緩存中還是會(huì)存在這條數(shù)據(jù)
? ? ? ? ? ? ??
可以看到,userId為1的數(shù)據(jù)依然存在,說明數(shù)據(jù)是從Redis緩存中查詢出來的。
三、數(shù)據(jù)同步
上面其實(shí)說明了一個(gè)問題,就是數(shù)據(jù)同步,我們很可能會(huì)出現(xiàn),數(shù)據(jù)庫中數(shù)據(jù)變了,但是緩存中的數(shù)據(jù)還沒有變,因此和數(shù)據(jù)庫中的數(shù)據(jù)不一致,所以我們需要一些策略來保證數(shù)據(jù)庫的數(shù)據(jù)和Redis的數(shù)據(jù)能夠保持一致,至于選用什么策略,那要具體項(xiàng)目具體分析,如果對數(shù)據(jù)的實(shí)時(shí)性要求很高的項(xiàng)目,那么就要在查詢的時(shí)候,檢測數(shù)據(jù)庫的數(shù)據(jù)和Redis的緩存數(shù)據(jù)是否一致,如果不一致就要刷新Redis的數(shù)據(jù),但是這樣必然對性能會(huì)有很高的要求;如果項(xiàng)目對數(shù)據(jù)的實(shí)時(shí)性要求沒有那么高,我們完全可以做一個(gè)定時(shí)任務(wù),比如每隔10分鐘或者半小時(shí)去數(shù)據(jù)庫拉一次數(shù)據(jù),再刷新到Redis緩存中,所以下面我們就來做一個(gè)定時(shí)任務(wù),每隔10分鐘去拉一次數(shù)據(jù),然后往Redis中刷新一次。
四、定時(shí)刷新緩存
?我們用Spring中的InitializingBean和DisposableBean接口(這兩個(gè)接口的具體用法請自行百度,大概就是在SpringBean的生命周期中,影響bean的行為,我們這里就是影響了bean,讓它去刷新緩存)來實(shí)現(xiàn)刷新緩存,在com.user.demo下新建包c(diǎn)ache,在cache下面新建類UserCache.java,具體代碼如下
package com.user.demo.cache;import com.alibaba.fastjson.JSON; import com.user.demo.dao.UserDao; import com.user.demo.entity.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.util.List; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit;/*** Redis緩存User數(shù)據(jù)并定時(shí)刷新* 每間隔一定時(shí)間后(如10分鐘)刷新一次緩存,刷新時(shí)另起一個(gè)線程,不影響執(zhí)行主任務(wù)的線程*/ @Component public class UserCache implements InitializingBean, DisposableBean {private final Logger log = LoggerFactory.getLogger(UserCache.class);//獲取電腦的CPU核心數(shù)量,設(shè)置線程池大小為核心數(shù)的2倍(比如我的電腦為4核心,那么這里的值就為8)private static final int CORE_NUM = Runtime.getRuntime().availableProcessors() * 2;//初始化值為redis.properties中配置的cache.redis.cacheExpire的值,表示每隔多長時(shí)間后執(zhí)行任務(wù)@Value("${cache.redis.cacheExpire}")private long cacheExpire;//執(zhí)行定時(shí)任務(wù)的類private ScheduledThreadPoolExecutor executor = null;@Resourceprivate RedisTemplate<String, String> redisCache;@Resourceprivate UserDao userDao;@Overridepublic void destroy() throws Exception {executor.shutdownNow();}@Overridepublic void afterPropertiesSet() throws Exception {executor = new ScheduledThreadPoolExecutor(CORE_NUM);RefreshCache refreshCache = new RefreshCache();refreshCache.run();executor.scheduleWithFixedDelay(refreshCache, cacheExpire, cacheExpire, TimeUnit.SECONDS);}//內(nèi)部類,開啟新線程執(zhí)行緩存刷新private class RefreshCache implements Runnable {@Overridepublic void run() {log.info("---開始刷新用戶信息緩存---");List<User> userList = userDao.findAll();if(!CollectionUtils.isEmpty(userList)){for(User user : userList){redisCache.opsForValue().set("USER" + user.getUserId(), JSON.toJSONString(user));}}}} }在redis.properties中加入cache.redis.cacheExpire配置項(xiàng),代碼如下
##Redis連接信息配置 #redis地址 cache.redis.host=127.0.0.1 #redis端口號 cache.redis.port=6379 #redis密碼 cache.redis.password= #redis使用的數(shù)據(jù)庫(Redis內(nèi)置18個(gè)數(shù)據(jù)庫,編號為0-17,默認(rèn)使用0) cache.redis.db=0 #redis鏈接超時(shí)時(shí)間 cache.redis.timeout=2000 #redis鏈接池中最大空閑數(shù) cache.redis.maxIdle=5 #redis鏈接池中最大連接數(shù) cache.redis.maxActive=20 #建立連接最長等待時(shí)間 cache.redis.maxWait=1000#定時(shí)任務(wù)執(zhí)行時(shí)間間隔,單位為毫秒,這里相當(dāng)于10分鐘 cache.redis.cacheExpire=600同時(shí)修改UserServiceImpl.java中的代碼如下
package com.user.demo.service.impl;import com.alibaba.fastjson.JSON; import com.user.demo.dao.UserDao; import com.user.demo.entity.User; import com.user.demo.service.IUserService; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set;/*** Service接口實(shí)現(xiàn)類*/ @Service public class UserServiceImpl implements IUserService {@Resourceprivate UserDao userDao;@Resourceprivate RedisTemplate<String, String> redisCache;@Overridepublic List<User> findAll() {List<User> result = new ArrayList<User>();Set<String> sets = redisCache.keys("USER*");//如果緩存有數(shù)據(jù)則從緩存中取數(shù)據(jù),如果沒有則從數(shù)據(jù)庫中取數(shù)據(jù)if(!CollectionUtils.isEmpty(sets)){Iterator<String> it = sets.iterator();while(it.hasNext()){String item = it.next();String value = redisCache.opsForValue().get(item);result.add(JSON.parseObject(value, User.class));}}else{result = userDao.findAll();}return result;}@Override@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) //事物public void saveUser(User user) {userDao.saveUser(user);}}此時(shí),項(xiàng)目已經(jīng)可以定時(shí)(每隔十分鐘)刷新緩存,我們編譯啟動(dòng)
我們新提交一個(gè)用戶“三德子”,剛提交后,刷新是查不出來數(shù)據(jù)的,但是數(shù)據(jù)庫是有數(shù)據(jù)的
? ??
然后等待10分鐘之后,再來查看,可以看到緩存已經(jīng)被自動(dòng)刷新到了Redis中
?
OK,到這里,這篇文章就結(jié)束了,關(guān)于Spring集成Redis做數(shù)據(jù)緩存我們也講的差不多了,其實(shí)關(guān)于Redis的應(yīng)用,遠(yuǎn)遠(yuǎn)不止這么簡單,我們可以很容易的搭建Redis集群,做分布式數(shù)據(jù)管理,也可以實(shí)現(xiàn)分布式session共享(這個(gè)我后面會(huì)有一篇文章講到),甚至假如寫數(shù)據(jù)量很大,我們也可以先緩存進(jìn)Redis中,再利用多線程來將數(shù)據(jù)寫入數(shù)據(jù)庫中,減輕數(shù)據(jù)庫的負(fù)擔(dān)(因?yàn)閿?shù)據(jù)庫寫操作是很耗費(fèi)資源的)等等~~那如果大家有什么意見和建議,也歡迎留言交流;下一篇文章我會(huì)講講mysql的讀寫分離,歡迎繼續(xù)關(guān)注!!
?
代碼URL:http://git.oschina.net/tian5017/UserDemoRedis
?
轉(zhuǎn)載于:https://www.cnblogs.com/tian5017/p/7171744.html
總結(jié)
以上是生活随笔為你收集整理的Spring整合Redis做数据缓存(Windows环境)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在一台电脑上运行两个或多个tomcat
- 下一篇: LInux命令随笔记