HttpSession是通过Servlet容器创建和管理的,像Tomcat/Jetty都是保存在内存中的。而如果我们把web服务器搭建成分布式的集群,然后利用LVS或Nginx做负载均衡,那么来自同一用户的Http请求将有可能被分发到两个不同的web站点中去。那么问题就来了,如何保证不同的web站点能够共享同一份session数据呢?
最简单的想法就是把session数据保存到内存以外的一个统一的地方,例如Memcached/Redis等数据库中。
那么问题又来了,如何替换掉Servlet容器创建和管理HttpSession的实现呢?
设计一个Filter,利用HttpServletRequestWrapper,实现自己的 getSession()方法,接管创建和管理Session数据的工作。spring-session就是通过这样的思路实现的。
使用Spring Session和Redis的组合来代替原有的HttpSession实现Session在不同项目之间的共享
在springboot中集成Spring Session
引入spring session相关依赖
<!-- spring session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
添加 SpringSession 配置类
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400*30)
public class HttpSessionConfig {
//不需要写任何代码
}
配置类中最关键的就是 @EnableRedisHttpSession
@EnableRedisHttpSession
注解创建了一个名为 springSessionRepositoryFilter
的bean
,负责替换 httpSession
,同时由 redis
提供缓存支持
为了做到全部替换,我们要确保Servlet容器(Tomcat)对于某个请求都使用这个Filter,这个由SpringBoot负责
(具体是这样的:@EnableRedisHttpSession
注解通过Import
,引入了RedisHttpSessionConfiguration
配置类。该配置类通过@Bean
注解,向Spring容器中注册了一个SessionRepositoryFilter
(SessionRepositoryFilter的依赖关系:SessionRepositoryFilter --> SessionRepository --> RedisTemplate --> RedisConnectionFactory,有兴趣可以查看源码)maxInactiveIntervalInSeconds
:设置Session
失效时间,使用Redis Session
之后,原springboot
的server.session.timeout
属性不再生效
添加验证的接口
在yml
或者properties
文件中可以通过server.port设置端口
@Value("${server.port}")
String port;
@GetMapping("/session")
public Object getSession(HttpServletRequest request){
Map<String, Object> map = new HashMap<String, Object>();
map.put("SessionId", request.getSession().getId());
map.put("ServerPort", "服务端口号为 "+port);
return map;
}
访问 http://localhost:8080/session
我们看下redis缓存的数据
可以发现
sessionId
已经缓存在redis数据库中
下面我们换个端口再访问一次看看
这次我把端口换成了8888 访问: http://localhost:8888/session
刷新了redis数据库,缓存的数据也没变
结果中的SessionId是一致的,却是由两个不同项目工程来提供服务。这样子,SpringSession 利用拦截器 Filter 帮我们在每个请求前进行了同步设置,达到了分布式系统中 session 共享。