session在分布式环境下的问题
Session在集群情况下,可能会发生不一致的问题,即Session不能在多机器共享
解决方案
1. 使用Nginx的ip_hash
保证同一个ip请求同一个实例
优点:
缺点:
- 服务器重启,Session会丢失,因为Session本质上保存在服务器内存中
- 单点负载,故障风险
2. 使用Redis统一保存Session
优点:
- 能适应各种负载均衡策略
- 服务器重启或者宕机不会造成Session丢失
- 扩展能力强
- 适合大集群数量使用
使用Spring Session
可以非常方便的实现Redis管理Session
引入jar包
1 2 3 4 5 6 7 8
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
|
配置redis
1 2 3 4 5
| spring: redis: database: 0 host: 127.0.0.1 port: 6379
|
添加注解
1 2 3 4 5
| @EnableRedisHttpSession @SpringBootApplication public class SpringApplication { }
|
源码分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper( request, response, this.servletContext); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper( wrappedRequest, response);
try { filterChain.doFilter(wrappedRequest, wrappedResponse); } finally { wrappedRequest.commitSession(); } } }
|
关注 SessionRepositoryRequestWrapper.getSession()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| S session = SessionRepositoryFilter.this.sessionRepository.createSession();
private void commitSession() { HttpSessionWrapper wrappedSession = getCurrentSession(); if (wrappedSession == null) { if (isInvalidateClientSession()) { SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response); } } else { S session = wrappedSession.getSession(); clearRequestedSessionCache(); SessionRepositoryFilter.this.sessionRepository.save(session); String sessionId = session.getId(); if (!isRequestedSessionIdValid() || !sessionId.equals(getRequestedSessionId())) { SessionRepositoryFilter.this.httpSessionIdResolver.setSessionId(this, this.response, sessionId); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| private void saveDelta() { if (this.delta.isEmpty()) { return; } String key = getSessionKey(getId()); RedisSessionRepository.this.sessionRedisOperations.opsForHash().putAll(key, new HashMap<>(this.delta)); RedisSessionRepository.this.sessionRedisOperations.expireAt(key, Date.from(Instant.ofEpochMilli(getLastAccessedTime().toEpochMilli()) .plusSeconds(getMaxInactiveInterval().getSeconds()))); this.delta.clear(); }
|