preface


  HttpSession Yes Servlet Container creation and management , image Tomcat/Jetty It's all stored in memory . But let's build an application cluster of component layout , And then use LVS or Nginx Load balancing , From the same user Http Requests will likely be distributed to multiple different applications . That's the question , How to ensure that different applications can share the same session Data ? The simplest idea , Is to session Data is saved to a unified place outside memory , for example Memcached/Redis Etc . That's another question , How to replace Servlet Container creation and management HttpSession The realization of ?

  1, utilize Servlet Plug in functions provided by container , custom HttpSession Create and manage policies for , And replace the default policy by configuration .
In fact, there have been open source projects for a long time , for example memcached-session-manager( You can refer to
load balancing +session share (memcached-session-manager realization
<http://www.cnblogs.com/youzhibing/p/5094460.html>)
, as well as tomcat-redis-session-manager. But there's a drawback , It's about coupling Tomcat/Jetty etc. Servlet Container code .

  2, Design a Filter, utilize HttpServletRequestWrapper, Realize your own
getSession() method , Take over creation and management Session Data work .spring-session That's how it works .

reference resources  spring-session one of Preliminary study spring-session
<http://feitianbenyue.iteye.com/blog/2326408>


   This blog does not cover session explain , about session Let's check the data by ourselves ; about spring-session You can go spring Official website (http://projects.spring.io/spring-session/).

Stand alone application

   Let's take a look at the stand-alone application , The application is very simple , It's in session Set variable in , Then get the variables of these settings for display , The specific code is as follows

  pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation
="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.yzb.lee</groupId>
<artifactId>spring-session</artifactId> <packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version> <name>spring-session Maven Webapp</name>
<url>http://maven.apache.org</url> <properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies>
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId>
<version>1.2</version> </dependency> <dependency> <groupId>junit</groupId>
<artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope>
</dependency> </dependencies> <build> <finalName>spring-session</finalName>
</build> </project> View Code
  web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app> <display-name>Archetype
Created Web Application</display-name> <servlet>
<servlet-name>session</servlet-name> <servlet-class
>com.yzb.lee.servlet.SessionServlet</servlet-class> </servlet>
<servlet-mapping> <servlet-name>session</servlet-name>
<url-pattern>/session</url-pattern> </servlet-mapping> <welcome-file-list>
<welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> View Code
  SessionServlet.java
package com.yzb.lee.servlet; import java.io.IOException; import
javax.servlet.ServletException;import javax.servlet.http.HttpServlet; import
javax.servlet.http.HttpServletRequest;import
javax.servlet.http.HttpServletResponse;public class SessionServlet extends
HttpServlet {private static final long serialVersionUID = 1L; @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException { String attributeName=
req.getParameter("attributeName"); String attributeValue =
req.getParameter("attributeValue");
req.getSession().setAttribute(attributeName, attributeValue);
resp.sendRedirect(req.getContextPath()+ "/"); } } View Code
  index.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page
isELIgnored="false" %> <!DOCTYPE html> <html lang="en"> <head> <title>Session
Attributes</title> </head> <body> <divclass="container"> <h1>Description</h1>
<p>This application demonstrates how to use a Redis instance to back your
session. Notice that there is no JSESSIONID cookie. We are also able to
customize the way of identifying what the requested session id is.</p> <h1>Try
it</h1> <formclass="form-inline" role="form" action="./session" method="post">
<labelfor="attributeName">Attribute Name</label> <input id="attributeName"
type="text" name="attributeName"/> <labelfor="attributeValue">Attribute
Value</label> <input id="attributeValue" type="text" name="attributeValue"/>
<input type="submit" value="Set Attribute"/> </form> <hr/> <tableclass="table
table-striped"> <thead> <tr> <th>Attribute Name</th> <th>Attribute Value</th>
</tr> </thead> <tbody> <c:forEach items="${sessionScope}" var="attr"> <tr>
<td><c:out value="${attr.key}"/></td> <td><c:out value="${attr.value}"/></td>
</tr> </c:forEach> </tbody> </table> </div> </body> </html> View Code
   The whole project structure is very simple , As follows

  

   Run locally , The effect is as follows



   Firefox and 360 Browsers represent different users , Each can get its own session All variables set in , It's normal , No problem .

Distributed cluster application

   In stand-alone application ,session No problem , It's local servlet In container , Is it as normal as a single machine in a distributed cluster ? Let's look down

   Build highly available , The distributed cluster environment for load balancing can be referred to nginx Load balancing of requests + keepalived realization nginx High availability of
<http://www.cnblogs.com/youzhibing/p/7327342.html>, What we didn't build needs to build the distributed environment first

   No settings ession share

     Application unchanged , The code is exactly the same as that in a single machine , Deploy the code to the distributed cluster

     Run it all , The effect is as follows



     The result is : No matter what session How many values to set ,session None of the values in could be obtained ( There is still a gap from my expectation , Please see my question about the specific gap )

  spring-session realization session share

     Application changes , Code is different from before , The specific differences are as follows (SessionServlet And index.jsp unchanged )

    pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation
="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.yzb.lee</groupId>
<artifactId>spring-session</artifactId> <packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version> <name>spring-session Maven Webapp</name>
<url>http://maven.apache.org</url> <properties>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies>
<dependency> <groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.3.1.RELEASE</version> <type>pom</type> </dependency> <dependency>
<groupId>biz.paluch.redis</groupId> <artifactId>lettuce</artifactId>
<version>3.5.0.Final</version> </dependency> <dependency>
<groupId>org.springframework</groupId> <artifactId>spring-web</artifactId>
<version>4.3.4.RELEASE</version> </dependency> <dependency>
<groupId>javax.servlet</groupId> <artifactId>jstl</artifactId>
<version>1.2</version> </dependency> <dependency> <groupId>junit</groupId>
<artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope>
</dependency> </dependencies> <build> <finalName>spring-session</finalName>
</build> </project> View Code
    web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app> <display-name>Archetype
Created Web Application</display-name> <!-- spring-session config -->
<context-param> <param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring-session.xml</param-value> </context-param> <!--
this filter Put it first --> <filter>
<filter-name>springSessionRepositoryFilter</filter-name> <filter-class
>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter>
<filter-mapping> <filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern> <dispatcher>REQUEST</dispatcher>
<dispatcher>ERROR</dispatcher> </filter-mapping> <listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <servlet> <servlet-name>session</servlet-name>
<servlet-class>com.yzb.lee.servlet.SessionServlet</servlet-class> </servlet>
<servlet-mapping> <servlet-name>session</servlet-name>
<url-pattern>/session</url-pattern> </servlet-mapping> <welcome-file-list>
<welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> View Code
    spring-session.xml
<?xml version="1.0" encoding="UTF-8"?> <beans
xmlns="http://www.springframework.org/schema/beans" xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p
="http://www.springframework.org/schema/p" xsi:schemaLocation
="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://
www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config /> <!-- load properties file --> <bean
id="configProperties"class
="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations"> <list>
<value>classpath:session-redis.properties</value> </list> </property> </bean>
<!-- RedisHttpSessionConfiguration --> <bean class
="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="${redis.session.timeout}"
/> <!-- session Expiration time , In seconds --> </bean> <!--LettuceConnectionFactory --> <bean
class
="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}"
/> </beans> View Code
    session-redis.properties
redis.host=192.168.0.221 redis.pass=myredis redis.port=6379
redis.session.timeout=600
     The overall project structure is as follows

    

     Deploy the code to the distributed cluster , Run again , The effect is as follows

 


     The effect is the same as that of single machine application , That means session Shared implementation , Let's see redis Is there any in session data , As shown below ,redis Middle being session Informative

    

  session colony


     One in front redis The server :192.168.0.221 Done session The server , If there is only one, it will cause a single point of failure , So the whole session The service is gone , Too much impact . To avoid a single point of failure , Need to build a session colony . When building a cluster , Do not open login authentication (requirepass Comment do not open , Specific reasons will be explained later )

    redis Cluster environment

      192.168.0.221:3 Nodes (7000,7001,7002)

      192.168.0.223:3 Nodes (7003,7004,7005)

    redis For the process of cluster construction, please refer to Redis Cluster building and simple use
<http://www.cnblogs.com/wuxl360/p/5920330.html>

    redis After each node is successfully set up , The starting conditions are as follows

    192.168.0.221

      

    192.168.0.223

      

    # ./redis-trib.rb create  --replicas  1  192.168.0.221:7000
192.168.0.221:7001  192.168.0.221:7002 192.168.0.223:7003  192.168.0.223:7004
 192.168.0.223:7005

      Whatever it is (192.168.0.221,192.168.0.223 Any one of them ) Just execute the above command , If the following information appears , It means that the cluster is built successfully

    


    redis The cluster has been set up , The next step is to redis Cluster application in our project , The code is in spring-sesson realization session Based on sharing , There are only different documents spring-session.xml and session-redis.properties

    spring-session.xml
<?xml version="1.0" encoding="UTF-8"?> <beans
xmlns="http://www.springframework.org/schema/beans" xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p
="http://www.springframework.org/schema/p" xsi:schemaLocation
="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://
www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config /> <!-- load properties file --> <bean
id="configProperties"class
="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations"> <list>
<value>classpath:session-redis.properties</value> </list> </property> </bean>
<!-- RedisHttpSessionConfiguration --> <bean class
="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="${redis.session.timeout}"
/> <!-- session Expiration time , In seconds --> </bean> <!--JedisConnectionFactory --> <beanclass
="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg> <!--redisCluster to configure --> <beanclass
="org.springframework.data.redis.connection.RedisClusterConfiguration">
<constructor-arg> <list> <value>${redis.master1}</value>
<value>${redis.master2}</value> <value>${redis.master3}</value> </list>
</constructor-arg> </bean> </constructor-arg> </bean>
<!--LettuceConnectionFactory --> <!-- Single node redis --> <!-- <bean class
="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}"
/> --> </beans> View Code
    session-redis.properties
#redis.host=192.168.0.221 #redis.pass=myredis #redis.port=6379 redis.master1
=192.168.0.221:7000 redis.master2=192.168.0.223:7003 redis.master3
=192.168.0.223:7004 redis.session.timeout=600 View Code
     According to my personal test , Effect and single node redis The effect is the same , I won't put the renderings , But you'd better go and have a test .

     Project address :spring-session <https://github.com/youzhibing/spring-session>

Attention points


  1, In stand-alone application ,HttpSession Yes Servlet Container creation and management ,servlet Once the container stops serving , that session And then it disappeared ; But if session Saved to redis in , as long as redis The service didn't stop and session During the validity period , that servlet Container is out of service ,session It still exists , What's the good about that , The advantage is servlet Flash stop and flash repair of container , Users don't have to log in again .


  2,spring In ContextLoaderListener And DispatcherServlet I don't know. Do you know , Strictly speaking, these two are responsible for loading bean There's a difference , It's also better to set it to load different bean, Or something unexpected may happen to you . If you don't know the difference, you can read it
Talking about ContextLoaderListener Its context and DispatcherServlet The difference between
<http://www.cnblogs.com/weknow619/p/6341395.html>.

  3, When testing, you can test from the bottom to the top , That is to say, test first tomcat, Retest nginx, Final test VIP.

  4,redis Can be deleted manually in session, It doesn't have to wait session be overdue .


  5, When distributed testing , It's better to index.jsp Add some marks ( for example ip, It's dead index.jsp On the server ip), Used to distinguish different servers , It's more obvious to test that way .


  6,spring-session In the example provided on the official website , Configured by annotation , But I didn't see it at all web.xml Among them spring Configuration of , But actually spring Container started , And instantiate the required bean, Applications can also run , It's hard for me to understand ,spring When was the container initialized ?
This is actually servlet3.0 New features of ,servlet3.0 Start support none web.xml Annotation configuration mode of , and AbstractHttpSessionApplicationInitializer(
AbstractHttpSessionApplicationInitializer implements WebApplicationInitializer
) It's the access point ( It's like web.xml Medium configuration spring equally ), More details need to be consulted .

  7, set up redis When clustering , If password login is set ( take redis.conf in requirepass Open and set your own password ), Then execute #
./redis-trib.rb create  --replicas  1  192.168.0.221:7000 192.168.0.221:7001
 192.168.0.221:7002 192.168.0.223:7003  192.168.0.223:7004
 192.168.0.223:7005 It will prompt [ERR] Sorry, can't connect to node 192.168.0.221:7000,
Then we need to /usr/lib/ruby/gems/1.8/gems/redis-3.3.0/lib/redis/client.rb In password Just change your password , Yes, of course ,redis All instances of must have the same password , Or all redis.conf The value of password setting in should be the same , modify
/usr/lib/ruby/gems/1.8/gems/redis-3.3.0/lib/redis/client.rb as follows
vim /usr/lib/ruby/gems/1.8/gems/redis-3.3.0/lib/redis/client.rb
take client.rb In password Change to your own redis password class Redis class Client DEFAULTS = { :url =>
lambda { ENV["REDIS_URL"] }, :scheme => "redis", :host => "127.0.0.1", :port =>
6379, :path => nil, :timeout => 5.0, :password => "myredis", # Change to your own password :db => 0,
:driver=> nil, :id => nil, :tcp_keepalive => 0, :reconnect_attempts => 1,
:inherit_socket=> false }
     As I said before , utilize redis Cluster to store session When , Do not open login authentication , because jedis I don't think so redis Cluster password settings for .

problem


  1, Not set for distributed cluster session In case of sharing , Why can't one of the set values get , According to my understanding, the data returned each time should be some tomcat On session Data in , When there are more values set , Every time there should be a value returned , And the test results are no matter how many values you set , No value returned , I don't know why .

  2,jedis Set the cluster password like this , Not yet known , Please leave a message if you know ;
Or know lettuce How to set redis Cluster and cluster password can also leave a message ; Or in any other way, you can leave a message ; Thank you !

reference resources

  spring-session one of Preliminary study spring-session
<http://feitianbenyue.iteye.com/blog/2326408>

   utilize spring session Resolve sharing Session problem
<http://blog.csdn.net/patrickyoung6625/article/details/45694157>

  【Spring】 Talking about ContextLoaderListener Its context and DispatcherServlet The difference between
<http://www.cnblogs.com/weknow619/p/6341395.html>

  Spring Session <http://projects.spring.io/spring-session/>

   explore Spring 3.1 the simplest and most common characters web.xml type Code based configuration servlet3.0 application
<http://blog.csdn.net/palmtale/article/details/8283521>

  Redis cluster tutorial <https://redis.io/topics/cluster-tutorial>

  Redis Cluster
<http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#cluster>

  Redis Cluster building and simple use <http://www.cnblogs.com/wuxl360/p/5920330.html>