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

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

[html] view plaincopy print?
  1. <context:component-scan base-package="com.defonds.bdp.cache.redis" />

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

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

[java] view plaincopy 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. // R
  8. @Cacheable("searchCity")
  9. public City searchCity(String city_code){
  10. logger.debug("city_code=" + city_code);
  11. return this.cityMapper.searchCity(city_code);
  12. }

4. 缓存数据一致性保证

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

[java] view plaincopy 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. // D
  25. @CacheEvict(value = { "provinceCities", "searchCity" }, allEntries = true)
  26. public int deleteCity(String city_code) {
  27. this.cityMapper.deleteCity(city_code);
  28. return 1;
  29. }

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

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

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

[java] view plaincopy print?
  1. @Cacheable("users")
  2. public User findByUsername(String username)

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

[java] view plaincopy print?
  1. @Cacheable("users")
  2. public Integer getLoginCountByUsername(String username)

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

[java] view plaincopy 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。

参考资料

  • Caching Data with Spring
  • 35. Cache Abstraction Part VII. Integration
  • Caching Data in Spring Using Redis
  • Caching with Spring Data Redis
  • spring-redis-caching-example

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

转载于:https://www.cnblogs.com/softidea/p/6575167.html

Redis 缓存 + Spring 的集成示例相关推荐

  1. spring Cache /Redis 缓存 + Spring 的集成示例

    spring Cache https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/ spring+redis 缓存 ht ...

  2. Spring Hibernate集成示例教程

    Spring Hibernate集成示例教程(Spring 4 + Hibernate 3和Hibernate 4) Spring是最常用的Java EE Framework之一,而Hibernate ...

  3. JSF Spring Hibernate集成示例教程

    JSF Spring Hibernate集成示例教程 欢迎使用JSF Spring Hibernate Integration示例教程.在上一篇教程中,我们了解了如何将JSF和Spring框架集成在一 ...

  4. jsf集成spring_JSF Spring Hibernate集成示例教程

    jsf集成spring Welcome to JSF Spring Hibernate Integration example tutorial. In our last tutorial, we s ...

  5. spring boot 缓存_Spring Boot 集成 Redis 实现数据缓存

    Spring Boot 集成 Redis 实现数据缓存,只要添加一些注解方法,就可以动态的去操作缓存了,减少代码的操作. 在这个例子中我使用的是 Redis,其实缓存类型还有很多,例如 Ecache. ...

  6. Spring Boot集成Redis缓存之模拟高并发场景处理

    前言 同样我们以上一篇文章为例子,搭建好环境之后,我欧美可以模拟高并发场景下,我们的缓存效率怎么样,到底能不能解决我们实际项目中的缓存问题.也就是如何解决缓存穿透? Spring Boot集成Redi ...

  7. Spring Boot集成Redis缓存之RedisTemplate的方式

    前言 Spring Boot 集成Redis,将自动配置 RedisTemplate,在需要使用的类中注入RedisTemplate的bean即可使用 @Autowired private Redis ...

  8. Spring Boot 集成 Redis 实现缓存机制

    本文章牵涉到的技术点比较多:spring Data JPA.Redis.Spring MVC,Spirng Cache,所以在看这篇文章的时候,需要对以上这些技术点有一定的了解或者也可以先看看这篇文章 ...

  9. Spring Boot集成Redis实现缓存

    前言 在此章,我们将 SpringBoot 集成 Redis缓存,Redis是一个开源的,基于内存的数据结构存储,可以用作数据库.缓存和消息代理,在本章仅讲解缓存集成.一键获取源码地址 准备工作 当前 ...

  10. 搞懂分布式技术14:Spring Boot使用注解集成Redis缓存

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/a724888/article/details/80785403 为了提高性能,减少数据库的压力,使用 ...

最新文章

  1. 最新发布| Jira官宣中国区本地部署特殊政策,公布Data Center价格
  2. uniapp自定义导航栏
  3. C# 索引器使用总结
  4. 《噬血代码》:一款轻度的魂Like游戏
  5. date时区 es logstash_elastic date时区问题解决办法
  6. java中的DAO设计模式
  7. LeetCode 513. 找树左下角的值(递归)
  8. [转载] python字符串情感分析_python进行情感分析
  9. html/css静态网页制作
  10. uniapp实现签名板效果
  11. cmos逻辑门传输延迟时间_【转载】CMOS与TTL电路的区别
  12. 2005年度世界500强公司名单[转]
  13. 【转】十分钟了结MySQL information_schema
  14. 拉普拉斯修正学习笔记
  15. 2021医学影像分割论文:MSGSE-Net:用于皮层下脑结构分割的多尺度引导压缩和提取网络
  16. 概率论与数理统计---------大数定律
  17. 更新数据时redis缓存与数据库数据不一致的问题
  18. 我热爱计算机作文450字,我的国学机作文450字
  19. js submit onsubmit区别
  20. 7kbscan御剑版下载及使用

热门文章

  1. 面试今日头条Android开发,结束时我问了面试官是否有女朋友,结果你猜?
  2. sftp连不上服务器 vscode_vscode+sftp 开发模式环境的同步
  3. linq判断集合中相同元素个数_高中数学:集合与函数概念知识点汇总
  4. C语言程序实验01,广西科技大学理学院《C语言程序设计与算法语言》实验01: 熟悉开发环境.pdf...
  5. java导入hbase_如何用java导入hbase.dat文件
  6. 算法知识点——(3)监督学习——决策树
  7. jquery上传 php,jQuery AJAX文件上传PHP
  8. go代码--数据结构
  9. python 怎么表示阶乘_python表示阶乘
  10. 如何安装python3.6_python3.6环境下如何安装freetype库和基本使用方法