1. 程式人生 > >Spring整合Redis做數據緩存(Windows環境)

Spring整合Redis做數據緩存(Windows環境)

端口號 init 技術分享 factory redis-cli @service tab long 配置

當我們一個項目的數據量很大的時候,就需要做一些緩存機制來減輕數據庫的壓力,提升應用程序的性能,對於java項目來說,最常用的緩存組件有Redis、Ehcache和Memcached。

Ehcache是用java開發的緩存組件,和java結合良好,直接在jvm虛擬機中運行,不需要額外安裝什麽東西,效率也很高;但是由於和java結合的太緊密了,導致緩存共享麻煩,分布式集群應用不方便,所以比較適合單個部署的應用。

Redis需要額外單獨安裝,是通過socket訪問到緩存服務,效率比Ehcache低,但比數據庫要快很多很多,而且處理集群和分布式緩存方便,有成熟的方案,比較適合分布式集群部署的項目;也有很多的應用將Ehcache和Redis結合使用,做成二級緩存。

至於Memcached嘛和Redis很類似,功能方面嘛理論上來說沒有Redis強大(但對於我們來說也完全足夠了),因此我們這裏先不講,後面如果有時間在寫一篇關於Memcached的文章;由於我們後面會涉及到Tomcat的集群部署,所以這裏就先講講Redis的應用,好為後面的文章打個基礎~~下面正式開始!

代碼URL:http://git.oschina.net/tian5017/UserDemoRedis

一、Redis環境準備

Redis有中文官方網站,地址為http://www.redis.cn/

Redis沒有官方的Windows版本,但是微軟開源技術團隊(Microsoft Open Tech group)開發和維護著一個 Win64 的版本,下載地址為

https://github.com/MicrosoftArchive/redis/releases

截止文章完成之時,Redis的最新win64版本為3.2.100,我們這裏就是使用的此版本;下載安裝好之後,打開安裝目錄,如下

技術分享

上圖紅框中標出的redis-cli.exe就是我們用來操作Redis的客戶端,在此安裝目錄下打開命令行窗口,輸入redis-cli.exe回車,如下

技術分享

可以看到已經連接到了Redis,地址是127.0.0.1(本機),默認的端口為6379,至於操作Redis的命令,大家可以自己百度,常用的也就那麽幾個,很簡單,我們來簡單演示下

技術分享

Redis是采用key-value鍵值對來存儲數據的,使用命令 set "haha" "123456",就表示將值(value)"123456"賦給鍵(key)"haha",再用 get "haha",就可以查看到值,好了關於Redis的別的東西,大家自己去玩,我們繼續我們的正題。

二、Spring集成

1、我們都知道,要在java中使用一個組件或者框架什麽的,第一步都是加載它的jar包,java中操作Redis的jar包叫做jedis,這裏我們還是利用上一篇文章《Spring+Mybatis+SpringMVC整合》所建立的UserDemo(項目連接:https://git.oschina.net/tian5017/UserDemo)

2、利用Maven來加載jedis的jar包,在UserDemo中的pom.xml的dependencies中添加如下代碼

    <!-- redis相關 -->
    <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之外,還需要另外一個jar包spring-data-redis,這是讓Spring管理Redis用的;我們先來改造我們的代碼,試試往Redis裏面存點數據,再來查詢。

3、在/resources/下新建redis.properties文件

技術分享

配置項如下

##Redis連接信息配置
#redis地址
cache.redis.host=127.0.0.1
#redis端口號
cache.redis.port=6379
#redis密碼
cache.redis.password=
#redis使用的數據庫(Redis內置18個數據庫,編號為0-17,默認使用0)
cache.redis.db=0
#redis鏈接超時時間
cache.redis.timeout=2000
#redis鏈接池中最大空閑數
cache.redis.maxIdle=5
#redis鏈接池中最大連接數
cache.redis.maxActive=20
#建立連接最長等待時間
cache.redis.maxWait=1000

4、在/resources/springConfig/下新建applicationContext-redis.xml文件作為Spring集成Redis的配置文件

技術分享

配置代碼如下

<?xml version="1.0" encoding="UTF-8"?>
<!-- 緩存redis相關配置 -->
<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/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://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、到這裏,我們的準備工作已經全部完成,接下來就是在代碼中使用redis了,我們修改UserServiceImpl.java

技術分享

修改思路為,查詢數據的時候,先查詢Redis緩存,如果查到了就直接返回數據,如果沒查到數據,就去數據庫中查詢,查到了數據先緩存進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接口實現類
 */
@Service
public class UserServiceImpl implements IUserService {

    @Resource
    private UserDao userDao;

    @Autowired
    private RedisTemplate<String, String> redisCache;

    @Override
    public List<User> findAll() {
        List<User> users = new ArrayList<User>();
        //先從redis緩存中獲取數據,如果緩存中沒有,去數據庫中查詢數據,查到後在寫入緩存
        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、接下來我們來驗證緩存是否生效,編譯,運行項目,如下圖

技術分享

打開redis-cli,輸入命令KEYS * (查看所有的key),如下

技術分享

可以看到我們的緩存已經加入進去了,key就是我們在UserServiceImpl.java中設置的(redisCache.opsForValue().set("USER"+user.getUserId(), JSON.toJSONString(user))),以USER開頭加上userId構成,為了進一步驗證有了緩存之後,查詢的數據優先來源於緩存,我們在數據庫中刪除一條數據,就刪除userId為1的那條數據

技術分享 技術分享

刪除之後,由於我們代碼中檢測緩存的邏輯只是檢測了能否查詢到緩存,而沒有檢測緩存數量是否和數據庫數據量一致,所以緩存中還是會存在這條數據

技術分享 技術分享

可以看到,userId為1的數據依然存在,說明數據是從Redis緩存中查詢出來的。

三、數據同步

上面其實說明了一個問題,就是數據同步,我們很可能會出現,數據庫中數據變了,但是緩存中的數據還沒有變,因此和數據庫中的數據不一致,所以我們需要一些策略來保證數據庫的數據和Redis的數據能夠保持一致,至於選用什麽策略,那要具體項目具體分析,如果對數據的實時性要求很高的項目,那麽就要在查詢的時候,檢測數據庫的數據和Redis的緩存數據是否一致,如果不一致就要刷新Redis的數據,但是這樣必然對性能會有很高的要求;如果項目對數據的實時性要求沒有那麽高,我們完全可以做一個定時任務,比如每隔10分鐘或者半小時去數據庫拉一次數據,再刷新到Redis緩存中,所以下面我們就來做一個定時任務,每隔10分鐘去拉一次數據,然後往Redis中刷新一次。

四、定時刷新緩存

我們用Spring中的InitializingBean和DisposableBean接口(這兩個接口的具體用法請自行百度,大概就是在SpringBean的生命周期中,影響bean的行為,我們這裏就是影響了bean,讓它去刷新緩存)來實現刷新緩存,在com.user.demo下新建包cache,在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數據並定時刷新
 * 每間隔一定時間後(如10分鐘)刷新一次緩存,刷新時另起一個線程,不影響執行主任務的線程
 */
@Component
public class UserCache implements InitializingBean, DisposableBean {

    private final Logger log = LoggerFactory.getLogger(UserCache.class);

    //獲取電腦的CPU核心數量,設置線程池大小為核心數的2倍(比如我的電腦為4核心,那麽這裏的值就為8)
    private static final int CORE_NUM = Runtime.getRuntime().availableProcessors() * 2;

    //初始化值為redis.properties中配置的cache.redis.cacheExpire的值,表示每隔多長時間後執行任務
    @Value("${cache.redis.cacheExpire}")
    private long cacheExpire;

    //執行定時任務的類
    private ScheduledThreadPoolExecutor executor = null;

    @Resource
    private RedisTemplate<String, String> redisCache;

    @Resource
    private UserDao userDao;


    @Override
    public void destroy() throws Exception {
        executor.shutdownNow();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        executor = new ScheduledThreadPoolExecutor(CORE_NUM);
        RefreshCache refreshCache = new RefreshCache();
        refreshCache.run();
        executor.scheduleWithFixedDelay(refreshCache, cacheExpire, cacheExpire, TimeUnit.SECONDS);
    }

    //內部類,開啟新線程執行緩存刷新
    private class RefreshCache implements Runnable {
        @Override
        public 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配置項,代碼如下

##Redis連接信息配置
#redis地址
cache.redis.host=127.0.0.1
#redis端口號
cache.redis.port=6379
#redis密碼
cache.redis.password=
#redis使用的數據庫(Redis內置18個數據庫,編號為0-17,默認使用0)
cache.redis.db=0
#redis鏈接超時時間
cache.redis.timeout=2000
#redis鏈接池中最大空閑數
cache.redis.maxIdle=5
#redis鏈接池中最大連接數
cache.redis.maxActive=20
#建立連接最長等待時間
cache.redis.maxWait=1000

#定時任務執行時間間隔,單位為毫秒,這裏相當於10分鐘
cache.redis.cacheExpire=600

同時修改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接口實現類
 */
@Service
public class UserServiceImpl implements IUserService {

    @Resource
    private UserDao userDao;

    @Resource
    private RedisTemplate<String, String> redisCache;

    @Override
    public List<User> findAll() {
        List<User> result = new ArrayList<User>();
        Set<String> sets = redisCache.keys("USER*");
        //如果緩存有數據則從緩存中取數據,如果沒有則從數據庫中取數據
        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);
    }

}

此時,項目已經可以定時(每隔十分鐘)刷新緩存,我們編譯啟動

技術分享

我們新提交一個用戶“三德子”,剛提交後,刷新是查不出來數據的,但是數據庫是有數據的

技術分享 技術分享

然後等待10分鐘之後,再來查看,可以看到緩存已經被自動刷新到了Redis中

技術分享

OK,到這裏,這篇文章就結束了,關於Spring集成Redis做數據緩存我們也講的差不多了,其實關於Redis的應用,遠遠不止這麽簡單,我們可以很容易的搭建Redis集群,做分布式數據管理,也可以實現分布式session共享(這個我後面會有一篇文章講到),甚至假如寫數據量很大,我們也可以先緩存進Redis中,再利用多線程來將數據寫入數據庫中,減輕數據庫的負擔(因為數據庫寫操作是很耗費資源的)等等~~那如果大家有什麽意見和建議,也歡迎留言交流;下一篇文章我會講講mysql的讀寫分離,歡迎繼續關註!!

代碼URL:http://git.oschina.net/tian5017/UserDemoRedis

Spring整合Redis做數據緩存(Windows環境)