现在集群中使用的Session共享机制有两种,分别是session复制和session粘性。
- Session复制
该种方式下,负载均衡器会根据各个node的状态,把每个request进行分发,使用这样的测试,必须在多个node之间复制用户的session,实时保持整个集群中用户的状态同步。其中jboss的实现原理是使用拦截器,根据用户的同步策略拦截request,做完同步处理后再交给server产生响应。
优点:session不会被绑定到具体的node,只要有一个node存活,用户状态就不会丢失,集群能够正常工作。
缺点:node之间通信频繁,响应速度有影响,高并发情况下性能下降比较厉害。
- Session粘性
该种方式下,当用户发出第一个request后,负载均衡器动态的把该用户分配至到某个节点,并记录该节点的jvm路由,以后该用户的所有的request都会绑定到这个jvm路由,用户只会和该server交互。
优点:响应速度快,多个节点之间无需通信
缺点:某个node死掉之后,它负责的所有用户都会丢失session。
改进:servlet容器在新建、更新或维护session时,向其它no de推送修改信息。这种方式在高并发情况下同样会影响效率。
以上这两种方式都需要负载均衡器和Servlet容器的支持,在部署时需要单独配置负载均衡器和Servelt容器。
- 基于分布式缓存的session共享机制
将会话Session统一存储在分布式缓存中,并使用Cookie保持客户端和服务端的联系,每一次会话开始就生成一个GUID作为SessionID,保存在客户端的Cookie中,在服务端通过SessionID向分布式缓存中获取session。
实现思路:通过一个Filter过滤所有的request请求,在Filter创建request和session的代理,通过代理使用分布式缓存对session进行操作。这样实现对现有应用中对request对象的操作透明。
- 使用spring实现:
引入spring-session和redis依赖:
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency>
1.创建redis连接工厂
public class RedisConfig {
private String host = null ;
private Integer port = null ;
/**
* redis连接工厂
* @return
*/
@Bean
public JedisConnectionFactory connectionFactory(Environment environment){
//获取redis主机和port
this.host = environment.getRequiredProperty("redis.host") ;
//默认端口号为6379
this.port = environment.getProperty("redis.port", Integer.class, 6379) ;
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory() ;
jedisConnectionFactory.setHostName(host);
jedisConnectionFactory.setPort(port);
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig() ;
jedisPoolConfig.setMinIdle(1);
jedisPoolConfig.setMaxTotal(10);
//最长等待10s
jedisPoolConfig.setMaxWaitMillis(10000);
jedisPoolConfig.setTestOnBorrow(true);
jedisConnectionFactory.setPoolConfig(jedisPoolConfig);
return jedisConnectionFactory ;
}
@Bean
public StringRedisTemplate redisTemplate(RedisConnectionFactory connectionFactory){
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(connectionFactory) ;
return stringRedisTemplate ;
}
}
2.引入RedisHttpSessionConfiguration.class。通过java-config引入或在xml中引入,因为要实现零XML配置,所以使用java-config引入。
/**
*基于redis的session共享配置
*/
@EnableRedisHttpSession(maxInactiveIntervalInSeconds=Constants.SESSION_TIMEOUT)
public class SessionRedisConfig {
}
3.创建HttpSessionApplicationInitializer,继承AbstractHttpSessionApplicationInitializer,HttpSessionApplicationInitializer不需要重载或实现任何方法,这个类只是一个使用Redis存储Session的一个标示类。在Servlet容器初始化时,会通过编码的方式添加一个Filter,通过WebApplicationContext查找名为springSessionRepositoryFilter的Filter类对request、response和session进行包装。
AbstractHttpSessionApplicationInitializer实现了WebApplicationInitializer接口。在Servlet3.0规范中,会自动扫描配置的ServletContainerInitializer的实现类,在实现类中可以配置需要处理的类。如下是SpringServletContainerInitializer,在启动时会自动寻找所有的WebApplicationInitializer实现类。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
......
}
关于Servlet3.0规范,参考附件Servlet3.1规范(最终版)。
说明:
- @EnableRedisHttpSession引入了RedisHttpSessionConfiguration.class,主要进行了如下配置:
//获取EnableRedisHttpSession中maxInactiveIntervalInSeconds,即session检测的最大时间间隔
public void setImportMetadata(AnnotationMetadata importMetadata){}
//通过spring容器中的connectionFactory创建RedisTemplate
public RedisTemplate<String,ExpiringSession> sessionRedisTemplate(RedisConnectionFactory connectionFactory) {}
//创建对session操作的工厂
public RedisOperationsSessionRepository sessionRepository(RedisTemplate<String, ExpiringSession> sessionRedisTemplate) {}
//创建filter,真正包装request、response和session对象的类。其中httpSessionStrategy默认使用的是CookieHttpSessionStrategy,即将session ID存储在cookie中,每次请求时由Cookie中获取session id
public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) {}
- Shiro由于是适用于B/S模式和非B/S模式的安全框架,为了兼容不同环境下Session,Shiro封装了自己的Session接口。在B/S模式下,经过Shiro过滤后,HttpServletRequest、HttpSession就会经过shiro的重新封装,但是封装的过程起始就是代理的过程,操作的具体类实际上还是发送请求的对象。所以,项目中如果集成了Spring-Session,在shiro中对Session的操作实际上还是对Spring封装Session的操作。 B/S模式下Shiro创建的DefaultWebSecurityManager在构造方法中实例化的SessionManager是ServletContainerSessionManager;org.apache.shiro.web.session.HttpServletSession是javax.servlet.http.HttpSession的代理。
public DefaultWebSecurityManager() {
super();
((DefaultSubjectDAO) this.subjectDAO).setSessionStorageEvaluator(new DefaultWebSessionStorageEvaluator());
this.sessionMode = HTTP_SESSION_MODE;
setSubjectFactory(new DefaultWebSubjectFactory());
setRememberMeManager(new CookieRememberMeManager());
setSessionManager(new ServletContainerSessionManager());
}
- shiro也提供了禁止其新创建Session的属性,即org.apache.shiro.subject.support.SESSION_CREATION_ENABLED,在shiro Filter执行之前通过其它Filter为request设置这个属性即可。参考如下:
@Service
public class DisableShiroSessionFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//禁止shiro创建session
request.setAttribute(DefaultSubjectContext.SESSION_CREATION_ENABLED, false);
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}