Problems and solutions of cluster session Session in Spring Security (keep, copy, share)

        When we first understand the Session, we know that the Session is usually stored in the server's memory. Each client visit will carry the SessionId, and then look for it in the server's memory. Although this method is simple and fast, there are still obvious shortcomings. The memory of the server is limited, so there is not much space left for the Session, so once the user visits a lot, the memory will be stretched. And because the session is stored in the memory, not a persistent storage, even if the server performance is particularly good, it is inevitable that there will be an abnormal stop and restart of the server, which will lead to the loss of the session state.

        You may say that using cluster deployment, deploying a few more servers can solve the above problems, such as the cluster deployment of the network structure shown in the figure above. In this case, the user's request will first go to the load-balanced server, and then distribute the request to the subsequent servers according to different load policies. At this time, there is such a possibility that the user's login operation is completed on Server1, so the Session information is cached on Server1, but Server2 and Server3 are unaware, if the same user visits again if the request After being assigned to Server2 and Server3, the Session cannot be found, and the user will be required to log in again. This is the problem of out-of-sync session state in a clustered environment.

Cluster session scheme

       Aiming at the problem of session sharing in the cluster environment, the current common solutions are mainly divided into the following three categories:

        1) Session keeps

        2) Session replication

        3) Session sharing

        Session keeps

         The so-called session retention is also called session stickiness. This method needs to be combined with the above load balancing strategy. The sticky and hold here actually means that the user's first request is forwarded to Server1 by the load balancing LB (Load Balance) server. After setting it as sticky and hold, the subsequent requests of the user are always only through Server1. The server, in a disguised form, binds the user to the Server1 server, which is the so-called retention and stickiness. The way to achieve this is also very simple. The load balancing strategy of IP hash is usually used to forward the requests from the same client to the same server.

# upstream代表负载均衡的策略
upstream session_keep{
    ip_hash;
    server xxxx:8080;
    server xxxx:8080;
    # down代表server暂时不参与负载均衡
    server xxxx:8080 down;
}

       The above is the way to implement session retention in the Nginx environment. It can be seen that it is very simple and does not need to do any processing on the session, and it can also achieve a certain degree of cluster session. However, in this way, it is still unavoidable that when the accessed server fails, the user will be transferred to another server.

       In addition, there is still an obvious error in the way of session retention - the request is forwarded to the server through IP hash, but if a company or organization uses the same IP exit, then the employees of this company or organization will be distributed to the same server, while the other IP is only used by one or several users at the same time. In this case, it is obvious that there is a certain degree of load imbalance, and if the company's employees or organizations use a large number of people, the pressure on the server is also challenging.

        Session copy

        As the name implies, this method is to synchronously copy session data between cluster servers. Generally speaking, the session information generated by user A after accessing Server1 will be regularly copied and pasted to other servers such as Server2 and Server3, so as to ensure The consistency of the session state of each instance of . The disadvantage of this method is undoubtedly that the session data of any server changes and will be copied to other servers. If there are many servers, this synchronization will not only consume data bandwidth, but also occupy a large amount of time resources, and it is inefficient, and there are also serious delays.

        The way to implement Session replication is also very simple, requiring the help of Tomcat cluster configuration. That is, configure the server.xml of each tomcat node. The specific configuration information is as follows

<!-- 
className:指定Cluster使用的类
channelSendOptions:Cluster发送消息的方式,可选项:2,4,8,10
    2:Channel.SEND_OPTIONS_USE_ACK(确认发送)
    4: Channel.SEND_OPTIONS_SYNCHRONIZED_ACK(同步发送)
    8: Channel.SEND_OPTIONS_ASYNCHRONOUS(异步发送)
在异步模式下,可以通过加上确认发送来提高可靠性,此时将channelSendOptions设置为10
-->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" 
			 channelSendOptions="8">
	
<!--
Manager决定如何管理集群中的Session信息。Tomcat包括BackupManager和DeltaManager等Manager。
    BackupManager:是将集群下所有Session信息放到一个备份节点。所有节点均可访问备份节点。
            每个节点部署的应用可以不一致
    DeltaManager:集群下某一节点生成、改动Session,将复制到其他节点。Tomcat默认的集群Manager。
             每个节点部署的应用要一致
expiressSessionsOnShutdown : 设置为true时,一个节点关闭,将导致集群下的所有Session失效。
notifyListenersOnReplication : 集群节点之间Session复制、删除操作是否通知Session Listeners
maxInactiveInterval : 集群下Session的有效时间
-->        
		<Manager className="org.apache.catalina.ha.session.DeltaManager" 
				 expireSessionsOnShutdown="false" 
				 notifyListenersOnReplication="true" />
        <!--
            Channel是Tomcat节点之间进行通讯的工具。Chanel包括四个组件:
            MemberShip(可用节点列表)、 Sender(发送器,负责发送消息)
            Receiver(接收器,负责接收消息)、Interceptor(拦截器)也分别对应下面的几个标签
        -->				 
		<Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <!--
            Membership维护集群的可用节点列表。它可以检查到新增节点,也可以检查到没有心跳节点
                这里默认使用的是多播通信即McastService
                address:广播的地址
                port:广播端口
                frequency:发送心跳(向广播地址发送UDP数据包)的时间间隔 
                dropTime: Membership在dropTime内未收到某一节点心跳,将从节点列表删除该成员
            
             组播(Multicast):一个发送者和多个接收者之间实现一对多的网络连接。一个发送者同时        
                               给多个接收者传输相同的数据,只需复制一份相同的数据包。它提高了数 
                               据传送效率,减少了骨干网络出现拥塞的可能性。相同组播地址、端口的        
                               Tomcat节点,可以组成集群下的子集群
            -->   
			<Membership className="org.apache.catalina.tribes.membership.McastService" 
						address="228.0.0.4" 
                        port="45564" 
						frequency="500" 
						dropTime="3000" />
		    
            <!--
                接收器主要分为两种:BioReceiver(阻塞式)/NioReceiver(非阻塞式)
                address:接收消息的地址
                port:接收消息的端口
                autoBind:端口的区间变化,即当port为4000,autoBind为100,
                         接收器将在4000-4099间取一个端口,进行监听
                selectorTimeout:NioReceiver内轮询的超时时间
                maxThreads:线程池最大的线程数
            -->  
			<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
					  address="auto" 
					  port="4000" 
                      autoBind="100" 
					  selectorTimeout="5000" 
					  maxThreads="6" />
            
			<!--
            发送器,内嵌Transport组件,Transport是真正负责发送消息的。Transport分为两种:    
                  bio.PooledMultiSender(阻塞式)、nio.PooledParallelSender(非阻塞式)
            -->		  
			<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
				<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" />
			</Sender>
        
            <!--
               Cluster的拦截器——TcpFailureDetector
               当网络、系统比较繁忙时,Membership可能无法及时更新可用节点列表,此时该拦截器
               可以拦截到某个节点关闭的信息,并尝试用TCP连接节点,确保节点真正关闭,从而更新列表
            -->
			<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector" />

            <!--
               Cluster的拦截器——MessageDispatch15Interceptor
               当Cluster的channelSendOptions设置为异步发送时,会先将等待发送的消息进行排队,然        
               后将排队好的信息转给Sender
            --> 
			<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor" />
		</Channel>
		
        <!--
            此对象可以理解为Tomcat的拦截器:
        ReplicationValve:在处理请求前后处理日志,过滤不涉及Session变化的请求
        JvmRouteBinderValve:Apache的mod_jk发生错误时,保证同一个客户端请求发送到集群同一个节点
        -->
		<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" />

        <!--监听Cluster组件接收的消息,启用DeltaManager时,会将Cluster接收的信息通过    
              ClusterSessionListener传递给DeltaManager--> 
		<ClusterListener 
className="org.apache.catalina.ha.session.ClusterSessionListener" />
</Cluster> 

        Session sharing

     

        Compared with the previous two solutions, Session sharing is more practical and reliable. Session sharing refers to extracting the session from the server, centrally storing it in an independent data container, and sharing it among various servers. The current mainstream solution is to save the Session data that needs to be shared between servers in a public place, such as Redis.

        The figure above shows the server network architecture under Session sharing. Since all server instances have a single point of access to sessions, the problem of cluster out-of-sync naturally does not exist, and the capacity of independent data containers is much larger than that of server memory. In addition, features such as separation from the service itself and persistence make the session state not lost due to service stop. However, due to the increased network interaction of independent data containers, the read/write performance, stability, and network I/O speed of data containers have become performance bottlenecks. Therefore, it is recommended that in the intranet environment, a highly available Redis server is the best choice. Redis is a memory-based feature that allows it to have extremely high read/write performance. High-availability deployment not only reduces network I/O loss, but also improves stability.

        If you want to share the session to Redis, you can implement it by yourself. However, in this case, you need to consider how to obtain the optimal access structure, how to clean up expired sessions, and integrate WebSocket. That's why Spring Session is specially designed to solve the cluster session problem.

        Here is a brief introduction to the integration of Spring and Spring Session based on Redis.

         First, you need to add Spring Session dependencies and Redis dependencies.

<dependencies>
        <!--Spring Session 核心依赖-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-core</artifactId>
        </dependency>

        <!--Spring Session对接Redis的依赖-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

        <!-- Spring Boot整合Redis核心依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
</dependencies>

        After that, Spring Session can be configured, mainly for Spring Security to provide a cluster-supported session registry, SpringSessionBackedSessionRegistry.

// 启用基于Redis的HttpSession
@EnableRedisHttpSession
public class HttpSessionConfig{
    
    // 提供Redis连接
    @Bean
    public RedisConnectionFactory connectionFactory(){
        return new JedisConnectionFactory();
    }
    
    @Autowired
    private FindByIndexNameSessionRepository sessionRepository;
    
    // SpringSessionBackedSessionRegistry 是session为Spring Security提供的
    // 主要用于集群环境下控制会话并发的会话注册表实现
    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry(){
        return new SpringSessionBackedSessionRegistry(sessionRepository);
    }
    
    // HttpSession事件监听,改用session提供的会话注册表
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher(){
        return new HttpSessionEventPublisher();
    }
}

        Finally, provide the new session registry to Spring Security.

 other options

        In addition to the three solutions mentioned above, there are other ways to achieve it. Here I will only cite one of them - using Token instead of Session. Token refers to the token credential for accessing resources, which is used to verify the legitimacy of the request and is used for projects where the front and back ends are separated. This method can also remember the current user and other information. The commonly used authentication and authorization mechanism is based on Json Web Token (JWT), and the use of Token can avoid CSRF attacks, and it perfectly meets the needs of the mobile terminal.

Reference article:

Spring Security Series Tutorial 21--Session Management Realization of Cluster Session Tomcat Cluster (1) Summarize the way tomcat's server.ml configures cluster, and the solution to Tomcat cluster session sharing failure

Guess you like

Origin blog.csdn.net/qq_35363507/article/details/121992530