我们使用session的功能实现了单机上的剔除前一个登录用户的功能,那么如果是集群化部署的情况下
如何实现该功能?
集群会话管理
如果是单机部署,不会存在session共享的问题
但是集群化部署的时候,就必须面对session共享的问题
session共享
主流的session共享方案是将session存放到公告的第三方组件,如redis
共享前架构 | |
---|---|
共享后架构 |
当所有 Tomcat 需要往 Session 中写数据时,都往 Redis 中写,
当所有 Tomcat 需要读数据时,都从 Redis 中读。这样,
不同的服务就可以使用相同的 Session 数据了
这样的方案,可以由开发者手动实现,即手动往 Redis 中存储数据,手动从 Redis 中读取数据,
相当于使用一些 Redis 客户端工具来实现这样的功能,毫无疑问,手动实现工作量还是蛮大的。
一个简化的方案就是使用 Spring Session 来实现这一功能,
Spring Session 就是使用 Spring 中的代理过滤器,将所有的 Session 操作拦截下来,
自动的将数据 同步到 Redis 中,或者自动的从 Redis 中读取数据。
对于开发者来说,所有关于 Session 同步的操作都是透明的,
开发者使用 Spring Session,一旦配置完成后,具体的用法就像使用一个普通的 Session 一样
session 拷贝
session 拷贝就是不利用 redis,直接在各个 Tomcat 之间进行 session 数据拷贝,但是这种方式效率有点低,Tomcat A、B、C 中任意一个的 session 发生了变化,都需要拷贝到其他 Tomcat 上,如果集群中的服务器数量特别多的话,这种方式不仅效率低,还会有很严重的延迟。
所以这种方案一般作为了解即可。
粘滞会话
所谓的粘滞会话就是将相同 IP 发送来的请求,通过 Nginx 路由到同一个 Tomcat 上去,这样就不用进行 session 共享与同步了。
这是一个办法,但是在一些极端情况下,可能会导致负载失衡(因为大部分情况下,都是很多人用同一个公网 IP)。
所以,Session 共享就成为了这个问题目前主流的解决方案了。
实现session共享
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
使用Spring Session
Spring Session支持以下方式存储Session,这里只使用Redis
public enum StoreType {
REDIS,
MONGODB,
JDBC,
HAZELCAST,
NONE;
private StoreType() {
}
}
1、配置
spring:
session:
store-type: redis
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 500
实现
配置完成后 ,就可以使用 Spring Session 了,
其实就是使用普通的 HttpSession ,其他的 Session 同步到 Redis 等操作,框架已经自动帮你完成了:
@RestController
public class SessionController {
@Value("${server.port}")
Integer port;
@GetMapping("/set")
public String set(HttpSession session) {
session.setAttribute("user", "admin");
return String.valueOf(port);
}
@GetMapping("/get")
public String get(HttpSession session) {
return session.getAttribute("user") + ":" + port;
}
}
考虑到一会 Spring Boot 将以集群的方式启动 ,
为了获取每一个请求到底是哪一个 Spring Boot 提供的服务,需要在每次请求时返回当前服务的端口号,因此这里我注入了 server.port 。
idea 中使用变量注入端口
-Dserver.port=29898
-Dserver.port=29899
在29898实例上登录并set session
localhost:29898/set
在29899实例上get
localhost:29899/get
我们不同的实例之间共享了session变量,访问其他接口也是一样可以
前后端分离security配置
session共享已经实现了,但是前后端分离的情况下,redis的共享session还是失效了
对于前后端分离,我们要将基于内存管理session的配置改为自定义的redis
修改其中的配置即可
实际上 Spring Session 为我们提供了对应的实现类 SpringSessionBackedSessionRegistry
@Autowired
FindByIndexNameSessionRepository sessionRepository;
@Bean
SpringSessionBackedSessionRegistry sessionRegistry() {
return new SpringSessionBackedSessionRegistry(sessionRepository);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.sessionManagement()
.maximumSessions(1)//配置最大session数为1,后登录会踢掉前一个
.maxSessionsPreventsLogin(true)//已登录后不允许再登录
.sessionRegistry(sessionRegistry())//前后端分离配置session共享
;
}
我们在这里只需要提供一个 SpringSessionBackedSessionRegistry 的实例,
并且将其配置到 sessionManagement 中去即可。
以后,session 并发数据的维护将由 SpringSessionBackedSessionRegistry 来完成,
而不是 SessionRegistryImpl,
我们关于 session 并发的配置就生效了,在集群环境下,用户也只可以在一台设备上登录。
nginx配置
为了测试我们的配置生效,引入nginx进行负载均衡
编辑配置文件
编辑 nginx.conf 文件:
upstream security{
server 127.0.0.1: 8080 weight=l
server 127.0.0.1: 8081 weight-2;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://javaboy.org;
proxy_redirect default;
}
}
- upstream 表示配置上游服务器
- javaboy.org 表示服务器集群的名字,这个可以随意取名字
- upstream 里边配置的是一个个的单独服务
- weight 表示服务的权重,意味者将有多少比例的请求从 Nginx 上转发到该服务上
- location 中的 proxy_pass 表示请求转发的地址,/ 表示拦截到所有的请求,转发转发到刚刚配置好的服务集群中
- proxy_redirect 表示设置当发生重定向请求时,nginx 自动修正响应头数据(默认是 Tomcat 返回重定向,此时重定向的地址是 Tomcat 的地址,我们需要将之修改使之成为 Nginx 的地址)。
将应用打包进行启动
nohup java -jar session-4-0.0.1-SNAPSHOT.jar --server.port=8080 &
nohup java -jar session-4-0.0.1-SNAPSHOT.jar --server.port=8081 &
- nohup 表示当终端关闭时,Spring Boot 不要停止运行
- & 表示让 Spring Boot 在后台启动
重启nginx
/usr/local/nginx/sbin/nginx -s reload
Nginx 启动成功后,我们首先手动清除 Redis 上的数据,
然后访问 192.168.66.128/set 表示向 session 中保存数据,这个请求首先会到达 Nginx 上,再由 Nginx 转发给某一个 Spring Boot 实例: