1. An overview of the

In web containers such as Tomcat, sessions are stored in native memory. If we cluster Tomcat, session synchronization will inevitably be involved. We must ensure that tomcat sessions in the same cluster are shared. This paper realizes Session synchronization of distributed system through Spring Boot, mainly including the following contents:

  • Introduce demo implementation in detail
  • The realization principle is analyzed from the point of view of source code

2. Manage multiple TOCMAT sessions.

In web containers such as Tomcat, sessions are stored in native memory. If we cluster Tomcat, session synchronization will inevitably be involved. We must ensure that tomcat sessions in the same cluster are shared. In order for the Tomcat cluster to work properly, there are usually the following methods:

A. Configure nginx on the front end of Tomcat to use the IP_hash load balancing algorithm to ensure that visitors from the same IP address access the same back-end server. In this way, session synchronization is avoided. This method also has a flaw. If the service is down, all user state is lost. Through tomcat's own cluster mode, multiple Tomcats share session information in real time. However, with the increase of the number of Tomcats and requests, the performance of this method will be severely degraded. Using the Filter method d. Using the Terracotta server to share sessions E. Use middleware such as Redis and memcached to store sessionsCopy the code

The following demonstrates session sharing using Redis in Spring Boot

3. Use Redis in Spring Boot to share sessions

3.1. Demo Project introduction

Project name: Redis introduces dependency JARS

<! - spring is introduced into the session information is stored into the redis dependent packages - > < the dependency > < groupId > org. Springframework. Session < / groupId > <artifactId>spring-session-data-redis</artifactId> </dependency>Copy the code

Application-dev. properties Session-related configuration parameters

# # # session configuration start # # # # # # # # # # # # # session storage type configuration of spring. The session. The store - type = # redis spring. Session. Redis. Namespace = # Session duration server.session.timeout=300 ### End ############Copy the code

In sessionTestCtrl. Java, we will find that the redis shared session operation is no different from the default session operation. There are three methods of this class: login(): mock login, storing a value in the session

@RequestMapping("login") public Map<String,Object> login(HttpServletRequest request) { HttpSession httpSession = request.getSession(); // Set the value in session httpsession.setattribute ("username", "hry" + System.currentTimemillis ()); Map<String,Object> rtnMap = new HashMap<>(); Enumeration<String> attributeNames = request.getSession().getAttributeNames(); while(attributeNames.hasMoreElements()){ String name = attributeNames.nextElement(); rtnMap.put(name, httpSession.getAttribute(name)); } rtnMap.put("sessionId", httpSession.getId()); return rtnMap; }Copy the code

GetSession (): Gets the value from the session

@RequestMapping("get-session")
public Object getSession(HttpServletRequest request) {
    HttpSession httpSession = request.getSession();
    Map<String,Object> rtnMap = new HashMap<>();
    Enumeration<String> attributeNames = request.getSession().getAttributeNames();
    while(attributeNames.hasMoreElements()){
        String name = attributeNames.nextElement();
        rtnMap.put(name, httpSession.getAttribute(name));
    }
    int count;
    try {
        count = Integer.parseInt(String.valueOf(httpSession.getAttribute("count")));
        count++;
    }catch (NumberFormatException e){
        count = 1;
    }
    httpSession.setAttribute("count",count+"");

    rtnMap.put("sessionId", httpSession.getId());
    return rtnMap;
}Copy the code

Invalidate (): invalidates the sesion value

@RequestMapping("invalidate")
 public int invalidate(HttpServletRequest request) {
     HttpSession httpSession = request.getSession();
     httpSession.invalidate();
     return 1;
 }Copy the code

The full code for SessionTestCtrl is here

3.2. Test

Start RedisApplication login: http://127.0.0.1:8080/login output: {“count”:”5″,”sessionId”:”a095bdf3-e907-4fac-bf5e-132f7d737000″,”username”:”hry1517311682642″}

For the session information: http://127.0.0.1:8080/get-session output:

{"count":"5","sessionId":"a095bdf3-e907-4fac-bf5e-132f7d737000","username":"hry1517311682642"}Copy the code

Check session values in Redis: our session values are already stored in Redis

Check the cookies in the browser and the value of the session generated by us is the same, indicating that the session generated by redis replaces the default session.



After the service restarts, accesshttp://127.0.0.1:8080/get-session, the session value remains unchanged, indicating that redis saves the session

Output:

{"count":"7","sessionId":"a095bdf3-e907-4fac-bf5e-132f7d737000","username":"hry1517311682642"}Copy the code

Execute the URL using session invalidation: To perform HTTP: / / http://127.0.0.1:8080/get-session, http://127.0.0.1:8080/invalidate, and the session value has changed, and the count value, invalidate method for success

{"sessionId":"a54128d6-55c5-4233-aa97-54816b51c5cf"}  Copy the code

4. Analyze its implementation principle from the perspective of source code

In the above, we found that after using Redis shared session, its operation is no different from ordinary session operation, we can find its principle through the source code.

4.1. ExpiringSessionHttpSession

In the spring – the session – data – redis to define new HttpSession object instead of the default: ExpiringSessionHttpSession ExpiringSessionHttpSession: This class inherits HttpSession, which is actually the Session operation described above

The class ExpiringSessionHttpSession < S extends ExpiringSession > implements the HttpSession {... }Copy the code

In addition to redis, there are GemFire, Hazelcast, JDBC, Mongo,Map, etc

4.2. SessionRepository

SessionRepository: session management operation instances where management is ExpiringSessionHttpSession object



Like ExpiringSessionHttpSession, also has the GemFire, Hazelcast, JDBC, mongo, Map the corresponding class

4.3. HttpSessionWrapper

HttpSessionWrapper: to packaging when executed ExpiringSessionHttpSession ExpiringSessionHttpSession instance will be passed into the HttpSessionWrapper by constructing method

private final class HttpSessionWrapper extends ExpiringSessionHttpSession<S> { HttpSessionWrapper(S session, ServletContext servletContext) { super(session, servletContext); } @Override public void invalidate() { super.invalidate(); SessionRepositoryRequestWrapper.this.requestedSessionInvalidated = true; setCurrentSession(null); SessionRepositoryFilter.this.sessionRepository.delete(getId()); }}Copy the code

4.4. SessionRepositoryRequestWrapper

SessionRepositoryRequestWrapper encapsulates the request, through SessionRepository + HttpSessionWrapper for the whole life cycle of the session management

Public HttpSessionWrapper getSession(Boolean create) {HttpSessionWrapper currentSession = getCurrentSession(); if (currentSession ! = null) { return currentSession; } // Obtain the session ID from the requested cookie. String requestedSessionId = getRequestedSessionId(); if (requestedSessionId ! = null && getAttribute(INVALID_SESSION_ID_ATTR) == null) {// If yes, S session = getSession(requestedSessionId); if (session ! = null) { this.requestedSessionIdValid = true; HttpSession currentSession = new HttpSessionWrapper(session, getServletContext()); currentSession.setNew(false); setCurrentSession(currentSession); return currentSession; } else { setAttribute(INVALID_SESSION_ID_ATTR, "true"); } } if (! create) { return null; } // create a new sessionid. Such as using redis SessionRepository create the object S session = SessionRepositoryFilter. Enclosing SessionRepository. CreateSession (); session.setLastAccessedTime(System.currentTimeMillis()); HttpSession currentSession = new HttpSessionWrapper(session, getServletContext()); setCurrentSession(currentSession); return currentSession; } / / by CookieHttpSessionStrategy, we know that using cookies stored the sessionid here, And sent to the browser private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy (); // Get the value sessionId from cookie:  @Override public String getRequestedSessionId() { return SessionRepositoryFilter.this.httpSessionStrategy .getRequestedSessionId(this); } / / in redis SessionRepositoryFilter. Here enclosing sessionRepository RedisOperationsSessionRepository, invokes the method for the corresponding information from redsi, If there is a new object httpSession getSession private S (String sessionId) {S session = SessionRepositoryFilter. This. SessionRepository  .getSession(sessionId); if (session == null) { return null; } session.setLastAccessedTime(System.currentTimeMillis()); return session; }Copy the code

4.5. SessionRepositoryFilter

The SessionRepositoryFilter is a Fitler that intercepts all requests. Through the Filter will use our above SessionRepositoryRequestWrapper encapsulation it request

DoFilter OncePerRequestFilter Public final void doFilter(ServletRequest Request, ServletResponse Response, FilterChain FilterChain) throws ServletException, IOException {... doFilterInternal(httpRequest, httpResponse, filterChain); ... } Override protected void doFilterInternal(HttpServletRequest Request,  HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository); / / use our SessionRepositoryRequestWrapper to encapsulate request above, This involves the operation of the session is called above, we define SessionRepositoryRequestWrapper SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper( request, response, this.servletContext); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper( wrappedRequest, response); HttpServletRequest strategyRequest = this.httpSessionStrategy .wrapRequest(wrappedRequest, wrappedResponse); HttpServletResponse strategyResponse = this.httpSessionStrategy .wrapResponse(wrappedRequest, wrappedResponse); Try {// Perform filterchain. doFilter(strategyRequest, strategyResponse); } finally { wrappedRequest.commitSession(); }}Copy the code

4.6. In SpringHttpSessionConfiguration configuration classes, initialize SessionRepositoryFilter

@Configuration public class SpringHttpSessionConfiguration implements ApplicationContextAware { @Bean public <S extends ExpiringSession> SessionRepositoryFilter<? extends ExpiringSession> springSessionRepositoryFilter( SessionRepository<S> sessionRepository) { // SessionRepositoryFilter<S> SessionRepositoryFilter = new SessionRepositoryFilter<S>( sessionRepository); sessionRepositoryFilter.setServletContext(this.servletContext); ... . return sessionRepositoryFilter; }Copy the code

4.7. @enablespringHttpSession: Starts the configuration

/ / introduce SpringHttpSessionConfiguration and initialize @ Import (SpringHttpSessionConfiguration. Class) @ Configuration public @ interface EnableSpringHttpSession { }Copy the code

4.8. When spring Boot is started, EnableSpringHttpSession is enabled by default

5. The code

Please use tag V0.13 instead of master as I can’t guarantee that the master code will always be the same