分布式-Session共享问题

一、分布式环境下的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=6379

  2.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做了存取。

 

 

 


版权声明:本文为king13127原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。