Spring session principle and source code analysis

Posted by nincha on Mon, 17 Jan 2022 09:58:05 +0100

Technical background

Hypertext Transfer Protocol (HTTP) is a simple request response protocol, which usually runs on TCP above. It specifies what messages the client may send to the server and what response it will get

HTTP protocol is a stateless protocol. An interaction between the browser and the server is a session. After the session is completed, the session ends. The server cannot remember this person. The server does not know that it was the last person in the next conversation, so the server needs to record the user's status, You need to use a mechanism to identify specific users. This mechanism is session. When the client accesses the server, the server first obtains the session. If it does not exist, create a session and save it to the server, and return the session to the client browser as the head of the reply. When the client browser accesses next time, The server can identify the specific user through the session in the request

With the evolution of the website and the development of technology, the functions provided by the network are becoming more and more abundant. Distributed and clustered processes have gradually become the design trend When A logged in user sends A request, it may load balance to different servers, or access server B when accessing server A. the traditional session management method is tried in the single instance mode. For distributed back-end services, the system cannot determine whether the request comes from A logged in user or an unlisted user

Based on this situation, we need a new reliable cluster distributed / cluster session solution to solve this problem. At this time, we need to understand another concept, session sharing

Shared Session

, Session sharing means that when a browser accesses multiple Web services, the Session data of the server needs to be shared

1. session based replication

Synchronization between servers, regularly synchronize the session information of each server. For example, through Ehcache RMI cluster, and then through cluster synchronization, copy sessions to other servers, so that other server nodes can still find corresponding sessions from their cache after obtaining sessions from requests,

Advantages: even if one machine goes down, it will not affect its access to other nodes and pass session verification

Disadvantages: there may be a certain delay, that is, accessing another node before synchronization is completed, and the business will be terminated. When there are many servers (dozens of servers), these nodes are synchronized with each other before, which is easy to cause network storm,

2. Binding based

Generally, the hash algorithm is used to distribute the same IP request to the same server. For example, the IP hash policy of nginx, see *** Load policy in

Disadvantages: this method does not meet the requirements of high availability. If A machine goes down, such as node A, the session will be lost. Even after switching to the machine of other nodes, there will be no session

3. Database based

Advantages: the memory table Heap is used to improve the reading and writing efficiency of session operation. This scheme is more practical.

Disadvantages: the concurrent reading and writing ability of session depends on the performance of the database. At the same time, you need to implement the session elimination logic to regularly update and delete session records from the data table. When the concurrency is too high, table locks are easy to occur. Although we can choose the table engine of row level lock, we have to deny that using database to store session is still a bit of a chicken killing posture.

4. Cache based server

Save the session data to Redis and other databases, design a Filter, use HttpServletRequestWrapper to implement its own getSession() method, replace the implementation of Servlet container to create and manage HttpSession. The Servlet container loads when it starts.

Advantages: get the session directly from the cache, which is faster than query and improves the reading and writing ability

Missing thought: implement the session elimination logic by yourself and occupy memory. The larger the access level, the greater the overhead

Spring-Session

Spring session is a project under spring. It replaces the httpSession implemented by servlet container with spring session and focuses on solving the problem of session management. It can be easily, quickly and seamlessly integrated into our applications. ( https://spring.io/projects/spring-session)

The advantage of spring session is that it is out of the box and has a strong design pattern. It also supports a variety of persistence methods. RedisSession is relatively mature, which is easy to integrate with spring data redis. It also supports spring frameworks such as springboot

spring--session

Information storage

The Session status is stored on the server, and the client has only session id

Resource occupation

The server saves authentication information and occupies memory. The higher the order of magnitude, the greater the overhead,

session sharing

Database saving session solves sharing problems, such as Redis, database, MogonDB, etc

characteristic

Follow the servlet specification and obtain the session in the same way, without invading the application code

Application examples

Redis installation: https://blog.csdn.net/Beijing_L/article/details/118580237

SpringBoot+SpringSession instance: https://blog.csdn.net/Beijing_L/article/details/118580237

Technical principle

The filter SessionRepositoryFilter wraps HttpServletRequest and HttpServletResponse, and uses HttpServletRequestWrapper to implement its own getSession() method to take over the work of creating and managing Session data

  • SessionRepositoryFilter: the implementation of the Filter interface in the Servlet specification, which is used to switch from HttpSession to Spring Session, and wrap HttpServletRequest and HttpServletResponse
  • HttpServerletRequest/HttpServletResponse/HttpSessionWrapper: wrap the original HttpServletRequest, HttpServletResponse and Spring Session, which is the key to switch sessions and inherit httpsessions transparently
  • Pass request The getsession method creates a session
  • Session: Spring Session module,
  • SessionRepository: a module for managing spring sessions, which creates and saves sessions and records the expiration time of sessions
  • Storage: session s can be saved to Redis, MogonDB and other databases

The core idea of spring session is to split the session from the web container and store it in an independent storage server. At present, it supports various forms of session memory: Redis, Database, MogonDB, etc. The management responsibility of the session is entrusted to spring session. When the request enters the web container and obtains the session according to the request, the spring session is responsible for obtaining the session in the memory. If it exists, it returns. If it does not exist, it is created and persisted to the memory.

Source code analysis

Replace HttpSession

As mentioned earlier, SpringBoot will create a Bean with SessionRepositroyFilter. This Bean implements the Filter interface and replaces HttpSession through this interface. Let's see how this object is implemented first,

//1. Every request will be intercepted by the SessionRepositoryFilter interceptor
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {
       request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

      //In processing, HttpServletRequest and HttpServletResponse are wrapped into new objects
      // SessionRepositoryRequestWrapper inherits HttpServletRequestWrapper
       SessionRepositoryRequestWrapper wrappedRequest = new     SessionRepositoryRequestWrapper(request, response);
      //SessionRepositoryResponseWrapper inherits HttpServletResponseWrapper
       SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest,
         response);

       try {
          //Subsequent processing uses wrapped objects to realize replacement
          filterChain.doFilter(wrappedRequest, wrappedResponse);
       }
       finally {
          //Save the session to the database at the end of the request
          //Actually, spring session. redis. The flush mode configuration determines whether to create a session or save it here
          //This configuration will be mentioned later
          wrappedRequest.commitSession();
       }
    }

    //....
}

Take another look at the SessionRepositoryRequestWrapper object. HttpServletRequestWrapper can create and get the session in the request. SessionRepositoryRequestWrapper inherits the HttpServletRequestWrapper class and rewrites the getsession and getsession(boolean create) methods. In this way, httpsession is replaced and spring session is used to create and manage the session information in the request

public class HttpServletRequestWrapper extends ServletRequestWrapper implements
        HttpServletRequest {
    
    /**
     * The default behavior of this method is to return getSession(boolean
     * create) on the wrapped request object.
     */
    @Override
    public HttpSession getSession(boolean create) {
        return this._getHttpServletRequest().getSession(create);
    }

    /**
     * The default behavior of this method is to return getSession() on the
     * wrapped request object.
     */
    @Override
    public HttpSession getSession() {
        return this._getHttpServletRequest().getSession();
    }


}

Create and save Session

See here, there is a new problem. How does the interface SessionRepository create a session? When using request When using the getsession () method, this request is actually the SessionRepositoryRequestWrapper} object that was replaced earlier. Look at the SessionRepositoryRequestWrapper object to create a session through the getsession method

    // HttpServletRequestWrapper inherits from ServletRequestWrapper and implements the interface HttpServletRequest
    // The SessionRepositoryRequestWrapper implements the getSession method of the HttpServletRequest interface,
    private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper{
        
		private SessionRepositoryRequestWrapper(HttpServletRequest request, HttpServletResponse response) {
			super(request);
			this.response = response;
		}
        
        //Overriding the HttpServletRequestWrapper#getSession method
		@Override
		public HttpSessionWrapper getSession() {
			return getSession(true);//
		}

        //Override the HttpServletRequestWrapper# getSession(boolean create) method
        public HttpSessionWrapper getSession(boolean create) {
        //1. Try to get the session in the HttpSessionWrapper property
   HttpSessionWrapper currentSession = getCurrentSession();
       if (currentSession != null) {
          //If the current session is already pure, the current session will be returned directly
          return currentSession;
       }
       // 2. Get the session from the request
       S requestedSession = getRequestedSession();
       if (requestedSession != null) {
          if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
             requestedSession.setLastAccessedTime(Instant.now());
             this.requestedSessionIdValid = true;
             currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
             currentSession.markNotNew();
             setCurrentSession(currentSession);
             return currentSession;
          }
       }
       else {
          // This is an invalid session id. No need to ask again if
          // request.getSession is invoked for the duration of this request
          if (SESSION_LOGGER.isDebugEnabled()) {
             SESSION_LOGGER.debug(
                   "No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
          }
          setAttribute(INVALID_SESSION_ID_ATTR, "true");
       }
       if (!create) {
          return null;
       }
       if (SESSION_LOGGER.isDebugEnabled()) {
          SESSION_LOGGER.debug(
                "A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
                  + SESSION_LOGGER_NAME,
                new RuntimeException("For debugging purposes only (not an error)"));
       }

        //3. Start creating session
        //From here, you can see the session created through the sessionRepository
        //For example, the RedisSessionRepository object implements sessionRepository and generates UUID as sessionId through its dependent MapSession object
       S session = SessionRepositoryFilter.this.sessionRepository.createSession();
       session.setLastAccessedTime(Instant.now());
       currentSession = new HttpSessionWrapper(session, getServletContext());
       // Set the current session and return
       setCurrentSession(currentSession);
       return currentSession;
    }


}

From the above source code, we can see how sessionRepository$#createSession() implements session creation, and how to create it

Navigate - > type hierarchy view the class interface hierarchy and find that RedisSessionRepository implements this interface

When configuring spring session. When store type = redis, RedisSessionRepository object is used to manage the session to create and save

public class RedisSessionRepository implements SessionRepository<RedisSessionRepository.RedisSession> {
    
    //properties configuration item spring session. redis. The default configuration item will be used when the namespace is empty
    //In this way, the default key format saved by redis is the sessionId generated by spring:session:
    private static final String DEFAULT_KEY_NAMESPACE = "spring:session:";
        
    //ON_SAVE indicates that the request is saved when the commitSession in the finally method is ended (the default method is to save the session)
    //IMMEDIATE means that the sessionId is directly saved to redis after it is created
    //You can also configure spring.com through the properties configuration item session. redis. Flush mode modification
    private FlushMode flushMode = FlushMode.ON_SAVE;
    
    	@Override
	public RedisSession createSession() {

        //The MapSession object is mainly used to save the session attribute through map
        //When creating a new one, a random code will be obtained through UUID
		MapSession cached = new MapSession();
		cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);

        //Create RedisSession 
		RedisSession session = new RedisSession(cached, true);

        //If the FlushMode is configured with IMMEDIATE, call the save method to save the session information to redis
		session.flushIfRequired();

		return session;
	}

    private void save() {
        //If the sessionId changes, reman
		saveChangeSessionId();

        //Get the sessionId through getSessionKey, and then save the session information to redis
		saveDelta();
		if (this.isNew) {
			this.isNew = false;
		}
	}
    

    //The format of SessionKey is keynamespace + "sessions:" + sessionid (previously generated UUID number)
	private String getSessionKey(String sessionId) {
		return this.keyNamespace + "sessions:" + sessionId;
	}

    //...

}    

Article summary

When flushmode ON_ When saving, getSession creates a session. At the end of the request, SessionRepositoryFilter executes to save the session to redis

When flushmode During immediate, the session created by getSession is directly saved to redis

reference resources:

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

Previous: springboot - spring session example