SpringBoot 分布式 Session 共享解决方案
分布式Session一致性?
说白了就是服务器集群Session共享的问题,集群情况下,session保存在各自的服务器的tomcat中,当分发地址至不同服务时,导致sesson取不到,就会产生session共享问题。
Session的作用?
Session 是客户端与服务器通讯会话跟踪技术,服务器与客户端保持整个通讯的会话基本信息。
客户端在第一次访问服务端的时候,服务端会响应一个sessionId并且将它存入到本地cookie中,在之后的访问会将cookie中的sessionId放入到请求头中去访问服务器,如果通过这个sessionid没有找到对应的数据那么服务器会创建一个新的sessionid并且响应给客户端。
分布式 Session 存在的问题?
假设第一次访问服务A生成一个sessionid并且存入cookie中,第二次却访问服务B客户端会在cookie中读取sessionid加入到请求头中,如果在服务B通过sessionid没有找到对应的数据那么它创建一个新的并且将sessionid返回给客户端,这样并不能共享我们的Session无法达到我们想要的目的。
解决方案:
使用cookie来完成(很明显这种不安全的操作并不可靠)
使用Nginx中的ip绑定策略,同一个ip只能在指定的同一个机器访问(不支持负载均衡)
利用数据库同步session(效率不高,访问压力大)
使用tomcat内置的session同步(同步可能会产生延迟,集群过多tomcat,session全局复制导致性能下架)
使用token代替session
使用spring-session以及集成好的解决方案,存放在redis中(读写效率高,并可在集群环境下做高可用)
spring-session 实战
启动两个SpringBoot项目端口号分别为 8080、8090 进行测试。
1、项目依赖
// spring boot 与 redis应用基本环境配置 compile 'org.springframework.boot:spring-boot-starter-data-redis:2.1.4.RELEASE' // spring session 与 redis应用基本环境配置 compile 'org.springframework.session:spring-session-data-redis:2.1.5.RELEASE'
2、测试类
@RestController @RequestMapping("/test") public class TestController { @Value("${server.port}") private Integer projectPort; @GetMapping("/createSession") public String createSession(HttpSession session, String name) { session.setAttribute("name", name); return "当前项目端口:" + projectPort + " 当前sessionId:" + session.getId() + " 在Session中存入成功!"; } @GetMapping("/getSession") public String getSession(HttpSession session) { return "当前项目端口:" + projectPort + " 当前sessionId:" + session.getId() + " 获取的姓名:" + session.getAttribute("name"); } }
3、yml 文件配置
为了演示方便,我配置了两个 yml 文件,只是端口不同。
application.yml
server: port: 8080 spring: redis: host: 127.0.0.1 port: 6379
application-8090.yml
server: port: 8090 spring: redis: host: 127.0.0.1 port: 6379
4、启动类
在 Spring boot 的文档中,添加 @EnableRedisHttpSession 来开启 `spring session`支持,配置如下:
@SpringBootApplication @EnableRedisHttpSession public class AppRun { public static void main(String[] args) { SpringApplication.run(AppRun.class, args); } }
这样以来,最简单的 Spring boot + redis 实现 session 共享就完成了,下面进行下测试。
5、测试
首先我们开启两个 tomcat 服务,端口分别为 8080 和 8090。
启动之后进行访问测试,首先访问 8080 端口的 tomcat
http://localhost:8080/test/createSession
然后访问 8080 端口的 getSession
http://localhost:8080/test/getSession
然后再访问 8090 端口的 getSession
http://localhost:8090/test/getSession
可见,8080与9090两个服务器返回结果一样,实现了session的共享。
6、redis 查看
查看 redis 时,注意到生成了 3 个key。包含了过期时间,sessionId等。
127.0.0.1:6379> keys * 1) "spring:session:expirations:1557234900000" 2) "spring:session:sessions:expires:70c59a29-e6ef-4d4f-825d-03dae4502686" 3) "spring:session:sessions:70c59a29-e6ef-4d4f-825d-03dae4502686" 127.0.0.1:6379> hkeys spring:session:sessions:70c59a29-e6ef-4d4f-825d-03dae4502686 1) "creationTime" 2) "maxInactiveInterval" 3) "lastAccessedTime" 4) "sessionAttr:name"
因为 session 是存储在 redis 中,所以,当我把 8080 和 8090 服务器同时停掉之后,再重新启动,然后刷新浏览器中的响应,session 依然可以获取得到。这就保证了用户在登录后,重新启动服务器 session 不会失效的问题。
@EnableRedisHttpSession 注解
在上面的启动类中,用到了 @EnableRedisHttpSession 的注解,查看源码注意到它有 4 个参数让我们使用。
public @interface EnableRedisHttpSession { /** * 会话超时(以秒为单位)。 默认情况下,它设置为1800秒(30分钟) */ int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; /** * 为 redis key 定义唯一的命名空间,默认前缀是 spring:session: */ String redisNamespace() default RedisOperationsSessionRepository.DEFAULT_NAMESPACE; /** * Redis会话的刷新模式。 默认为 ON_SAVE,另一个是 IMMEDIATE */ RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE; /** * 过期会话清理的cron表达式。 默认情况下每分钟运行一次 */ String cleanupCron() default RedisHttpSessionConfiguration.DEFAULT_CLEANUP_CRON; }
比如设置
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 7200)
Spring Session 的两种刷新模式 RedisFlushMode,参考文章:http://www.ibloger.net/article/3360.html
nginx 集群配置
通过spring boot + redis来实现session的共享非常简单,而且用处也极大,配合nginx进行负载均衡,便能实现分布式的应用了。
例如开启 nginx 集群配置如下:
# 默认使用轮询, upstream backserver{ server 127.0.0.1:8080; server 127.0.0.1:8090; } # 修改server中的local location / { proxy_pass http://backserver; ... }
Gitee源码
https://gitee.com/liurenkui/springboot-session-demo
未经允许请勿转载:程序喵 » SpringBoot 分布式 Session 共享解决方案