一、分布式环境下的Session问题
在单体应用时代,我们不需要考虑Session不同步的问题,但在分布式系统中,因为Http协议是⽆状态的协议,客户端和服务端在某次会话中产⽣的数据不会被保留下来,所以第⼆次请求服务端⽆法认识到你曾经来过。
Http为什么要设计为⽆状态协议?早期都是静态⻚⾯⽆所谓有⽆状态,后来有动态的内容更丰富,就需要有状态,出现了两种⽤于保持Http状态的技术,那就是Cookie和Session。单体应用时代依靠Cookie和Session成功实现了记录登录状态的功能,可session是在服务器端存储的,在分布式系统中每次访问可能是不同的服务器,导致session未被找到,而导致重复登录问题,如下图。

二、解决Session⼀致性的⽅案
2.1 Nginx的 IP_Hash 策略(可以使用)
修改nginx的配置文件,这样同⼀个客户端IP的请求都会被路由到同⼀个⽬标服务器,也叫做会话粘滞,这样就解决了session找不到问题。
#配置负载服务
upstream mylogin{
ip_hash;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
server {
listen 80;
server_name localhost;
#配置路径和服务
location / {
proxy_pass http://mylogin/;
}
}使用这种方式优点是配置简单,不⼊侵应⽤,不需要额外修改代码,但也有不少缺点,如服务器重启Session丢失、存在单点负载⾼的⻛险等问题。
2.2 Session复制(不推荐)
多个tomcat之间通过修改配置⽂件,达到Session之间的复制。
<!-- 第1步:修改server.xml,在Host节点下添加如下Cluster节点 -->
<!-- 用于Session复制 -->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown="false" notifyListenersOnReplication="true" />
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService" address="228.0.0.4"
port="45564" frequency="500" dropTime="3000" />
<!-- 这里如果启动出现异常,则可以尝试把address中的"auto"改为"localhost" -->
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver" address="auto" port="4000"
autoBind="100" selectorTimeout="5000" maxThreads="6" />
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" />
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector" />
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor" />
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve" filter="" />
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve" />
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer" tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/" watchDir="/tmp/war-listen/" watchEnabled="false" />
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener" />
</Cluster>
<!-- 第2步:在web.xml中添加如下节点 -->
<!-- 用于Session复制 -->
<distributable/>这种方式优点也是不⼊侵应⽤,服务器重启或者宕机不会造成Session丢失。但因为需要数据同步,导致性能低,有延迟性。
2.3 Spring Session基于redis实现Session共享(推荐)
Spring Session提供了多种方式来存储Session信息,包括redis、mongo、gemfire、hazelcast、jdbc等。这里用redis来举例说明,首先进行依赖添加,然后进行配置即可。
2.3.1 引入jar包
<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>2.3.2 配置redis
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=63792.3.3 添加注解@EnableRedisHttpSession
@SpringBootApplication
@EnableCaching
@EnableRedisHttpSession
public class LoginApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(LoginApplication.class, args);
}
}这种方式能适应各种负载均衡策略,同时扩展能力强,唯一缺点就是对代码有一些侵入性,但好在因为springboot的强大装配功能,使得配置比较简单。
扩展一下,通过观察源码,会发现实际springsession有一个核心Filter去对session做了存取。