Manager 是tomcat 用于管理session的操作的定义,ManagerBase 设置默认参数,实现一些公用方法
StandardManager 是真正tomcat用来处理session的管理器,它实现了lifecycle接口,具体的关系如下
其中clusterManager是定义集群环境下session管理的操作,clusterManagerBase继承了ManagerBase,实现了ClusterManager,是真正集群环境中使用的session管理器
Manager中的操作:
每个managerbase都会与一个context绑定,初始化init方法
if( initialized ) return; initialized=true; log = LogFactory.getLog(ManagerBase.class); if( oname==null ) { try { StandardContext ctx=(StandardContext)this.getContainer(); Engine eng=(Engine)ctx.getParent().getParent(); domain=ctx.getEngineName(); distributable = ctx.getDistributable(); StandardHost hst=(StandardHost)ctx.getParent(); String path = ctx.getPath(); if (path.equals("")) { path = "/"; } oname=new ObjectName(domain + ":type=Manager,path=" + path + ",host=" + hst.getName()); Registry.getRegistry(null, null).registerComponent(this, oname, null ); } catch (Exception e) { log.error("Error registering ",e); } } // Initialize random number generation getRandomBytes(new byte[16]); // Ensure caches for timing stats are the right size by filling with // nulls. while (sessionCreationTiming.size() < TIMING_STATS_CACHE_SIZE) { sessionCreationTiming.add(null); } while (sessionExpirationTiming.size() < TIMING_STATS_CACHE_SIZE) { sessionExpirationTiming.add(null); } if(log.isDebugEnabled()) log.debug("Registering " + oname );
ManagerBase 中定义了用于生成session的random 值的方法(值得参考)
(entropy[i]) << ((i % 8) * 8)
public Random getRandom() { if (this.random == null) { // Calculate the new random number generator seed long seed = System.currentTimeMillis(); long t1 = seed; char entropy[] = getEntropy().toCharArray(); for (int i = 0; i < entropy.length; i++) { //这里提供了一个 char 字符 变成long的方法, entropy[i]) << ((i % 8) * 8 long update = ((long) entropy[i]) << ((i % 8) * 8); seed ^= update; } try { // Construct and seed a new random number generator Class clazz = Class.forName(randomClass); this.random = (Random) clazz.newInstance(); this.random.setSeed(seed); } catch (Exception e) { // Fall back to the simple case log.error(sm.getString("managerBase.random", randomClass), e); this.random = new java.util.Random(); this.random.setSeed(seed); } if(log.isDebugEnabled()) { long t2=System.currentTimeMillis(); if( (t2-t1) > 100 ) log.debug(sm.getString("managerBase.seeding", randomClass) + " " + (t2-t1)); } } return (this.random);
调用得到32bit 随机字符的方法
注意,这里调用了org.apache.tomcat.jni.OS的静态方法public static native int random(byte [] buf, int len);来得到随机数组,native关键字是指tomcat在这里调用了外部的非java方法来实现这个调用
public String getEntropy() { // Calculate a semi-useful value if this has not been set if (this.entropy == null) { // Use APR to get a crypto secure entropy value byte[] result = new byte[32]; boolean apr = false; try { String methodName = "random"; Class paramTypes[] = new Class[2]; paramTypes[0] = result.getClass(); paramTypes[1] = int.class; Object paramValues[] = new Object[2]; paramValues[0] = result; paramValues[1] = new Integer(32); Method method = Class.forName("org.apache.tomcat.jni.OS") .getMethod(methodName, paramTypes); method.invoke(null, paramValues); apr = true; } catch (Throwable t) { // Ignore } if (apr) { try { setEntropy(new String(result, "ISO-8859-1")); } catch (UnsupportedEncodingException ux) { // ISO-8859-1 should always be supported throw new Error(ux); } } else { setEntropy(this.toString()); } } return (this.entropy); }
sessionID的生成方法
protected synchronized String generateSessionId() { byte random[] = new byte[16]; String jvmRoute = getJvmRoute(); String result = null; // Render the result as a String of hexadecimal digits StringBuffer buffer = new StringBuffer(); do { int resultLenBytes = 0; if (result != null) { buffer = new StringBuffer(); duplicates++; } while (resultLenBytes < this.sessionIdLength) { //生成随机数组 getRandomBytes(random); //对数组进行加密,默认MD5 random = getDigest().digest(random); for (int j = 0; j < random.length && resultLenBytes < this.sessionIdLength; j++) { //涉及到数学,不是很懂为啥?? byte b1 = (byte) ((random[j] & 0xf0) >> 4); byte b2 = (byte) (random[j] & 0x0f); if (b1 < 10) buffer.append((char) ('0' + b1)); else buffer.append((char) ('A' + (b1 - 10))); if (b2 < 10) buffer.append((char) ('0' + b2)); else buffer.append((char) ('A' + (b2 - 10))); resultLenBytes++; } } if (jvmRoute != null) { //其中jvmRoute是为了防止tomcat集群导致的sessionId冲突 buffer.append('.').append(jvmRoute); } result = buffer.toString(); } while (sessions.containsKey(result)); return (result);
StandardManager 生命周期:
start()
if( ! initialized ) //managerbase.init init(); // Validate and update our current component state if (started) { return; } lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; // Force initialization of the random number generator if (log.isDebugEnabled()) log.debug("Force random number initialization starting"); String dummy = generateSessionId(); if (log.isDebugEnabled()) log.debug("Force random number initialization completed"); // Load unloaded sessions, if any try { load(); } catch (Throwable t) { log.error(sm.getString("standardManager.managerLoad"), t); }
load
if (SecurityUtil.isPackageProtectionEnabled()){ try{ AccessController.doPrivileged( new PrivilegedDoLoad() ); } catch (PrivilegedActionException ex){ Exception exception = ex.getException(); if (exception instanceof ClassNotFoundException){ throw (ClassNotFoundException)exception; } else if (exception instanceof IOException){ throw (IOException)exception; } if (log.isDebugEnabled()) log.debug("Unreported exception in load() " + exception); } } else { doLoad(); }
doload方法
if (log.isDebugEnabled()) log.debug("Start: Loading persisted sessions"); // Initialize our internal data structures sessions.clear(); // Open an input stream to the specified pathname, if any //根据pathname得到 session文件 File file = file(); if (file == null) return; if (log.isDebugEnabled()) log.debug(sm.getString("standardManager.loading", pathname)); FileInputStream fis = null; ObjectInputStream ois = null; Loader loader = null; ClassLoader classLoader = null; try { fis = new FileInputStream(file.getAbsolutePath()); BufferedInputStream bis = new BufferedInputStream(fis); if (container != null) loader = container.getLoader(); if (loader != null) classLoader = loader.getClassLoader(); if (classLoader != null) { if (log.isDebugEnabled()) log.debug("Creating custom object input stream for class loader "); ois = new CustomObjectInputStream(bis, classLoader); } else { if (log.isDebugEnabled()) log.debug("Creating standard object input stream"); ois = new ObjectInputStream(bis); } } catch (FileNotFoundException e) { if (log.isDebugEnabled()) log.debug("No persisted data file found"); return; } catch (IOException e) { log.error(sm.getString("standardManager.loading.ioe", e), e); if (ois != null) { try { ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } // Load the previously unloaded active sessions synchronized (sessions) { try { Integer count = (Integer) ois.readObject(); int n = count.intValue(); if (log.isDebugEnabled()) log.debug("Loading " + n + " persisted sessions"); for (int i = 0; i < n; i++) { //初始化session StandardSession session = getNewSession(); session.readObjectData(ois); session.setManager(this); sessions.put(session.getIdInternal(), session); session.activate(); if (!session.isValidInternal()) { // If session is already invalid, // expire session to prevent memory leak. session.setValid(true); session.expire(); } sessionCounter++; } } catch (ClassNotFoundException e) { log.error(sm.getString("standardManager.loading.cnfe", e), e); if (ois != null) { try { ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } catch (IOException e) { log.error(sm.getString("standardManager.loading.ioe", e), e); if (ois != null) { try { ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } finally { // Close the input stream try { if (ois != null) ois.close(); } catch (IOException f) { // ignored } // Delete the persistent storage file if (file != null && file.exists() ) file.delete(); } } if (log.isDebugEnabled()) log.debug("Finish: Loading persisted sessions");
stop 方法
先unload,对session 钝化 发出事件通知,写入文件
然后session过期
最后销毁
// Write out sessions try { unload(); } catch (Throwable t) { log.error(sm.getString("standardManager.managerUnload"), t); } // Expire all active sessions Session sessions[] = findSessions(); for (int i = 0; i < sessions.length; i++) { Session session = sessions[i]; try { if (session.isValid()) { session.expire(); } } catch (Throwable t) { ; } finally { // Measure against memory leaking if references to the session // object are kept in a shared field somewhere session.recycle(); } } // Require a new random number generator if we are restarted this.random = null; if( initialized ) { destroy(); }
dounload(),
if (log.isDebugEnabled()) log.debug(sm.getString("standardManager.unloading.debug")); if (sessions.isEmpty()) { log.debug(sm.getString("standardManager.unloading.nosessions")); return; // nothing to do } // Open an output stream to the specified pathname, if any File file = file(); if (file == null) return; if (log.isDebugEnabled()) log.debug(sm.getString("standardManager.unloading", pathname)); FileOutputStream fos = null; ObjectOutputStream oos = null; try { fos = new FileOutputStream(file.getAbsolutePath()); oos = new ObjectOutputStream(new BufferedOutputStream(fos)); } catch (IOException e) { log.error(sm.getString("standardManager.unloading.ioe", e), e); if (oos != null) { try { oos.close(); } catch (IOException f) { ; } oos = null; } throw e; } // Write the number of active sessions, followed by the details ArrayList list = new ArrayList(); synchronized (sessions) { if (log.isDebugEnabled()) log.debug("Unloading " + sessions.size() + " sessions"); try { oos.writeObject(new Integer(sessions.size())); Iterator elements = sessions.values().iterator(); while (elements.hasNext()) { StandardSession session = (StandardSession) elements.next(); list.add(session); ((StandardSession) session).passivate(); session.writeObjectData(oos); } } catch (IOException e) { log.error(sm.getString("standardManager.unloading.ioe", e), e); if (oos != null) { try { oos.close(); } catch (IOException f) { ; } oos = null; } throw e; } } // Flush and close the output stream try { oos.flush(); oos.close(); oos = null; } catch (IOException e) { if (oos != null) { try { oos.close(); } catch (IOException f) { ; } oos = null; } throw e; } // Expire all the sessions we just wrote if (log.isDebugEnabled()) log.debug("Expiring " + list.size() + " persisted sessions"); Iterator expires = list.iterator(); while (expires.hasNext()) { StandardSession session = (StandardSession) expires.next(); try { session.expire(false); } catch (Throwable t) { ; } finally { session.recycle(); } } if (log.isDebugEnabled()) log.debug("Unloading complete");
destory()
if( oname != null ) Registry.getRegistry(null, null).unregisterComponent(oname); if (randomIS!=null) { try { randomIS.close(); } catch (IOException ioe) { log.warn("Failed to close randomIS."); } randomIS=null; } initialized=false; oname = null;
ClusterManger 接口方法
DeltaManager 继承了 ClusterManagerBase 方法,是集群的session管理器
创建session
public Session createSession(String sessionId, boolean distribute) { if ((maxActiveSessions >= 0) && (sessions.size() >= maxActiveSessions)) { rejectedSessions++; throw new TooManyActiveSessionsException( sm.getString("deltaManager.createSession.ise"), maxActiveSessions); } DeltaSession session = (DeltaSession) super.createSession(sessionId) ; if (distribute) { sendCreateSession(session.getId(), session); } if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.createSession.newSession",session.getId(), new Integer(sessions.size()))); return (session); }
发送消息
protected void sendCreateSession(String sessionId, DeltaSession session) { if(cluster.getMembers().length > 0 ) { SessionMessage msg = new SessionMessageImpl(getName(), SessionMessage.EVT_SESSION_CREATED, null, sessionId, sessionId + "-" + System.currentTimeMillis()); if (log.isDebugEnabled()) log.debug(sm.getString("deltaManager.sendMessage.newSession",name, sessionId)); msg.setTimestamp(session.getCreationTime()); counterSend_EVT_SESSION_CREATED++; send(msg); } }
protected void send(SessionMessage msg) { if(cluster != null) { if(doDomainReplication()) cluster.sendClusterDomain(msg); else cluster.send(msg); } }
sessionID 序列化操作
protected byte[] serializeSessionId(String sessionId) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeUTF(sessionId); oos.flush(); oos.close(); return bos.toByteArray(); } /** * Load sessionID * @throws IOException if an input/output error occurs */ protected String deserializeSessionId(byte[] data) throws IOException { ReplicationStream ois = getReplicationStream(data); String sessionId = ois.readUTF(); ois.close(); return sessionId; }
变更sessionID
public void changeSessionId(Session session, boolean notify) { // original sessionID String orgSessionID = session.getId(); super.changeSessionId(session);
public void changeSessionId(Session session) { String oldId = session.getIdInternal(); session.setId(generateSessionId(), false); String newId = session.getIdInternal(); if (container instanceof ContainerBase) { ((ContainerBase)container).fireContainerEvent( Context.CHANGE_SESSION_ID_EVENT, new String[] {oldId, newId}); } }
if (notify) { // changed sessionID String newSessionID = session.getId(); try { // serialize sessionID byte[] data = serializeSessionId(newSessionID); // notify change sessionID SessionMessage msg = new SessionMessageImpl(getName(), SessionMessage.EVT_CHANGE_SESSION_ID, data, orgSessionID, orgSessionID + "-" + System.currentTimeMillis()); msg.setTimestamp(System.currentTimeMillis()); counterSend_EVT_CHANGE_SESSION_ID++; send(msg); } catch (IOException e) { log.error(sm.getString("deltaManager.unableSerializeSessionID", newSessionID), e); } } }
start方法
if (!initialized) init(); // Validate and update our current component state if (started) { return; } started = true; lifecycle.fireLifecycleEvent(START_EVENT, null); // Force initialization of the random number generator generateSessionId(); // Load unloaded sessions, if any try { //the channel is already running Cluster cluster = getCluster() ; // stop remove cluster binding //wow, how many nested levels of if statements can we have ;) if(cluster == null) { Container context = getContainer() ; if(context != null && context instanceof Context) { //集群环境下 必须有Host 或者 Engine 容器 Container host = context.getParent() ; if(host != null && host instanceof Host) { cluster = host.getCluster(); if(cluster != null && cluster instanceof CatalinaCluster) { setCluster((CatalinaCluster) cluster) ; } else { Container engine = host.getParent() ; if(engine != null && engine instanceof Engine) { cluster = engine.getCluster(); if(cluster != null && cluster instanceof CatalinaCluster) { setCluster((CatalinaCluster) cluster) ; } } else { cluster = null ; } } } } } if (cluster == null) { log.error(sm.getString("deltaManager.noCluster", getName())); return; } else { if (log.isInfoEnabled()) { String type = "unknown" ; if( cluster.getContainer() instanceof Host){ type = "Host" ; } else if( cluster.getContainer() instanceof Engine){ type = "Engine" ; } log.info(sm.getString("deltaManager.registerCluster", getName(), type, cluster.getClusterName())); } } if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.startClustering", getName())); //to survice context reloads, as only a stop/start is called, not // createManager //在cluster中注册此session管理器 cluster.registerManager(this); //得到其他context的session getAllClusterSessions(); } catch (Throwable t) { log.error(sm.getString("deltaManager.managerLoad"), t); }
getAllClusterSessions
if (cluster != null && cluster.getMembers().length > 0) { long beforeSendTime = System.currentTimeMillis(); Member mbr = findSessionMasterMember(); if(mbr == null) { // No domain member found return; } SessionMessage msg = new SessionMessageImpl(this.getName(),SessionMessage.EVT_GET_ALL_SESSIONS, null, "GET-ALL","GET-ALL-" + getName()); // set reference time stateTransferCreateSendTime = beforeSendTime ; // request session state counterSend_EVT_GET_ALL_SESSIONS++; stateTransfered = false ; // FIXME This send call block the deploy thread, when sender waitForAck is enabled try { synchronized(receivedMessageQueue) { receiverQueue = true ; } cluster.send(msg, mbr); if (log.isWarnEnabled()) log.warn(sm.getString("deltaManager.waitForSessionState",getName(), mbr,getStateTransferTimeout())); // FIXME At sender ack mode this method check only the state transfer and resend is a problem! waitForSendAllSessions(beforeSendTime); } finally { synchronized(receivedMessageQueue) { for (Iterator iter = receivedMessageQueue.iterator(); iter.hasNext();) { SessionMessage smsg = (SessionMessage) iter.next(); if (!stateTimestampDrop) { messageReceived(smsg, smsg.getAddress() != null ? (Member) smsg.getAddress() : null); } else { if (smsg.getEventType() != SessionMessage.EVT_GET_ALL_SESSIONS && smsg.getTimestamp() >= stateTransferCreateSendTime) { // FIXME handle EVT_GET_ALL_SESSIONS later messageReceived(smsg,smsg.getAddress() != null ? (Member) smsg.getAddress() : null); } else { if (log.isWarnEnabled()) { log.warn(sm.getString("deltaManager.dropMessage",getName(), smsg.getEventTypeString(),new Date(stateTransferCreateSendTime), new Date(smsg.getTimestamp()))); } } } } receivedMessageQueue.clear(); receiverQueue = false ; } } } else { if (log.isInfoEnabled()) log.info(sm.getString("deltaManager.noMembers", getName())); }
整个集群环境下的session管理,可以总结为,集群环境下的context都使用DeltaManager作为管理器,管理DeltaSession, 同时不同cluster之间使用 DeltaRequest 封装 SessionMessage来传递消息
然后ClusterSessionListener 通过在 ChannelListener中的messageReceived 事件来 触发DeltaManager中的messageReceived事件
具体见图: