微服务下Session共享问题的解决

微服务下Session共享问题的解决

一、Session共享原理图

image20200602175051461.png

Session是记录在服务端的,所以在分布式架构下session是不能实现服务与服务之间的贡献,不同域名下也是不能实现贡献的

二、解决方案

(1) 解决方案一:session复制

  • 优点
    web-server (Tomcat)原生支持,只需要修改配置文件

  • 缺点
    session同步需要数据传输, 占用大量网络带宽,降低了
    服务器群的业务处理能力
    任意一台web- server保存的数据都是所有web-server的
    session总和,受到内存限制无法水平扩展更多的web-
    server
    大型分布式集群情况下,由于所有web server都全量保
    存数据,所以此方案不可取。

    配置如下:

	<!-- 第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/>

解决方案二:hash一直性

  • 优点:
    只需要改nginx配置,不需要修改应用代码
  • 负载均衡,只要hash属性的值分布是均匀的,多台web-
    server的负载是均衡的
    可以支持web-server水平扩展(session同步法是不行的,
    受内存限制)
  • 缺点
    session还是存在web-server中的,所以web-server重启
    可能导致部分session丢失,影响业务,如部分用户需要
    重新登录
    • 如果web-server水平扩展, rehash后session重新分布,
      也会有一部分用户路由不到正确的session
  • 但是以上缺点问题也不是很大,因为session本来都是有有效
    期的。所以这两种反向代理的方式可以使用

解决方案三:统一存储(springsession)

将以前要存在session中的数据,统一存储在缓存中间件(redis、DB……)

  • 优点:

    • 没有安全隐患可以水平扩展,数据库/缓存水平切分即可
    • web-server重启或者扩容都不会有session丢失
  • 不足

    • 增加了一次网络调用,并且需要修改应用代码:如将所有的getSession方法替换为从Redis
      查数据的方式。redis获取数据比内存慢很多
    • 上面缺点可以用SpringSession完美解决

    使用spring-session

    官网:https://spring.io/projects/spring-session-data-redis

    步骤1:引入Maven依赖

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-bom</artifactId>
                <version>Corn-SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    
            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-data-redis</artifactId>
                <exclusions>
                  <!--因为我其他服务中引入了spring-data-redis这个依赖所以需要排除这个依赖-->  
                    <exclusion>
                        <groupId>org.springframework.data</groupId>
                        <artifactId>spring-data-redis</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    

步骤二: yml配置选择缓存session对象的存储方式()
image20200602232459993.png

spring:
  session:
    timeout: 30m  // 过期时间
    store-type: redis 

步骤三: 启动类上开启 对springsession的支持

@EnableRedisHttpSession // 开启spring session redis
@SpringBootApplication
public class ServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class, args);
    }

}

步骤四: 添加session

// 返回的登录用户的数据放在session中  HttpSession sessionRespone
sessionRespone.setAttribute("loginUser",data);

这样springsession就会自动帮我们完成redis的session操作,要解决不同域名下的情况共享还要通过下面的配置来解决

session子域名共享

@Configuration
public class GulimallSessionConfig  {

   private ClassLoader loader;

   @Bean
   public CookieSerializer cookieSerializer () {
      DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
      cookieSerializer.setDomainName("一般使用域名");// 设置session的作用域,将session的作用域放大到父域名
      return cookieSerializer;
   }

   @Bean
   public RedisSerializer<Object> redisSerializer(){
      return new FastJsonRedisSerializer<>(Object.class);
   }

}