大家好,我这名CRUD工程师又来了,最近我的一个同事突然在看分布式Seesion
的问题,然后我们两个也是互相讨论了一下,今天我就想着把分布式Session
的知识点好好的梳理一下。
在很多系统中,用户的登录功能都是用Session
去实现的,客户端填写好用户名和密码,发送一个请求,服务器收到请求之后,创建Session
,然后返回当前Session
对应的一个JessionId
,浏览器存储在cookie
中,当客户端调用其他方法给服务器发送请求的时候就会携带JessionId
,服务端收到请求后,验证Session
是否存在进而判断用户是否登录。
在分布式环境下,Session
就会出现问题了,假如服务端部署在两个服务器A和B上
用户登录时,请求落在了服务器A上,服务器A创建了一个Session
,并返回JessionId
;
用户获取个人信息时,请求落在了服务器B上,请求携带的JesssionId
在服务器B上并不会找到对应的Session
。
这时候服务器B就会给客户端返回一个异常提醒(用户未登录),客户端收到返回值,用户就会发现自己又被T到登录界面了
接下来,我们就看看在分布式环境下如何实现Session
的一致性
一 、客户端存储
既然分布式环境中,一个客户端的多个请求可能会落在多个服务器上,那么我们是否可以改变策略,直接将Session
信息存储在客户端?
答案当然是可以的,服务器将Session
信息直接存储到cookie
中,这样就保证了Session
的一致性,但是我们并不推荐这样去做,这样就会产生数据安全的隐患,因为将一些信息存储在cookie
中,相当于就把这些信息暴露给了客户端,存在严重的安全隐患。
缺点
- 系统安全性存在问题
- cookie对于数据类型及数据大小有所限制
二 、Session复制
将服务器A的Session
,复制到服务器B,同样将服务器B的Session
也复制到服务器A,这样两台服务器的Session
就一致了。像tomcat
等web容器都支持Session
复制的功能,在同一个局域网内,一台服务器的Session
会广播给其他服务器。
缺点
- 同一个网段内服务器太多,每个服务器都会去复制session,会造成服务器内存浪费。
三、Session黏性
利用Nginx
服务器的反向代理,将服务器A和服务器B进行代理,然后采用ip_hash
的负载策略,将客户端和服务器进行绑定,也就是说客户端A第一次访问的是服务器B,那么第二次访问也必然是服务器B,这样就不存在Session
不一致的问题了。
缺点
- 如果服务器A宕机了,那么客户端A和客户端B的
Session
就会出现丢失,并且客户端A、B的所有请求都会失败
四、session集中管理
这种方式就是将所有服务器的Session
进行统一管理,可以使用redis
等高性能服务器来集中管理Session
,而且spring官方提供的spirng-session
就是这样处理Session
的一致性问题。这也是目前很多企业开发用到的比较多的一种分布式Session
解决方案。
spring-session实战
Spring
提供了处理分布式Session
的解决方案——Spring Session
。Spring Session
提供了用于管理用户会话的API和实现。
Spring Session
提供了对redis
,mongodb
,mysql
等常用的存储库的支持,Spring Session
提供与HttpSession
的透明整合,这意味着开发人员可以使用Spring Session
支持的实现切换HttpSession
实现。
我在网上找了一个Spring Session
实现的流程图
Spring Session
添加了一个SessionRepositoryFilter
的过滤器,用来修改包装请求和响应,包装后的请求为SessionRepositoryRequestWrapper
,调用getSession()
方法的时候实际上就是调用Spring Session
实现了的Session
。
Spring Session
使用非常简单,添加了相关依赖后,直接操作HttpSession
就可以实现效果。
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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>
yml
spring:
session:
# session 失效时间(分钟)
timeout: 86400
# session 使用redis存储
store-type: redis
# redis 配置
redis:
# redis 端口号
port: 6379
# redis 服务器地址
host: localhost
# redis库
database: 0
# redis 密码
password: 123456
使用session
public String test( HttpServletRequest request){
HttpSession session = request.getSession();
session.setAttribute("key_big_handsome","value:it is me");
return session.getAttribute("key_big_handsome").toString();
}
执行了这个方法,我们看redis
- 第一个用来表示
Session
在Redis中的过期,这个k-v不存储任何有用数据,只是表示Session
过期而设置。这个key在Redis
中的过期时间即为Session
的过期时间间隔。 - 第二个存储这个
Session
的id,是一个Set
类型的Redis
数据结构。这个key中的最后的1681633260000值是一个时间戳,根据这个Session过期时刻滚动至下一分钟而计算得出。 - 第三个用来存储
Session
的详细信息,包括Session
的过期时间间隔、最近的访问时间、attributes
等等。
Spring Session
中有个定时任务,每个整分钟都会查询相应的spring:session:expirations:
整分钟的时间戳中的过期SessionId
,然后再访问一次这个SessionId
,即spring:session:sessions:expires:SessionId
,以便能够让Redis
及时的产生key过期事件——即Session
过期事件。