1、分布式会话管理是什么?
在Web项目开发中,会话管理是一个很重要的部分,用于存储与用户相关的数据。通常是由符合session规范的容器来负责存储管理,也就是一旦容器关闭,重启会导致会话失效。因此打造一个高可用性的系统,必须将session管理从容器中独立出来。
2、分布式会话管理的解决方案选用
实现方案有很多种,下面简单介绍下:
第一种是使用容器扩展来实现,大家比较容易接受的是通过容器插件来实现,比如基于Tomcat的tomcat-redis-session-manager,基于Jetty的jetty-session-redis等等。好处是对项目来说是透明的,无需改动代码。不过前者目前还不支持Tomcat 8,或者说不太完善。个人觉得由于过于依赖容器,一旦容器升级或者更换意味着又得从新来过。并且代码不在项目中,对开发者来说维护也是个问题。
第二种是自己写一套会话管理的工具类,包括Session管理和Cookie管理,在需要使用会话的时候都从自己的工具类中获取,而工具类后端存储可以放到Redis中。很显然这个方案灵活性最大,但开发需要一些额外的时间。并且系统中存在两套Session方案,很容易弄错而导致取不到数据。
第三种是使用框架的会话管理工具,也就是本文要说的spring-session,可以理解是替换了Servlet那一套会话管理,既不依赖容器,又不需要改动代码,并且是用了spring-data-redis那一套连接池,可以说是最完美的解决方案。当然,前提是项目要使用Spring Framework才行。
3、为什么使用Spring Session
Spring Session为企业级Java应用的session管理带来了革新,使得以下的功能更加容易实现:
- 将session所保存的状态卸载到特定的外部session存储中,如Redis或Apache Geode中,它们能够以独立于应用服务器的方式提供高质量的集群。
- 当用户使用WebSocket发送请求的时候,能够保持HttpSession处于活跃状态。
- 在非Web请求的处理代码中,能够访问session数据,比如在JMS消息的处理代码中。
- 支持每个浏览器上使用多个session,从而能够很容易地构建更加丰富的终端用户体验。
- 控制session id如何在客户端和服务器之间进行交换,这样的话就能很容易地编写Restful API,因为它可以从HTTP 头信息中获取session id,而不必再依赖于cookie。
参考:http://m.blog.csdn.net/article/details?id=51483471
4、Spring Session分布式会话解决方案
web.xml配置:
1<filter> 2 <filter-name>springSessionRepositoryFilter</filter-name> 3 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 4 </filter> 5 <filter-mapping> 6 <filter-name>springSessionRepositoryFilter</filter-name> 7 <url-pattern>/*</url-pattern> 8 </filter-mapping>
编写配置,注解@EnableRedisHttpSession通过Import,引入了RedisHttpSessionConfiguration配置类。该配置类通过@Bean注解,向Spring容器中注册了一个SessionRepositoryFilter(SessionRepositoryFilter的依赖关系:SessionRepositoryFilter --> SessionRepository --> RedisTemplate --> RedisConnectionFactory)。
1 import org.springframework.context.annotation.Bean; 2 import org.springframework.data.redis.connection.RedisConnectionFactory; 3 import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 4 import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; 5 6 @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) 7 public class RedisHttpSessionConfig { 8 9 @Bean 10 public RedisConnectionFactory connectionFactory() { 11 JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); 12 connectionFactory.setPort(6379); 13 connectionFactory.setHostName("127.0.0.1"); 14 return connectionFactory; 15 } 16 }
更复杂的Java Confg,需要加入spring 容器。
1package io.flysium.framework.session; 2import org.springframework.context.ApplicationEventPublisher; 3import org.springframework.context.annotation.Bean; 4import org.springframework.context.annotation.Configuration; 5import org.springframework.data.redis.connection.RedisConnectionFactory; 6import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 7import org.springframework.data.redis.core.RedisOperations; 8import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer; 9import org.springframework.data.redis.serializer.RedisSerializer; 10import org.springframework.scheduling.annotation.EnableScheduling; 11import org.springframework.session.data.redis.RedisFlushMode; 12import org.springframework.session.data.redis.RedisOperationsSessionRepository; 13import org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration; 14import org.springframework.session.web.http.CookieSerializer; 15import org.springframework.session.web.http.DefaultCookieSerializer; 16/** 17 * Spring Session分布式会话解决方案 18 * 19 * @author SvenAugustus(蔡政滦) e-mail: SvenAugustus@outlook.com 20 * @version 1.0 21 */ 22@Configuration 23@EnableScheduling 24public class RedisHttpSessionConfig extends RedisHttpSessionConfiguration { 25 /** 26 * Spring Data Redis 的连接工厂配置,必选 27 */ 28 @Bean(name = "connectionFactory") 29 public RedisConnectionFactory connectionFactory() { 30 JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); 31 connectionFactory.setPort(6379); 32 connectionFactory.setHostName("127.0.0.1"); 33 return connectionFactory; 34 } 35 /** 36 * Spring Data Redis 的会话存储仓库配置,可选 37 */ 38 @Bean(name = "sessionRepository") 39 public RedisOperationsSessionRepository sessionRepository( 40 RedisOperations<Object, Object> sessionRedisTemplate, 41 ApplicationEventPublisher applicationEventPublisher) { 42 this.setMaxInactiveIntervalInSeconds(Integer.valueOf(900)); // 单位:秒 43 this.setRedisNamespace(getApplicationName()); 44 this.setRedisFlushMode(RedisFlushMode.ON_SAVE); 45 return super.sessionRepository(sessionRedisTemplate, applicationEventPublisher); 46 } 47 /** 48 * Spring Data Redis 的默认序列化工具,可选 49 */ 50 @Bean(name = "springSessionDefaultRedisSerializer") 51 public RedisSerializer springSessionDefaultRedisSerializer() { 52 RedisSerializer defaultSerializer = new JdkSerializationRedisSerializer(); 53 //RedisSerializer defaultSerializer = new FastJsonStringRedisSeriaziler( 54 // Charset.forName("utf-8")); 55 return defaultSerializer; 56 } 57 /** 58 * Session会话策略配置,可选 59 * 60 * 1、Spring Session 默认支持Cookie存储当前session的id, 61 * 即CookieHttpSessionStrategy。 62 * 2、Spring Session 支持RESTFul APIS,响应头回返回x-auth-token,来标识当前session的token, 63 * 即HeaderHttpSessionStrategy。 64 */ 65 /*@Bean(name = "httpSessionStrategy") 66 public HttpSessionStrategy httpSessionStrategy() { 67 HeaderHttpSessionStrategy headerHttpSessionStrategy = new HeaderHttpSessionStrategy(); 68 headerHttpSessionStrategy.setHeaderName("Auth-Token"); 69 return headerHttpSessionStrategy; 70 }*/ 71 /** 72 * Session会话策略为 CookieHttpSessionStrategy 情况下配置的 Cookie序列化工具,可选 73 */ 74 @Bean(name = "cookieSerializer") 75 public CookieSerializer cookieSerializer() { 76 DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); 77 // Cookie名称 78 cookieSerializer.setCookieName( 79 new StringBuilder(getApplicationName()).append("SESSION").toString()); 80 // HttpOnly 81 cookieSerializer.setUseHttpOnlyCookie(true); 82 // HTTPS定义 83 //cookieSerializer.setUseSecureCookie(true); 84 // 解决子域问题:把cookiePath的返回值设置为统一的根路径就能让session id从根域获取, 85 //这样同根下的所有web应用就可以轻松实现单点登录共享session 86 cookieSerializer.setCookiePath("/"); 87 return cookieSerializer; 88 } 89 private String getApplicationName() { 90 return "app"; 91 } 92}
5、如何查看session数据?
(1)Http Session数据(spring:session:命名空间:sessions:xxxx)在Redis中是以Hash结构存储的。


(2)Http Session过期数据(spring:session:命名空间:expirations:xxxx)以Set结构保存的。
记录了所有session数据应该被删除的时间(即最新的一个session数据过期的时间)。

6、Spring Session原理讲解
所有的request都会经过SessionRepositoryFilter,而 SessionRepositoryFilter是一个优先级最高的javax.servlet.Filter,它使用了一个SessionRepositoryRequestWrapper类接管了Http Session的创建和管理工作。
1 public class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { 2 3 public SessionRepositoryRequestWrapper(HttpServletRequest original) { 4 super(original); 5 } 6 7 public HttpSession getSession() { 8 return getSession(true); 9 } 10 11 public HttpSession getSession(boolean createNew) { 12 // create an HttpSession implementation from Spring Session 13 } 14 15 // ... other methods delegate to the original HttpServletRequest ... 16 }