Cluster session shared two solutions

First, what is a cluster and shared Session

Cluster is a group of interconnected and have the same functions of the server, each server node in the cluster is called. Server load balancing by scheduling the client requests access to these nodes equalized. However, a problem appears, problems such session, the user A to log in the first node A, is next scheduled to load balance server node B, and node B does not session information of the user A, and then re-login . There are many solutions, such as using Redis, but also provides a Tomcat cluster session synchronization method.

And here said load balancing servers, such as Nginx, receives the user's request and will be forwarded to the backend server cluster a machine through a certain algorithm.
Here Insert Picture Description
Perhaps there are other shared programs, but I have used the following two. Tomcat and comes Spring-Session + Redis

Two, Tomcat cluster Session Sharing

First, do not set up the cluster configuration, two Servlet written test to see if problems arise.

Determining whether the request parameter LoginServlet pass 123, and is then redirected to set session information is IndexServlet, otherwise the output authentication failure.

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class LoginServlet
 */
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    public LoginServlet() {
        super();
    }
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.addHeader("Content-Type","text/plain;charset=utf-8");
		if("123".equals( request.getParameter("pass"))) {
			request.getSession().setAttribute("name", "张三");
			response.sendRedirect("IndexServlet");
		}else {
			response.getWriter().append("pass 认证失败");
		}
	}
}

IndexServlet first acquires the session information, and if not then go to the LoginServlet tone, exists output name.

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/IndexServlet")
public class IndexServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    public IndexServlet() {
        super();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		response.addHeader("Content-Type","text/plain;charset=utf-8");
		HttpSession session =request.getSession();
		StringBuffer sb =new StringBuffer();
		String name =(String) session.getAttribute("name");
		if(name==null) {
			response.sendRedirect("LoginServlet");
		}else {
			sb.append("sessionId="+session.getId() +"\n");
			sb.append("name="+name);
			response.getWriter().append(sb);
		}
	}
}


Prepare two Tomcat, but pay attention to modify the port number, mainly the following three nodes, or occupied by the port will not start.

<Server port="8004" shutdown="SHUTDOWN">
<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Connector port="8008" protocol="AJP/1.3" redirectPort="8443" />

After packing the webapps were placed two Tomcat.
Here Insert Picture Description
After 8080 log on, tune go IndexServlet, and successfully display name.
http: // localhost:? 8080 / DynamicWebDemo / LoginServlet pass = 123
Here Insert Picture Description
but when the conduct of 8081 to http: // localhost: 8081 / DynamicWebDemo / IndexServlet, this time will jump to LoginServlet.

首先Cookie是和端口无关的,也就是8080的Cookie,会被8081的请求所带上,而8081的Tomcat上没有保存8080中的session信息,所以会失败。如果8081重新登录后,由于产生新sessionId,被8080所带上请求,而8080的Tomcat又没有这个session的信息。如次反复,互相顶来顶去。

下面就是解决这个问题,配置Tomcat集群设置。

首先在server.xml中增加<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>,并且在项目的web.xml中增加<distributable />即可,还有很多设置,这里都采用默认就行。
Here Insert Picture Description
<distributable /> 必须增加。
Here Insert Picture Description
这时候重启两个Tomcat,首先在8080上进行登录
Here Insert Picture Description
接着直接访问8081上的IndexServlet,可以发现直接显示了信息,并且sessionId是一样的。
Here Insert Picture Description
而刚才所说的Cluster配置,还有很多配置,如以下。
可以参考https://tomcat.apache.org/tomcat-9.0-doc/cluster-howto.html或者https://www.mulesoft.com/tcat/tomcat-clustering

<Engine name="Catalina" defaultHost="localhost" jvmRoute="[workername]">
<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" />
            <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>

在Tomcat官网也说,对于较小的群集适用,而超过4个节点则不建议使用,节点多了通信开销也大,

三、Spring-Session

Spring-Session 提供了一套创建和管理HttpSession 的方案,是把session信息保存在一个公共的会话仓库中,所有服务器都访问同一个仓库,这样所有服务器的状态就都一致了,他支持存储在Hazelcast 、Redis、MongoDB、关系型数据中,以下主要写如何保存在Redis,来解决 session 共享的问题。

Here Insert Picture Description

You must first download and install Redis. Windows is simple to install, under linux, unpacked into the directory, execute make (the premise installed compiler).
After entering the src directory, you can start the execution ./redis-server redis service here is not to say that the basic operation of the redis.
Here Insert Picture Description
Here Insert Picture Description
After the introduction of two jar package.

 <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>

If the download is slow, change it mavne set to increase Ali cloud images.

 <mirror>  
	  <id>nexus-aliyun</id>  
      <mirrorOf>central</mirrorOf>    
      <name>Nexus aliyun</name>  
      <url>http://maven.aliyun.com/nexus/content/groups/public</url>  
</mirror>

Here Insert Picture Description
They use the default configuration, with a password of password, no password is empty on the line.

#redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=

spring.session.store-type=redis

Increase in EnableRedisHttpSession startup class notes, maxInactiveIntervalInSeconds as session expiration time, in seconds.

@SpringBootApplication
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 10)
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Written test interface, logic, and said the same.

@Controller
@RequestMapping(value = "/")
public class SessionController {

        @ResponseBody
        @RequestMapping(value = "/login")
        public void  getSession(HttpServletRequest request, HttpServletResponse response) throws IOException {
            response.addHeader("Content-Type","text/plain;charset=utf-8");
            if("123".equals( request.getParameter("pass"))) {
                request.getSession().setAttribute("name", "张三");
                response.sendRedirect("/index");
            }else {
                response.getWriter().append("pass 认证失败");
            }
        }

        @ResponseBody
        @RequestMapping(value = "/index")
        public void  get(HttpServletRequest request, HttpServletResponse response) throws IOException {
            response.addHeader("Content-Type","text/plain;charset=utf-8");
            HttpSession session =request.getSession();
            StringBuffer sb =new StringBuffer();
            String name =(String) session.getAttribute("name");
            if(name==null) {
                response.sendRedirect("/login");
            }else {
                sb.append("sessionId="+session.getId() +"\n");
                sb.append("name="+name);
                response.getWriter().append(sb);
            }
        }

}

Jar is then packaged into packets. Start two different port numbers.

mvn package
java -jar target/demo-0.0.1-SNAPSHOT.jar --server.port=8081
java -jar target/demo-0.0.1-SNAPSHOT.jar --server.port=8080

After 8080 when the login, access / index on 8081, the same information can be seen, after 10 seconds, this live session expired, the same two services will fail.
Here Insert Picture Description
You can also create session failed to listen.

@Configuration
public class RedisSessionConfig {


    @EventListener
    public void onSessionExpired(SessionExpiredEvent expiredEvent) {
        String sessionId = expiredEvent.getSessionId();
        System.out.println("过期"+sessionId);
    }

    @EventListener
    public void onSessionCreated(SessionCreatedEvent createdEvent) {
        String sessionId = createdEvent.getSessionId();
        System.out.println("创建"+sessionId);
    }

    @EventListener
    public void onSessionDeleted(SessionDeletedEvent deletedEvent) {
        String sessionId = deletedEvent.getSessionId();
        System.out.println("删除"+sessionId);
    }
}

Here Insert Picture Description
* View redis while the key by keys, you can also hash type of operation associated with the command redis view some information.
Here Insert Picture Description

Published 42 original articles · won praise 7 · views 7705

Guess you like

Origin blog.csdn.net/HouXinLin_CSDN/article/details/104535712