Redis 缓存 + Spring 的集成示例

SpringSession和Redis实现Session跨域

http://www.ithao123.cn/content-11111681.html

 

tomcat中创建session很耗服务器内存

原生session与session in redis对比
下面是从stackoverflow上找到的一些观点:

Using something like Redis for storing sessions is a great way to get more performance out of load balanced servers. Here is a case in point:

On Amazon Web Services, the load balances have what’s called ‘sticky sessions’. What this means is that when a user first connects to your web app, e.g. when logging in to it, the load balance will choose one of your app. servers and this user will continue to be served from this server until they exit your application. This is because the sessions used by PHP, for example, will be stored on the app. server that they first start using. Now, if you use Redis on a separate server, then configure your PHP on each of your app. servers to store it’s sessions in Redis, you can turn this ‘sticky sessions’ off. This would mean that any of your servers can access the sessions and, therefore, the user be served from a different server with every request to your app. This ultimately makes for more efficient use of your load balancing set-up.

上面这段提到了一个优点:在集群环境中,使用redis可以更灵活地实现负载均衡。

You want the session save handler to be fast. This is due to the fact that a PHP session will block all other concurrent requests from the same user until the first request is finished.

There are a variety of handlers you could use for PHP sessions across multiple servers: File w/ NFS, MySQL Database, Memcache, and Redis.

The database method (using InnoDB) was the slowest in my experience followed by File w/ NFS. Locking and write contention are the main factors. Memcache and Redis provide similar performance and are by far the better alternatives since all operations are in RAM. Redis is my choice because you can enable disk persistence, and Memcache is only memory based.

这里提到了几种用来存储会话数据的方式,并把原生的session归类的使用文件的存储。显然是Redis在效率上要更快些,而与memcached相比,因为有持久化,也更安全一些。

由大家的使用经验可以看出,说“原生的session要比使用redis来存储session更好”的说话是没有道理的。而且session还存在以下问题:

由于session回收的问题,使用session还会带来一些像登录会话不能准时过期等问题。
在使用swoole做websocket服务器的时候,在尝试使用session_id来获取原生session的会话信息的时候,由于原生session总是需要配合session_start()使用,在尝试在处理请求session_start()的时候会报“header already sent”的问题;尝试使用sessionHandler类的方法时,也会报告一些奇怪的问题。
因此没必要守着原生session这老古董,应该积极拥抱redis存储会话的方式。

 

整合 spring 4(包括mvc、context、orm) + mybatis 3 示例》一文简要介绍了最新版本的 Spring MVC、IOC、MyBatis ORM 三者的整合以及声明式事务处理。现在我们需要把缓存也整合进来,缓存我们选用的是 Redis,本文将在该文示例基础上介绍 Redis 缓存 + Spring 的集成。关于 Redis 服务器的搭建请参考博客《Redhat5.8 环境下编译安装 Redis 并将其注册为系统服务》。

1. 依赖包安装

pom.xml 加入:

[html] view plain copy

 
 print?

  1. <!-- redis cache related.....start -->  
  2. <dependency>  
  3.     <groupId>org.springframework.data</groupId>  
  4.     <artifactId>spring-data-redis</artifactId>  
  5.     <version>1.6.0.RELEASE</version>  
  6. </dependency>  
  7. <dependency>  
  8.     <groupId>redis.clients</groupId>  
  9.     <artifactId>jedis</artifactId>  
  10.     <version>2.7.3</version>  
  11. </dependency>  
  12. <!-- redis cache related.....end -->  

 

2. Spring 项目集成进缓存支持

要启用缓存支持,我们需要创建一个新的 CacheManager bean。CacheManager 接口有很多实现,本文演示的是和 Redis 的集成,自然就是用 RedisCacheManager 了。Redis 不是应用的共享内存,它只是一个内存服务器,就像 MySql 似的,我们需要将应用连接到它并使用某种“语言”进行交互,因此我们还需要一个连接工厂以及一个 Spring 和 Redis 对话要用的 RedisTemplate,这些都是 Redis 缓存所必需的配置,把它们都放在自定义的 CachingConfigurerSupport 中:

[java] view plain copy

 
 print?

  1. /** 
  2.  * File Name:RedisCacheConfig.java 
  3.  * 
  4.  * Copyright Defonds Corporation 2015  
  5.  * All Rights Reserved 
  6.  * 
  7.  */  
  8. package com.defonds.bdp.cache.redis;  
  9.   
  10. import org.springframework.cache.CacheManager;  
  11. import org.springframework.cache.annotation.CachingConfigurerSupport;  
  12. import org.springframework.cache.annotation.EnableCaching;  
  13. import org.springframework.context.annotation.Bean;  
  14. import org.springframework.context.annotation.Configuration;  
  15. import org.springframework.data.redis.cache.RedisCacheManager;  
  16. import org.springframework.data.redis.connection.RedisConnectionFactory;  
  17. import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;  
  18. import org.springframework.data.redis.core.RedisTemplate;  
  19.   
  20. /** 
  21.  *  
  22.  * Project Name:bdp  
  23.  * Type Name:RedisCacheConfig  
  24.  * Type Description: 
  25.  *  Author:Defonds 
  26.  * Create Date:2015-09-21 
  27.  *  
  28.  * @version 
  29.  *  
  30.  */  
  31. @Configuration  
  32. @EnableCaching  
  33. public class RedisCacheConfig extends CachingConfigurerSupport {  
  34.   
  35.     @Bean  
  36.     public JedisConnectionFactory redisConnectionFactory() {  
  37.         JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();  
  38.   
  39.         // Defaults  
  40.         redisConnectionFactory.setHostName("192.168.1.166");  
  41.         redisConnectionFactory.setPort(6379);  
  42.         return redisConnectionFactory;  
  43.     }  
  44.   
  45.     @Bean  
  46.     public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {  
  47.         RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();  
  48.         redisTemplate.setConnectionFactory(cf);  
  49.         return redisTemplate;  
  50.     }  
  51.   
  52.     @Bean  
  53.     public CacheManager cacheManager(RedisTemplate redisTemplate) {  
  54.         RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);  
  55.   
  56.         // Number of seconds before expiration. Defaults to unlimited (0)  
  57.         cacheManager.setDefaultExpiration(3000); // Sets the default expire time (in seconds)  
  58.         return cacheManager;  
  59.     }  
  60.       
  61. }  

当然也别忘了把这些 bean 注入 Spring,不然配置无效。在 applicationContext.xml 中加入以下:

[html] view plain copy

 

 print?

  1. <context:component-scan base-package="com.defonds.bdp.cache.redis" />  

 

3. 缓存某些方法的执行结果

设置好缓存配置之后我们就可以使用 @Cacheable 注解来缓存方法执行的结果了,比如根据省份名检索城市的 provinceCities 方法和根据 city_code 检索城市的 searchCity 方法:

[java] view plain copy

 

 print?

  1. // R  
  2. @Cacheable("provinceCities")  
  3. public List<City> provinceCities(String province) {  
  4.     logger.debug("province=" + province);  
  5.     return this.cityMapper.provinceCities(province);  
  6. }  
  7.   
  8. // R  
  9. @Cacheable("searchCity")  
  10. public City searchCity(String city_code){  
  11.     logger.debug("city_code=" + city_code);  
  12.     return this.cityMapper.searchCity(city_code);     
  13. }  

 

4. 缓存数据一致性保证

CRUD (Create 创建,Retrieve 读取,Update 更新,Delete 删除) 操作中,除了 R 具备幂等性,其他三个发生的时候都可能会造成缓存结果和数据库不一致。为了保证缓存数据的一致性,在进行 CUD 操作的时候我们需要对可能影响到的缓存进行更新或者清除。

[java] view plain copy

 

 print?

  1. // C  
  2. @CacheEvict(value = { "provinceCities"}, allEntries = true)  
  3. public void insertCity(String city_code, String city_jb,   
  4.         String province_code, String city_name,  
  5.         String city, String province) {  
  6.     City cityBean = new City();  
  7.     cityBean.setCityCode(city_code);  
  8.     cityBean.setCityJb(city_jb);  
  9.     cityBean.setProvinceCode(province_code);  
  10.     cityBean.setCityName(city_name);  
  11.     cityBean.setCity(city);  
  12.     cityBean.setProvince(province);  
  13.     this.cityMapper.insertCity(cityBean);  
  14. }  
  15. // U  
  16. @CacheEvict(value = { "provinceCities", "searchCity" }, allEntries = true)  
  17. public int renameCity(String city_code, String city_name) {  
  18.     City city = new City();  
  19.     city.setCityCode(city_code);  
  20.     city.setCityName(city_name);  
  21.     this.cityMapper.renameCity(city);  
  22.     return 1;  
  23. }  
  24.   
  25. // D  
  26. @CacheEvict(value = { "provinceCities", "searchCity" }, allEntries = true)  
  27. public int deleteCity(String city_code) {  
  28.     this.cityMapper.deleteCity(city_code);  
  29.     return 1;  
  30. }  

业务考虑,本示例用的都是 @CacheEvict 清除缓存。如果你的 CUD 能够返回 City 实例,也可以使用 @CachePut 更新缓存策略。笔者推荐能用 @CachePut 的地方就不要用 @CacheEvict,因为后者将所有相关方法的缓存都清理掉,比如上面三个方法中的任意一个被调用了的话,provinceCities 方法的所有缓存将被清除。

5. 自定义缓存数据 key 生成策略

对于使用 @Cacheable 注解的方法,每个缓存的 key 生成策略默认使用的是参数名+参数值,比如以下方法:

[java] view plain copy

 

 print?

  1. @Cacheable("users")  
  2. public User findByUsername(String username)  

这个方法的缓存将保存于 key 为 users~keys 的缓存下,对于 username 取值为 "赵德芳" 的缓存,key 为 "username-赵德芳"。一般情况下没啥问题,二般情况如方法 key 取值相等然后参数名也一样的时候就出问题了,如:

[java] view plain copy

 

 print?

  1. @Cacheable("users")  
  2. public Integer getLoginCountByUsername(String username)  

这个方法的缓存也将保存于 key 为 users~keys 的缓存下。对于 username 取值为 "赵德芳" 的缓存,key 也为 "username-赵德芳",将另外一个方法的缓存覆盖掉。
解决办法是使用自定义缓存策略,对于同一业务(同一业务逻辑处理的方法,哪怕是集群/分布式系统),生成的 key 始终一致,对于不同业务则不一致:

[java] view plain copy

 

 print?

  1. @Bean  
  2. public KeyGenerator customKeyGenerator() {  
  3.     return new KeyGenerator() {  
  4.         @Override  
  5.         public Object generate(Object o, Method method, Object... objects) {  
  6.             StringBuilder sb = new StringBuilder();  
  7.             sb.append(o.getClass().getName());  
  8.             sb.append(method.getName());  
  9.             for (Object obj : objects) {  
  10.                 sb.append(obj.toString());  
  11.             }  
  12.             return sb.toString();  
  13.         }  
  14.     };  
  15. }  

于是上述两个方法,对于 username 取值为 "赵德芳" 的缓存,虽然都还是存放在 key 为 users~keys 的缓存下,但由于 key 分别为 "类名-findByUsername-username-赵德芳" 和 "类名-getLoginCountByUsername-username-赵德芳",所以也不会有问题。
这对于集群系统、分布式系统之间共享缓存很重要,真正实现了分布式缓存。
笔者建议:缓存方法的 @Cacheable 最好使用方法名,避免不同的方法的 @Cacheable 值一致,然后再配以以上缓存策略。

6. 缓存的验证

6.1 缓存的验证

为了确定每个缓存方法到底有没有走缓存,我们打开了 MyBatis 的 SQL 日志输出,并且为了演示清楚,我们还清空了测试用 Redis 数据库。
先来验证 provinceCities 方法缓存,Eclipse 启动 tomcat 加载项目完毕,使用 JMeter 调用 /bdp/city/province/cities.json 接口:

Eclipse 控制台输出如下:

说明这一次请求没有命中缓存,走的是 db 查询。JMeter 再次请求,Eclipse 控制台输出:

标红部分以下是这一次请求的 log,没有访问 db 的 log,缓存命中。查看本次请求的 Redis 存储情况:

同样可以验证 city_code 为 1492 的 searchCity 方法的缓存是否有效:

图中标红部分是 searchCity 的缓存存储情况。

6.2 缓存一致性的验证

先来验证 insertCity 方法的缓存配置,JMeter 调用 /bdp/city/create.json 接口:

之后看 Redis 存储:

可以看出 provinceCities 方法的缓存已被清理掉,insertCity 方法的缓存奏效。
然后验证 renameCity 方法的缓存配置,JMeter 调用 /bdp/city/rename.json 接口:

之后再看 Redis 存储:

searchCity 方法的缓存也已被清理,renameCity 方法的缓存也奏效。

7. 注意事项

  1. 要缓存的 Java 对象必须实现 Serializable 接口,因为 Spring 会将对象先序列化再存入 Redis,比如本文中的 com.defonds.bdp.city.bean.City 类,如果不实现 Serializable 的话将会遇到类似这种错误:nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.defonds.bdp.city.bean.City]]。
  2. 缓存的生命周期我们可以配置,然后托管 Spring CacheManager,不要试图通过 redis-cli 命令行去管理缓存。比如 provinceCities 方法的缓存,某个省份的查询结果会被以 key-value 的形式存放在 Redis,key 就是我们刚才自定义生成的 key,value 是序列化后的对象,这个 key 会被放在 key 名为 provinceCities~keys key-value 存储中,参考下图"provinceCities 方法在 Redis 中的缓存情况"。可以通过 redis-cli 使用 del 命令将 provinceCities~keys 删除,但每个省份的缓存却不会被清除。
  3. CacheManager 必须设置缓存过期时间,否则缓存对象将永不过期,这样做的原因如上,避免一些野数据“永久保存”。此外,设置缓存过期时间也有助于资源利用最大化,因为缓存里保留的永远是热点数据。
  4. 缓存适用于读多写少的场合,查询时缓存命中率很低、写操作很频繁等场景不适宜用缓存。

后记

本文完整 Eclipse 下的开发项目示例已上传 CSDN 资源,有兴趣的朋友可以去下载下来参考:http://download.csdn.net/detail/defonds/9137505

参考资料

http://blog.csdn.net/defonds/article/details/48716161

 

时间: 2024-08-29 10:23:22

Redis 缓存 + Spring 的集成示例的相关文章

Redis 缓存 + Spring 的集成示例(转)

<整合 spring 4(包括mvc.context.orm) + mybatis 3 示例>一文简要介绍了最新版本的 Spring MVC.IOC.MyBatis ORM 三者的整合以及声明式事务处理.现在我们需要把缓存也整合进来,缓存我们选用的是 Redis,本文将在该文示例基础上介绍 Redis 缓存 + Spring 的集成.关于 Redis 服务器的搭建请参考博客<Redhat5.8 环境下编译安装 Redis 并将其注册为系统服务>. 1. 依赖包安装 pom.xml

CI框架中redis缓存相关操作文件示例代码_php实例

本文实例讲述了CI框架中redis缓存相关操作文件.分享给大家供大家参考,具体如下: redis缓存类文件位置: 'ci\system\libraries\Cache\drivers\Cache_redis.php' <?php /** * CodeIgniter * * An open source application development framework for PHP 5.2.4 or newer * * NOTICE OF LICENSE * * Licensed under

NoSQL之Redis(五)--- Redis测试以及其与spring的集成

概述         本文是紧接着上文安装好单机版的redis和rediscluster的之后需要去验证是否安装成功,以及如何和Spring集成到一起.           Jedis         Jedis是是Redis官方推荐的Java客户端开发包,用于处理redis服务上的缓存数据.笔者用的是Jedis-2.7.2.jar           Redis安装测试         接着这上文,我们先简单的写一个测试类,测试一下单机版的redis是否已经可用 [java] view pla

spring boot 配置redis缓存

问题描述 spring boot 配置redis缓存 环境:spring boot+java8,spring版本为4以上 用spring boot 配置redis缓存时报错如下: java.lang.IllegalStateException: No CacheResolver specified, and no unique bean of type CacheManager found. Mark one as primary (or give it the name 'cacheManag

Redis整合Spring结合使用缓存实例_Redis

一.Redis介绍什么是Redis?      redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hash(哈希类型).这些数据类型都支持push/pop.add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的.在此基础上,redis支持各种不同方式的排序.与memcached一样,为了保证效率,数据都是缓存

spring boot redis缓存JedisPool使用

spring boot redis缓存JedisPool使用 添加依赖pom.xml中添加如下依赖 <!-- Spring Boot Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency> redis配置文件 # RE

java 整合redis缓存 SSM 后台框架 rest接口 shiro druid maven bootstrap html5

获取[下载地址]   QQ: 313596790A 调用摄像头拍照,自定义裁剪编辑头像 [新录针对本系统的视频教程,手把手教开发一个模块,快速掌握本系统]B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,开发利器)+快速构建表单;  技术:313596790 freemaker模版技术 ,0个代码不用写,生成完整的一个模块,带页面.建表sql脚本,处理类,service等完整模块C 集成阿里巴巴数据库连接池druid;  数据库连接池  阿里巴巴的 druid.Druid在监控.可扩

Spring Boot 集成Shiro和CAS

  Spring Boot 集成Shiro和CAS 标签: springshirocas 2016-01-17 23:03 35765人阅读 评论(22) 收藏 举报  分类: Spring(42)  版权声明:本文为博主原创文章,未经博主允许不得转载. 请大家在看本文之前,先了解如下知识点: 1.Shiro 是什么?怎么用? 2.Cas 是什么?怎么用? 3.最好有Spring基础 可以先看看这两篇文章,按照这2篇文章的内容做一遍: Spring Boot Shiro 权限管理 CAS单点登录

php操作redis缓存方法分享

  除了memcache这个比较常用的php的操作类库,我们可能还非常熟悉一个内存缓存的东西,那就是redis,我们给大家分享的这个php技术文章,就是关于如何使用php进行操作redis这个内存缓存工具类库的哦. php redis缓存操作 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45