浅谈Tomcat Session管理分析

前言

在上文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是一个不错的选择。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 深入浅析TomCat Session管理分析

    前言 对于广大java开发者而已,对于J2EE规范中的Session应该并不陌生,我们可以使用Session管理用户的会话信息,最常见的就是拿Session用来存放用户登录.身份.权限及状态等信息.对于使用Tomcat作为Web容器的大部分开发人员而言,Tomcat是如何实现Session标记用户和管理Session信息的呢? 概要 SESSION Tomcat内部定义了Session和HttpSession这两个会话相关的接口,其类继承体系如图1所示. 图1 Session类继承体系 图1中额

  • Tomcat集群和Session复制应用介绍

    一个配置文件: 复制代码 代码如下: <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6"> <Manager className="org.apache.catalina.ha.session.BackupManager" expireSessionsOnShutdown="false"

  • Tomcat如何监控并删除超时Session详解

    前言 偶然发现Tomcat会话时间的半小时,并不是说会话创建后,只有半小时的有效使用时间,而是说会话空闲半小时后,会被删除.索性就翻了一下源码.做了一番整理. 注:空闲时间,指的是同一个会话两次请求之间的间隔时间 Session相关类图 HttpSession就是大家Servlet层可以直接使用的Session. Session是Tomcat内部使用的接口,可以做一些内部调用 StandardSession是标准的HttpSession实现,同时它也实现了Session接口,用于Tomcat内部

  • Nginx+Tomcat关于Session的管理的实现

    前言 Nginx+Tomcat对Session的管理一直有了解,但是一直没有实际操作一遍,本文从最简单的安装启动开始,通过实例的方式循序渐进的介绍了几种管理session的方式. nginx安装配置 1.安装nginx [root@localhost ~]# yum install nginx 提示报如下错误: No package nginx available. 解决办法安装epel:EPEL是企业版 Linux 附加软件包的简称,EPEL是一个由Fedora特别兴趣小组创建.维护并管理的,

  • Tomcat中session的管理机制

    详细描述Tomcat中session的管理机制: 1. 请求过程中的session操作: 简述:在请求过程中首先要解析请求中的sessionId信息,然后将sessionId存储到request的参数列表中.然后再从 request获取session的时候,如果存在sessionId那么就根据Id从session池中获取session,如果sessionId不 存在或者session失效,那么则新建session并且将session信息放入session池,供下次使用. (1)SessionId

  • Java中tomcat memecached session 共享同步问题的解决办法

    事件缘由:一个主项目"图说美物",另外一个子功能是品牌商的入驻功能,是跟主项目分开的项目,为了共享登录的用户信息,而实现session共享,俩个tomcat,一个tomcat6,一个tomcat7 web项目windows系统下实现session的共享 第一个步: 在俩个tomcat的context.xml这个文件中配置如下代码: <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManage

  • nginx+tomcat实现负载均衡,使用redis session共享

    环境准备 1.准备一台nginx服务器 ip192.168.1.133 端口81 安装过程: #首先安装依赖: yum -y install gcc-c++ yum -y install pcre pcre-devel yum -y install zlib zlib-devel yum -y install openssl openssl-devel #注意 : 安装nginx必须使用 root 用户安装 #创建一个nginx目录 mkdir /usr/local/src/nginx #进入到

  • Tomcat中的Session与Cookie深入讲解

    前言 HTTP 是一种无状态通信协议,每个请求之间相互独立,服务器不能识别曾经来过的请求.而对于 Web 应用,它的活动都是依赖某个状态的,比如用户登录,此时使用 HTTP 就需要它在一次登录请求后,有为后续请求提供已登录信息的能力.本文首发于公众号顿悟源码. 解决办法就是使用 Cookie,它由服务器返回给浏览器,浏览器缓存并在每次请求时将 cookie 数据提交到服务器.Cookies 在请求中以明文传输,且大小限制 4KB,显然把所有状态数据保存在浏览器是不靠谱的,主流做法是: 浏览器发出

  • Tomcat中实现Session小结

    什么是Session 对Tomcat而言,Session是一块在服务器开辟的内存空间,其存储结构为ConcurrentHashMap: Session的目的 Http协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录: Session的主要目的就是为了弥补Http的无状态特性.简单的说,就是服务器可以利用session存储客户端在同一个会话期间的一些操作记录: 实现机制 先看两个问题,如下: 1.服务器如何判断客户端发送过来的请求是属于

  • Tomcat实现session共享(session 会话复制)

    一.如何保持session会话 目前,为了使web能适应大规模的访问,需要实现应用的集群部署.集群最有效的方案就是负载均衡,而实现负载均衡用户每一个请求都有可能被分配到不固定的服务器上,这样我们首先要解决session的统一来保证无论用户的请求被转发到哪个服务器上都能保证用户的正常使用,即需要实现session的共享机制. 在集群系统下实现session统一的有如下几种方案: 1.请求精确定位:session sticky,例如基于访问ip的hash策略,即当前用户的请求都集中定位到一台服务器中

随机推荐