文档章节

Tomcat Session管理分析

ksfzhaohui
 ksfzhaohui
发布于 06/06 12:37
字数 3117
阅读 1999
收藏 67
点赞 18
评论 12

前言

在上文Nginx+Tomcat关于Session的管理中简单介绍了如何使用redis来集中管理session,本文首先将介绍默认的管理器是如何管理Session的生命周期的,然后在此基础上对Redis集中式管理Session进行分析。

Tomcat Manager介绍

上文中在Tomcat的context.xml中配置了Session管理器RedisSessionManager,实现了通过redis来存储session的功能;Tomcat本身提供了多种Session管理器,如下类图:
图片描述

1.Manager接口类
定义了用来管理session的基本接口,包括:createSession,findSession,add,remove等对session操作的方法;还有getMaxActive,setMaxActive,getActiveSessions活跃会话的管理;还有Session有效期的接口;以及与Container相关联的接口;

2.ManagerBase抽象类
实现了Manager接口,提供了基本的功能,使用ConcurrentHashMap存放session,提供了对session的create,find,add,remove功能,并且在createSession中了使用类SessionIdGenerator来生成会话id,作为session的唯一标识;

3.ClusterManager接口类
实现了Manager接口,集群session的管理器,Tomcat内置的集群服务器之间的session复制功能;

4.ClusterManagerBase抽象类
继承了ManagerBase抽象类,实现ClusterManager接口类,实现session复制基本功能;

5.PersistentManagerBase抽象类
继承了ManagerBase抽象类,实现了session管理器持久化的基本功能;内部有一个Store存储类,具体实现有:FileStore和JDBCStore;

6.StandardManager类
继承ManagerBase抽象类,Tomcat默认的Session管理器(单机版);对session提供了持久化功能,tomcat关闭的时候会将session保存到javax.servlet.context.tempdir路径下的SESSIONS.ser文件中,启动的时候会从此文件中加载session;

7.PersistentManager类
继承PersistentManagerBase抽象类,如果session空闲时间过长,将空闲session转换为存储,所以在findsession时会首先从内存中获取session,获取不到会多一步到store中获取,这也是PersistentManager类和StandardManager类的区别;

8.DeltaManager类
继承ClusterManagerBase,每一个节点session发生变更(增删改),都会通知其他所有节点,其他所有节点进行更新操作,任何一个session在每个节点都有备份;

9.BackupManager类
继承ClusterManagerBase,会话数据只有一个备份节点,这个备份节点的位置集群中所有节点都可见;相比较DeltaManager数据传输量较小,当集群规模比较大时DeltaManager的数据传输量会非常大;

10.RedisSessionManager类
继承ManagerBase抽象类,非Tomcat内置的管理器,使用redis集中存储session,省去了节点之间的session复制,依赖redis的可靠性,比起sessin复制扩展性更好;

Session的生命周期

1.解析获取requestedSessionId

当我们在类中通过request.getSession()时,tomcat是如何处理的,可以查看Request中的doGetSession方法:

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;
}

如果session已经存在,则直接返回;如果不存在则判定requestedSessionId是否为空,如果不为空则通过requestedSessionId到Session manager中获取session,如果为空,并且不是创建session操作,直接返回null;否则会调用Session manager创建一个新的session;
关于requestedSessionId是如何获取的,Tomcat内部可以支持从cookie和url中获取,具体可以查看CoyoteAdapter类的postParseRequest方法部分代码:

String sessionID;
if (request.getServletContext().getEffectiveSessionTrackingModes()
        .contains(SessionTrackingMode.URL)) {
 
    // Get the session ID if there was one
    sessionID = request.getPathParameter(
            SessionConfig.getSessionUriParamName(
                    request.getContext()));
    if (sessionID != null) {
        request.setRequestedSessionId(sessionID);
        request.setRequestedSessionURL(true);
    }
}
 
// Look for session ID in cookies and SSL session
parseSessionCookiesId(req, request);

可以发现首先去url解析sessionId,如果获取不到则去cookie中获取,此处的SessionUriParamName=jsessionid;在cookie被浏览器禁用的情况下,我们可以看到url后面跟着参数jsessionid=xxxxxx;下面看一下parseSessionCookiesId方法:

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<String, Session> sessions = new ConcurrentHashMap<String, Session>();
 
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);
 
}

如果传的sessionId为空,tomcat会生成一个唯一的sessionId,具体可以参考类StandardSessionIdGenerator的generateSessionId方法;这里发现创建完session之后并没有把session放入ConcurrentHashMap中,其实在session.setId(id)中处理了,具体代码如下:

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.销毁Session

Tomcat会定期检测出不活跃的session,然后将其删除,一方面session占用内存,另一方面是安全性的考虑;启动tomcat的同时会启动一个后台线程用来检测过期的session,具体可以查看ContainerBase的内部类ContainerBackgroundProcessor:

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) {
                 processChildren(children[i], cl);
             }
         }
     }
 }

backgroundProcessorDelay默认值是10,也就是每10秒检测一次,然后调用Container的backgroundProcess方法,此方法又调用Manager里面的backgroundProcess:

public void backgroundProcess() {
    count = (count + 1) % processExpiresFrequency;
    if (count == 0)
        processExpires();
}
 
/**
 * Invalidate all sessions that have expired.
 */
public void processExpires() {
 
    long timeNow = System.currentTimeMillis();
    Session sessions[] = findSessions();
    int expireHere = 0 ;
     
    if(log.isDebugEnabled())
        log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);
    for (int i = 0; i < sessions.length; i++) {
        if (sessions[i]!=null && !sessions[i].isValid()) {
            expireHere++;
        }
    }
    long timeEnd = System.currentTimeMillis();
    if(log.isDebugEnabled())
         log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);
    processingTime += ( timeEnd - timeNow );
 
}

processExpiresFrequency默认值是6,那其实最后就是6*10=60秒执行一次processExpires,具体如何检测过期在session的isValid方法中:

public boolean isValid() {
 
    if (!this.isValid) {
        return false;
    }
 
    if (this.expiring) {
        return true;
    }
 
    if (ACTIVITY_CHECK && accessCount.get() > 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;
}

主要是通过对比当前时间到上次活跃的时间是否超过了maxInactiveInterval,如果超过了就做expire处理;

Redis集中式管理Session分析

在上文中使用tomcat-redis-session-manager来管理session,下面来分析一下是如果通过redis来集中式管理Session的;围绕session如何获取,如何创建,何时更新到redis,以及何时被移除;

1.如何获取

RedisSessionManager重写了findSession方法

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);
      }
    }

sessionId不为空的情况下,会先比较sessionId是否等于currentSessionId中的sessionId,如果等于则从currentSession中取出session,currentSessionId和currentSession都是ThreadLocal变量,这里并没有直接从redis里面取数据,如果同一线程没有去处理其他用户信息,是可以直接从内存中取出的,提高了性能;最后才从redis里面获取数据,从redis里面获取的是一段二进制数据,需要进行反序列化操作,相关序列化和反序列化都在JavaSerializer类中:

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) {
    ......
}

二进制数据中保存了2个对象,分别是SessionSerializationMetadata和RedisSession,SessionSerializationMetadata里面保存的是Session中的attributes信息,RedisSession其实也有attributes数据,相当于这份数据保存了2份;

2.如何创建

同样RedisSessionManager重写了createSession方法,2个重要的点分别:sessionId的唯一性问题和session保存到redis中;

// 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
}

分布式环境下有可能出现生成的sessionId相同的情况,所以需要确保唯一性;保存session到redis中是最核心的一个方法,何时更新,何时过期都在此方法中处理;

3.何时更新到redis

具体看saveInternal方法

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;
    }
  }

以上方法中大致有5中情况下需要保存数据到redis中,分别是:forceSave,redisSession.isDirty(),null == (isCurrentSessionPersisted = this.currentSessionIsPersisted.get()),!isCurrentSessionPersisted以及!Arrays.equals(originalSessionAttributesHash, (sessionAttributesHash = serializer.attributesHashFrom(redisSession)))其中一个为true的情况下保存数据到reids中;

3.1重点看一下forceSave,可以理解forceSave就是内置保存策略的一个标识,提供了三种内置保存策略:DEFAULT,SAVE_ON_CHANGE,ALWAYS_SAVE_AFTER_REQUEST
DEFAULT:默认保存策略,依赖其他四种情况保存session,
SAVE_ON_CHANGE:每次session.setAttribute()、session.removeAttribute()触发都会保存,
ALWAYS_SAVE_AFTER_REQUEST:每一个request请求后都强制保存,无论是否检测到变化;

3.2redisSession.isDirty()检测session内部是否有脏数据

public Boolean isDirty() {
    return Boolean.valueOf(this.dirty.booleanValue() || !this.changedAttributes.isEmpty());
}

每一个request请求后检测是否有脏数据,有脏数据才保存,实时性没有SAVE_ON_CHANGE高,但是也没有ALWAYS_SAVE_AFTER_REQUEST来的粗暴;

3.3后面三种情况都是用来检测三个ThreadLocal变量;

4.何时被移除

上一节中介绍了Tomcat内置看定期检测session是否过期,ManagerBase中提供了processExpires方法来处理session过去的问题,但是在RedisSessionManager重写了此方法

public void processExpires() {
}

直接不做处理了,具体是利用了redis的设置生存时间功能,具体在saveInternal方法中:

jedis.expire(binaryId, getMaxInactiveInterval());

总结

本文大致分析了Tomcat Session管理器,以及tomcat-redis-session-manager是如何进行session集中式管理的,但是此工具完全依赖tomcat容器,如果想完全独立于应用服务器的方案,Spring session是一个不错的选择。

Spring-Session基于Redis管理Session

© 著作权归作者所有

共有 人打赏支持
ksfzhaohui

ksfzhaohui

粉丝 301
博文 128
码字总数 158547
作品 3
南京
高级程序员
加载中

评论(12)

墨子Zhai
墨子Zhai

引用来自“墨子Zhai”的评论

坚持http无状态设计, 决不使用servlet session机制, 简化服务器架构, 要像百度,阿里,QQ学习, 坚持采用胖cookie模式。

引用来自“ksfzhaohui”的评论

没了解过这种方式,如果禁用了cookie怎么解决
自已分析一下大网站的请求吧, 比如登一下淘宝,QQ。 他们都在尽可能的使用客户端cookie, 来存用户状态信息, 而不是server端的session.
ksfzhaohui
ksfzhaohui

引用来自“墨子Zhai”的评论

坚持http无状态设计, 决不使用servlet session机制, 简化服务器架构, 要像百度,阿里,QQ学习, 坚持采用胖cookie模式。
没了解过这种方式,如果禁用了cookie怎么解决
墨子Zhai
墨子Zhai

引用来自“墨子Zhai”的评论

坚持http无状态设计, 决不使用servlet session机制, 简化服务器架构, 要像百度,阿里,QQ学习, 坚持采用胖cookie模式。
换句话说, 使用servlet session模式, 有些low了。
墨子Zhai
墨子Zhai
坚持http无状态设计, 决不使用servlet session机制, 简化服务器架构, 要像百度,阿里,QQ学习, 坚持采用胖cookie模式。
ksfzhaohui
ksfzhaohui

引用来自“java思维导图”的评论

上一篇文章,tomcat的session共享,tomcat8.5好像用不了?报找不到包的错误

对,官方的文档里面介绍的确实不支持Tomcat8及以上,GitHub上我看到其他项目基于此做了一些修改可以支持8及以上,或者使用spring session也是一个方案
java思维导图
java思维导图
上一篇文章,tomcat的session共享,tomcat8.5好像用不了?报找不到包的错误
ksfzhaohui
ksfzhaohui

引用来自“开源中国首席睡觉专家”的评论

怎么把jssid改成叫assid?
assid?
ksfzhaohui
ksfzhaohui

引用来自“夜辰”的评论

Spring Session这个确实不错。我之前用的那版本对JSP支持不太好。因为jsp默认第一行是request.getSession(true),如果没有登录则会报错了。
是的,后面准备看看spring seesion
ksfzhaohui
ksfzhaohui

引用来自“robortly”的评论

好东西,有没有对IIS的管理分析的呢?
IIS没用过
robortly
robortly
好东西,有没有对IIS的管理分析的呢?
Tomcat7.0源码分析——Session管理分析(上)

前言   对于广大java开发者而言,就J2EE规范中的Session应该并不陌生,我们可以使用Session管理用户的会话信息,最常见的就是拿Session来存放用户登录、身份、权限及状态等信息。对于使用T...

beliefer
2016/09/23
0
0
Tomcat7.0源码分析——Session管理分析(下)

前言   在《Tomcat7.0源码分析——Session管理分析(上)》一文中我介绍了Session、Session管理器,还以StandardManager为例介绍了Session管理器的初始化与启动,本文将接着介绍Session管理...

beliefer
2016/09/30
0
0
Memcached高可用方案收集(集群及分布式)

Memcached的集群方案有很多,不止magent一个,但是单靠集群软件去实现高可用感觉还是会缺少一步,最推荐的方案应该是软件加编码去实现高可用,至少能保证站点的99.5%的可运行行,以下是集群的...

easonjim
2017/09/23
0
0
Tomcat架构分析blog

http://gearever.iteye.com/ 开了个tomcat架构分析的blog。目前总结了一些tomcat整体架构,消息流机制,session管理等方面的文章,后续会有JNDI,性能分析,扩展等方面的总结,希望能成为一个...

gearever
2012/06/05
340
0
Tomcat中Session对象部分属性值丢失问题的分析与解决

Tomcat中Session对象部分属性值丢失问题的分析与解决 éŠŹć–‡ĺťşçš„ĺšĺŽ˘2017-11-0522 阅读 JavaSESSION互联网Tomcat 我们最近的一个 Java 项目在从开发环境迁移到测试环境...

éŠŹć–‡ĺťşçš„ĺšĺŽ˘
2017/11/05
0
0
求助一个session丢失的问题(集群后已经做了memcache的session管理)

本人近期做了一个apache + tomcat + memcached 的集群部署,之前存在session共享的问题,后来通过memcache来管理session,解决了session复制的问题,但是最近还是会发现有session丢失的情况,...

smh821025
2013/08/26
1K
4
Spring-Session基于Redis管理Session

前言 在上文Tomcat Session管理分析介绍了使用tomcat-redis-session-manager来集中式管理session,其中一个局限性就是必须使用tomcat容器;本文介绍的spring-session也能实现session的集中式...

ksfzhaohui
06/29
0
0
Nginx+Tomcat关于Session的管理

前言 Nginx+Tomcat对Session的管理一直有了解,但是一直没有实际操作一遍,本文从最简单的安装启动开始,通过实例的方式循序渐进的介绍了几种管理session的方式。 nginx安装配置 1.安装nginx...

ksfzhaohui
05/31
0
0
分布式Session集中存储方案

在文章分布式web架构中Session管理的方法中讲了分布式Session管理的4种办法,其中第4种的Session集中存储实际运用比较广泛,下面来讲解这种的实现方案。 目前常用的Session集中管理方案有两种...

刘诗书
2017/11/23
0
0
基于Redis的Session共享示例

在单机情况下,Session可由部署在服务器上的Web容器来管理 (如Tomcat、JBoss)。 在负载均衡的集群环境下,负载均衡可能将请求分发到不同的服务器上去,在这种情况,需要将有状态的session统一...

王孟君
2016/12/22
5.2K
26

没有更多内容

加载失败,请刷新页面

加载更多

下一页

MyBatis源码解读之延迟加载

1. 目的 本文主要解读MyBatis 延迟加载实现 2. 延迟加载如何使用 Setting 参数配置 设置参数 描述 有效值 默认值 lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加...

无忌
2分钟前
0
0
javascript 类变量的实现

代码如下: function echo(){ for(let i=0;i<arguments.length;i++) console.log(arguments[i]);}function extend(o, p){for (prop in p) {o[prop] = p[prop]}retur......

backbye
6分钟前
0
1
编程语言对比分析:Python与Java和JavaScript(图)

编程语言对比分析:Python与Java和JavaScript(图): 凭什么说“Python 太慢,Java 太笨拙,我讨厌 JavaScript”?[图] 编程语言生而为何? 我们人类从原始社会就是用语言表达自己,互相沟通...

原创小博客
15分钟前
0
0
Akka构建Reactive应用《one》

看到这Akka的官网,描述使用java或者scala构建响应式,并发和分布式应用更加简单,听着很高级的样子,下面的小字写着消息驱动,但是在quickstart里面又写容错事件驱动,就是这么钻牛角尖。 ...

woshixin
26分钟前
0
0
ffmpeg源码分析 (四)

io_open 承接上一篇,对于avformat_open_input的分析还差其中非常重要的一步,就是io_open,该函数用于打开FFmpeg的输入输出文件。 在init_input中有这么一句 if ((ret = s->io_open(s, &s-...

街角的小丑
28分钟前
0
0
String,StringBuffer ,StringBuilder的区别

不同点 一、基类不同 StringBuffer、StringBuilder 都继承自AbStractStringBuilder,String 直接继承自 Object 2、底层容器“不同” 虽然底层都是字符数组,但是String的是final修饰的不可变...

不开心的时候不要学习
43分钟前
0
0
nodejs 文件操作

写文件code // 加载文件模块var fs = require("fs");var content = 'Hello World, 你好世界!';//params 文件名,内容,编码,回调fs.writeFile('./hello.txt',content,'utf8',function (er......

yanhl
45分钟前
0
0
SpringBoot mybits 查询为0条数据 但是在Navicat 中可以查询到数据

1.页面请求: 数据库查询: 2018-07-16 17:56:25.054 DEBUG 17312 --- [nio-9010-exec-3] c.s.h.m.C.selectSelective : ==> Preparing: select id, card_number, customer_id, customer_nam......

kuchawyz
55分钟前
0
0
译:Self-Modifying cod 和cacheflush

date: 2014-11-26 09:53 翻译自: http://community.arm.com/groups/processors/blog/2010/02/17/caches-and-self-modifying-code Cache处在CPU核心与内存存储器之间,它给我们的感觉是,它具......

我叫半桶水
58分钟前
0
0
Artificial Intelligence Yourself

TensorFlow是谷歌基于DistBelief进行研发的第二代人工智能学习系统,其命名来源于本身的运行原理。Tensor(张量)意味着N维数组,Flow(流)意味着基于数据流图的计算,TensorFlow为张量从流...

孟飞阳
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部