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环境)的主要内容,如果未能解决你的问题,请参考以下文章