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