Spring整合Redis做数据缓存(Windows环境)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring整合Redis做数据缓存(Windows环境)相关的知识,希望对你有一定的参考价值。

当我们一个项目的数据量很大的时候,就需要做一些缓存机制来减轻数据库的压力,提升应用程序的性能,对于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环境)的主要内容,如果未能解决你的问题,请参考以下文章

spring整合redis后怎么更改db

spring 整合redis 简单使用

sping整合redis,以及做mybatis的第三方缓存

springboot整合redis做简单缓存

redis集群配置,spring整合jedis,缓存同步

微服务 Spring Boot 整合Redis 实战开发解决高并发数据缓存