开发者社区> developerguy> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Redis 缓存 + Spring 的集成示例

简介: SpringSession和Redis实现Session跨域 http://www.ithao123.cn/content-11111681.html   tomcat中创建session很耗服务器内存 原生session与session in redis对比下面是从stackoverflo...
+关注继续查看

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 接口:
使用 JMeter 调用 /bdp/city/province/cities.json 接口.png
Eclipse 控制台输出如下:
Eclipse 控制台输出如下.png
说明这一次请求没有命中缓存,走的是 db 查询。JMeter 再次请求,Eclipse 控制台输出:
Eclipse 控制台输出
标红部分以下是这一次请求的 log,没有访问 db 的 log,缓存命中。查看本次请求的 Redis 存储情况:
查看本次请求的 Redis 存储情况.png
同样可以验证 city_code 为 1492 的 searchCity 方法的缓存是否有效:
同样可以验证 city_code 为 1492 的 searchCity 方法的缓存是否有效.png
图中标红部分是 searchCity 的缓存存储情况。

6.2 缓存一致性的验证

先来验证 insertCity 方法的缓存配置,JMeter 调用 /bdp/city/create.json 接口:
JMeter 调用 /bdp/city/create.json 接口.png
之后看 Redis 存储:
之后看 Redis 存储
可以看出 provinceCities 方法的缓存已被清理掉,insertCity 方法的缓存奏效。
然后验证 renameCity 方法的缓存配置,JMeter 调用 /bdp/city/rename.json 接口:
JMeter 调用 /bdp/city/rename.json 接口.png
之后再看 Redis 存储:
之后再看 Redis 存储.png
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. 缓存适用于读多写少的场合,查询时缓存命中率很低、写操作很频繁等场景不适宜用缓存。

provinceCities方法在Redis中的存储.png

后记

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

参考资料

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

 

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Springboot集成redis (使用注解)
Springboot集成redis (使用注解),在一定程度上能够方便开发
0 0
Springboot集成redis和解决redis key乱码问题
今天在使用springboot整合redis时出现序列化乱码的问题,所以记录一下
0 0
SpringBoot集成Redis
RedisTemplate这是SpringBoot默认提供的Redis模板,在真实项目中肯定不会直接使用该模板,我们应该去自己定义该模板的一些属性。这篇文章主要总结SpringBoot与Redis的简单集成与可能碰到的问题。
0 0
SpringBoot集成Redis报non null key required(已解决)
SpringBoot开发过程中遇到的问题并记录
0 0
利用Java来访问Redis并对Redis进行相关操作以及spring+redis集成配置与注解式注解
redis缓存的一些注意事项 只应将热数据放到缓存中 所有缓存信息都应设置过期时间 缓存过期时间应当分散以避免集中过期 缓存key应具备可读性 应避免不同业务出现同名缓存key 可对key进行适当的缩写以节省内存空间 选择合适的数据结构 确保写入缓存中的数据是完整且正确的 避免使用耗时较长的操作命令,如:keys * Redis默认配置中操作耗时超过10ms即视为慢查询 一个key对应的数据不应过大 对于string类型,一个key对应的value大小应控制在10K以内,1K左右更优hash类型,不应超过5000行
0 0
【Sa-Token】6、Sa-Token集成Redis
Sa-Token 支持 Redis、Memcached 等专业的缓存中间件中, 做到重启数据不丢失,而且保证分布式环境下多节点的会话一致性
0 0
SpringBoot集成Redis业务功能 02、定时任务+Redis删除特定前缀key的优雅实现
SpringBoot集成Redis业务功能 02、定时任务+Redis删除特定前缀key的优雅实现
0 0
SpringBoot集成Redis业务功能 01、限制玩家积分的每日重置次数
SpringBoot集成Redis业务功能 01、限制玩家积分的每日重置次数
0 0
【Redis】Spring Boot集成Redis
本期主要介绍Spring Boot集成Redis
0 0
SpringBoot——SpringBoot集成SSM、Dubbo、Redis、JSP的汇总案例
SpringBoot——SpringBoot集成SSM、Dubbo、Redis、JSP的汇总案例
0 0
+关注
developerguy
A code cleanliness code farmers, A programmer concentrate on elegant design, A want to do with the product architect
文章
问答
文章排行榜
最热
最新
相关电子书
更多
阿里云Redis容灾体系介绍--夏周
立即下载
阿里云Redis服务助力游戏行业发展--王义成
立即下载
国内开发者与Redis开源社区的发展
立即下载