In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-27 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >
Share
Shulou(Shulou.com)06/01 Report--
This article mainly introduces the example analysis of Session management in Tomcat, which has a certain reference value, and interested friends can refer to it. I hope you will gain a lot after reading this article.
Tomcat Manager introduction
The Session manager RedisSessionManager is configured in the context.xml of Tomcat to store session through redis. Tomcat itself provides a variety of Session managers, as shown in the following class diagram:
1.Manager interface class
Basic interfaces for managing session are defined, including methods for session operations such as createSession,findSession,add,remove, management of getMaxActive,setMaxActive,getActiveSessions active sessions, interfaces for the validity of Session, and interfaces associated with Container.
2.ManagerBase Abstract Class
It implements the Manager interface, provides basic functions, stores session with ConcurrentHashMap, provides create,find,add,remove function for session, and uses class SessionIdGenerator in createSession to generate session id as the unique identity of session.
3.ClusterManager interface class
It realizes the Manager interface, the manager of the cluster session, and the session replication function between the cluster servers built in Tomcat.
4.ClusterManagerBase Abstract Class
Inherit the ManagerBase abstract class, implement the ClusterManager interface class, and realize the basic function of session replication.
5.PersistentManagerBase Abstract Class
Inherits the ManagerBase abstract class and implements the basic functions of session manager persistence; there is an internal Store storage class, the specific implementations are: FileStore and JDBCStore
6.StandardManager class
Inherits the ManagerBase abstract class, Tomcat's default Session manager (stand-alone version); provides persistence for session. When tomcat is closed, session is saved to the SESSIONS.ser file under the javax.servlet.context.tempdir path, and session is loaded from this file when starting.
7.PersistentManager class
Inherit the PersistentManagerBase abstract class. If the session is idle for too long, convert the free session into storage, so when you findsession, you will first get the session from memory. If you don't get it, you will get it from the store. This is the difference between the PersistentManager class and the StandardManager class.
8.DeltaManager class
Inherit ClusterManagerBase, each node session changes (add, delete, modify), will notify all other nodes, all other nodes to update operation, any session in each node has a backup
9.BackupManager class
Inheriting ClusterManagerBase, the session data has only one backup node, and the location of this backup node is visible to all nodes in the cluster. Compared with DeltaManager, the data transmission volume of DeltaManager is very large when the cluster scale is relatively large.
10.RedisSessionManager class
Inherit the ManagerBase abstract class, not the built-in manager of Tomcat, use redis to store session centrally, save the session replication between nodes, rely on the reliability of redis, and have better scalability than sessin replication.
Life cycle of Session
1. Parsing to get requestedSessionId
How tomcat handles it when we pass request.getSession () in the class, you can look at the doGetSession method in Request:
Protected Session doGetSession (boolean create) {/ / There cannot be a session if no context has been assigned yet Context context = getContext (); if (context = = null) {return (null);} / / Return the current session if it exists and is valid if ((session! = null) & &! session.isValid ()) {session = null;} if (session! = null) {return (session) } / / Return the requested session if it exists and is valid Manager manager = context.getManager (); if (manager = = null) {return null; / / Sessions are not supported} if (requestedSessionId! = null) {try {session = manager.findSession (requestedSessionId);} catch (IOException e) {session = null;} if ((session! = null) & &! session.isValid ()) {session = null } if (session! = null) {session.access (); return (session);} / / Create a new session if requested and the response is not committed if (! create) {return (null);} if ((response! = null) & & context.getServletContext (). GetEffectiveSessionTrackingModes (). Contains (SessionTrackingMode.COOKIE) & & response.getResponse (). IsCommitted () {throw new IllegalStateException (sm.getString ("coyoteRequest.sessionCreateCommitted"));} / / Re-use session IDs provided by the client in very limited / / circumstances. String sessionId = getRequestedSessionId (); if (requestedSessionSSL) {/ / If the session ID has been obtained from the SSL handshake then / / use it. } else if (("/" .equals (context.getSessionCookiePath ()) & & isRequestedSessionIdFromCookie ()) {/ * This is the common (ish) use case: using the same session ID with * multiple web applications on the same host. Typically this is * used by Portlet implementations. It only works if sessions are * tracked via cookies. The cookie must have a path of "/" else it * won't be provided for requests to all web applications. * Any session ID provided by the client should be for a session * that already exists somewhere on the host. Check if the context * is configured for this to be confirmed. * / if (context.getValidateClientProvidedNewSessionId ()) {boolean found = false; for (Container container: getHost (). FindChildren ()) {Manager m = ((Context) container) .getManager (); if (m! = null) {try {if (m.findSession (sessionId)! = null) {found = true; break } catch (IOException e) {/ / Ignore. Problems with this manager will be / / handled elsewhere. } if (! found) {sessionId = null;} else {sessionId = null;} session = manager.createSession (sessionId); / / Creating a new session cookie based on that session if ((session! = null) & & (getContext ()! = null) & & getContext (). GetServletContext () GetEffectiveSessionTrackingModes () .contains (SessionTrackingMode.COOKIE) {Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie (context, session.getIdInternal (), isSecure ()); response.addSessionCookieInternal (cookie);} if (session = = null) {return null;} session.access (); return session;}
If session already exists, return it directly. If it does not exist, determine whether requestedSessionId is empty. If it is not empty, use requestedSessionId to get session in Session manager. If it is empty and you are not creating a session operation, return null; directly. Otherwise, Session manager will be called to create a new session.
As to how to obtain requestedSessionId, you can obtain it from cookie and url internally in Tomcat. For more information, please see the code of postParseRequest method of CoyoteAdapter class:
String sessionID;if (request.getServletContext (). GetEffectiveSessionTrackingModes () .broadcast (SessionTrackingMode.URL)) {/ / Get the sessionID if there was one sessionID = request.getPathParameter (SessionConfig.getSessionUriParamName (request.getContext (); if (sessionID! = null) {request.setRequestedSessionId (sessionID); request.setRequestedSessionURL (true);}} / / Look for sessionID in cookies and SSL sessionparseSessionCookiesId (req, request)
You can find that you first go to url to parse the sessionId, and if you can't get it, go to cookie to get it. If cookie is disabled by the browser, we can see that url is followed by the parameter jsessionid=xxxxxx;. Take a look at the parseSessionCookiesId method:
String sessionCookieName = SessionConfig.getSessionCookieName (context); for (int I = 0; I
< count; i++) { ServerCookie scookie = serverCookies.getCookie(i); if (scookie.getName().equals(sessionCookieName)) { // Override anything requested in the URL if (!request.isRequestedSessionIdFromCookie()) { // Accept only the first session id cookie convertMB(scookie.getValue()); request.setRequestedSessionId (scookie.getValue().toString()); request.setRequestedSessionCookie(true); request.setRequestedSessionURL(false); if (log.isDebugEnabled()) { log.debug(" Requested cookie session id is " + request.getRequestedSessionId()); } } else { if (!request.isRequestedSessionIdValid()) { // Replace the session id until one is valid convertMB(scookie.getValue()); request.setRequestedSessionId (scookie.getValue().toString()); } } }} sessionCookieName也是jsessionid,然后遍历cookie,从里面找出name=jsessionid的值赋值给request的requestedSessionId属性; 2.findSession查询session 获取到requestedSessionId之后,会通过此id去session Manager中获取session,不同的管理器获取的方式不一样,已默认的StandardManager为例: protected Map sessions = new ConcurrentHashMap(); public Session findSession(String id) throws IOException { if (id == null) { return null; } return sessions.get(id);} 3.createSession创建session 没有获取到session,指定了create=true,则创建session,已默认的StandardManager为例: public Session createSession(String sessionId) { if ((maxActiveSessions >= 0) & & (getActiveSessions () > = maxActiveSessions) {rejectedSessions++; throw new TooManyActiveSessionsException (sm.getString ("managerBase.createSession.ise"), maxActiveSessions);} / / Recycle or create a Session instance Session session = createEmptySession (); / / Initialize the properties of the new session and return it session.setNew (true); session.setValid (true); session.setCreationTime (System.currentTimeMillis ()) Session.setMaxInactiveInterval (Context) getContainer ()). GetSessionTimeout () * 60); String id = sessionId; if (id = = null) {id = generateSessionId ();} session.setId (id); sessionCounter++; SessionTiming timing = new SessionTiming (session.getCreationTime (), 0); synchronized (sessionCreationTiming) {sessionCreationTiming.add (timing); sessionCreationTiming.poll ();} return (session);}
If the passed sessionId is empty, tomcat will generate a unique sessionId. For more information, please refer to the generateSessionId method of class StandardSessionIdGenerator. It is found here that session is not put into ConcurrentHashMap after the creation of session, but is actually handled in session.setId (id). The specific code is as follows:
Public void setId (String id, boolean notify) {if ((this.id! = null) & & (manager! = null)) manager.remove (this); this.id = id; if (manager! = null) manager.add (this); if (notify) {tellNew ();}}
4. Destroy Session
Tomcat regularly detects inactive session, and then deletes it. On the one hand, session takes up memory, on the other hand, it is considered for security. When starting tomcat, a background thread is started to detect expired session. For more information, please see the inner class ContainerBackgroundProcessor of ContainerBase:
Protected class ContainerBackgroundProcessor implements Runnable {@ Override public void run () {Throwable t = null; String unexpectedDeathMessage = sm.getString ("containerBase.backgroundProcess.unexpectedThreadDeath", Thread.currentThread (). GetName ()); try {while (! threadDone) {try {Thread.sleep (backgroundProcessorDelay * 1000L) } catch (InterruptedException e) {/ / Ignore} if (! threadDone) {Container parent = (Container) getMappingObject (); ClassLoader cl = Thread.currentThread () .getContextClassLoader (); if (parent.getLoader ()! = null) {cl = parent.getLoader () .getClassLoader () } processChildren (parent, cl);} catch (RuntimeException e) {t = e; throw e;} catch (Error e) {t = e; throw e;} finally {if (! threadDone) {log.error (unexpectedDeathMessage, t) } protected void processChildren (Container container, ClassLoader cl) {try {if (container.getLoader ()! = null) {Thread.currentThread (). SetContextClassLoader (container.getLoader (). GetClassLoader ());} container.backgroundProcess ();} catch (Throwable t) {ExceptionUtils.handleThrowable (t); log.error ("Exception invoking periodic operation:", t) } finally {Thread.currentThread () .setContextClassLoader (cl);} Container [] children = container.findChildren (); for (int I = 0; I)
< children.length; i++) { if (children[i].getBackgroundProcessorDelay() 0) { return true; } if (maxInactiveInterval >0) {long timeNow = System.currentTimeMillis (); int timeIdle; if (LAST_ACCESS_AT_START) {timeIdle = (int) ((timeNow-lastAccessedTime) / 1000L);} else {timeIdle = (int) ((timeNow-thisAccessedTime) / 1000L);} if (timeIdle > = maxInactiveInterval) {expire (true);}} return this.isValid;}
Mainly by comparing whether the time from the current time to the last active time exceeds maxInactiveInterval, and if so, do expire processing.
Session Analysis of Redis centralized Management
Using tomcat-redis-session-manager to manage session above, let's take a look at how Session is managed centrally through redis; around how session is obtained, how it is created, when it is updated to redis, and when it is removed
1. How to get
RedisSessionManager overrides the findSession method
Public Session findSession (String id) throws IOException {RedisSession session = null; if (null = = id) {currentSessionIsPersisted.set (false); currentSession.set (null); currentSessionSerializationMetadata.set (null); currentSessionId.set (null);} else if (id.equals (currentSessionId.get () {session = currentSession.get ();} else {byte [] data = loadSessionDataFromRedis (id); if (data! = null) {DeserializedSessionContainer container = sessionFromSerializedData (id, data); session = container.session CurrentSession.set (session); currentSessionSerializationMetadata.set (container.metadata); currentSessionIsPersisted.set (true); currentSessionId.set (id);} else {currentSessionIsPersisted.set (false); currentSession.set (null); currentSessionSerializationMetadata.set (null); currentSessionId.set (null);}}
If sessionId is not empty, it will first compare whether sessionId is equal to sessionId in currentSessionId. If so, both session,currentSessionId and currentSession from currentSession are ThreadLocal variables. Here, data is not directly fetched from redis. If the same thread does not process other user information, it can be taken out of memory directly, thus improving performance. Finally, we get the data from redis. What we get from redis is a piece of binary data, which needs to be deserialized. The related serialization and deserialization are in the JavaSerializer class:
Public void deserializeInto (byte [] data, RedisSession session, SessionSerializationMetadata metadata) throws IOException, ClassNotFoundException {BufferedInputStream bis = new BufferedInputStream (new ByteArrayInputStream (data)); Throwable arg4 = null; try {CustomObjectInputStream x2 = new CustomObjectInputStream (bis, this.loader); Throwable arg6 = null; try {SessionSerializationMetadata x21 = (SessionSerializationMetadata) x2.readObject (); metadata.copyFieldsFrom (x21); session.readObjectData (x2);} catch (Throwable arg29) {.}
Two objects are saved in the binary data, that is, SessionSerializationMetadata and RedisSession,SessionSerializationMetadata store the attributes information in Session. In fact, RedisSession also has attributes data, which is equivalent to two copies of this data.
two。 How to create
Similarly, RedisSessionManager rewrites the createSession method, and two important points are saved in redis: the uniqueness of sessionId and session.
/ / Ensure generation of a unique session identifier.if (null! = requestedSessionId) {sessionId = sessionIdWithJvmRoute (requestedSessionId, jvmRoute); if (jedis.setnx (sessionId.getBytes (), NULL_SESSION) = = 0L) {sessionId = null;}} else {do {sessionId = sessionIdWithJvmRoute (generateSessionId (), jvmRoute);} while (jedis.setnx (sessionId.getBytes (), NULL_SESSION) = = 0L); / / 1 = key set; 0 = key already existed}
It is possible to generate the same sessionId in a distributed environment, so it is necessary to ensure uniqueness; saving session to redis is the core method, and when it is updated and when it expires, it is handled in this method.
3. When to update to redis
Look at saveInternal method concretely
Protected boolean saveInternal (Jedis jedis, Session session, boolean forceSave) throws IOException {Boolean error = true; try {log.trace ("Saving session" + session + "into Redis"); RedisSession redisSession = (RedisSession) session; if (log.isTraceEnabled ()) {log.trace ("Session Contents [" + redisSession.getId () + "]:"); Enumeration en = redisSession.getAttributeNames (); while (en.hasMoreElements ()) {log.trace ("" + en.nextElement ()) } byte [] binaryId = redisSession.getId () .getBytes (); Boolean isCurrentSessionPersisted; SessionSerializationMetadata sessionSerializationMetadata = currentSessionSerializationMetadata.get (); byte [] originalSessionAttributesHash = sessionSerializationMetadata.getSessionAttributesHash (); byte [] sessionAttributesHash = null If (forceSave | | redisSession.isDirty () | null = (isCurrentSessionPersisted = this.currentSessionIsPersisted.get ()) | |! isCurrentSessionPersisted |! Arrays.equals (originalSessionAttributesHash, (sessionAttributesHash = serializer.attributesHashFrom (redisSession) {log.trace ("Save was determined to be necessary"); if (null = = sessionAttributesHash) {sessionAttributesHash = serializer.attributesHashFrom (redisSession);} SessionSerializationMetadata updatedSerializationMetadata = new SessionSerializationMetadata (); updatedSerializationMetadata.setSessionAttributesHash (sessionAttributesHash) Jedis.set (binaryId, serializer.serializeFrom (redisSession, updatedSerializationMetadata)); redisSession.resetDirtyTracking (); currentSessionSerializationMetadata.set (updatedSerializationMetadata); currentSessionIsPersisted.set (true);} else {log.trace ("Save was determined to be unnecessary");} log.trace ("Setting expire timeout on session [" + redisSession.getId () + "] to" + getMaxInactiveInterval ()); jedis.expire (binaryId, getMaxInactiveInterval ()); error = false; return error } catch (IOException e) {log.error (e.getMessage ()); throw e;} finally {return error;}}
There are roughly 5 cases in which you need to save data to redis in the above methods, namely: forceSave,redisSession.isDirty (), null = = (isCurrentSessionPersisted = this.currentSessionIsPersisted.get ()),! isCurrentSessionPersisted and! Arrays.equals (originalSessionAttributesHash, (sessionAttributesHash = serializer.attributesHashFrom (redisSession), where one of them is true, save data to reids
3.1When you focus on forceSave, you can understand that forceSave is an identifier of the built-in save policy, which provides three built-in save policies: DEFAULT,SAVE_ON_CHANGE,ALWAYS_SAVE_AFTER_REQUEST
DEFAULT: default save policy, depending on the other four cases to save session
SAVE_ON_CHANGE: saved every time session.setAttribute () or session.removeAttribute () triggers
ALWAYS_SAVE_AFTER_REQUEST: forced save after every request request, regardless of whether a change is detected or not
3.2redisSession.isDirty () detects whether there is dirty data inside the session
Public Boolean isDirty () {return Boolean.valueOf (this.dirty.booleanValue () | |! this.changedAttributes.isEmpty ());}
Check whether there is dirty data after each request request, and save it only if there is dirty data. The real-time performance is not as high as SAVE_ON_CHANGE, but it is also not as rough as ALWAYS_SAVE_AFTER_REQUEST.
3.3.The last three cases are used to detect three ThreadLocal variables
4. When was removed
In the previous section, I introduced the built-in Tomcat to check whether the session is out of date on a regular basis. The processExpires method is provided in ManagerBase to deal with session's past problems, but this method is rewritten in RedisSessionManager.
Public void processExpires () {}
It doesn't need to be dealt with directly. Specifically, it takes advantage of redis's ability to set survival time, which is specified in the saveInternal method:
Jedis.expire (binaryId, getMaxInactiveInterval ()); Thank you for reading this article carefully. I hope the article "sample Analysis of Session Management in Tomcat" shared by the editor will be helpful to you. At the same time, I also hope you will support us and pay attention to the industry information channel. More related knowledge is waiting for you to learn!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.