`
gearever
  • 浏览: 147891 次
社区版块
存档分类
最新评论

tomcat架构分析 (Session管理)

阅读更多
出处:http://gearever.iteye.com

Session管理是JavaEE容器比较重要的一部分,在app中也经常会用到。在开发app时,我们只是获取一个session,然后向session中存取数据,然后再销毁session。那么如何产生session,以及session池如何维护及管理,这些并没有在app涉及到。这些工作都是由容器来完成的。
Tomcat中主要由每个context容器内的一个Manager对象来管理session。对于这个manager对象的实现,可以根据tomcat提供的接口或基类来自己定制,同时,tomcat也提供了标准实现。
在tomcat架构分析(容器类)中已经介绍过,在每个context对象,即web app都具有一个独立的manager对象。通过server.xml可以配置定制化的manager,也可以不配置。不管怎样,在生成context对象时,都会生成一个manager对象。缺省的是StandardManager类,其类路径为:
引用
org.apache.catalina.session.StandardManager

Session对象也可以定制化实现,其主要实现标准servlet的session接口:
引用
javax.servlet.http.HttpSession

Tomcat也提供了标准的session实现:
引用
org.apache.catalina.session.StandardSession

本文主要就是结合消息流程介绍这两个类的实现,及session机制。
Session方面牵涉的东西还是蛮多的,例如HA,session复制是其中重要部分等,不过本篇主要从功能方面介绍session管理,有时间再说说扩展。
Session管理主要涉及到这几个方面:

  • 创建session
  • 注销session
  • 持久化及启动加载session


创建session
在具体说明session的创建过程之前,先看一下BS访问模型吧,这样理解直观一点。

  1. browser发送Http request;
  2. tomcat内核Http11Processor会从HTTP request中解析出“jsessionid”(具体的解析过程为先从request的URL中解析,这是为了有的浏览器把cookie功能禁止后,将URL重写考虑的,如果解析不出来,再从cookie中解析相应的jsessionid),解析完后封装成一个request对象(当然还有其他的http header);
  3. servlet中获取session,其过程是根据刚才解析得到的jsessionid(如果有的话),从session池(session maps)中获取相应的session对象;这个地方有个逻辑,就是如果jsessionid为空的话(或者没有其对应的session对象,或者有session对象,但此对象已经过期超时),可以选择创建一个session,或者不创建;
  4. 如果创建新session,则将session放入session池中,同时将与其相对应的jsessionid写入cookie通过Http response header的方式发送给browser,然后重复第一步。

以上是session的获取及创建过程。在servlet中获取session,通常是调用request的getSession方法。这个方法需要传入一个boolean参数,这个参数就是实现刚才说的,当jsessionid为空或从session池中获取不到相应的session对象时,选择创建一个新的session还是不创建。
看一下核心代码逻辑;
protected Session doGetSession(boolean create) {

        ……
        // 先获取所在context的manager对象
        Manager manager = null;
        if (context != null)
            manager = context.getManager();
        if (manager == null)
            return (null);      // Sessions are not supported
        
        //这个requestedSessionId就是从Http request中解析出来的
        if (requestedSessionId != null) {
            try {
                //manager管理的session池中找相应的session对象
                session = manager.findSession(requestedSessionId);
            } catch (IOException e) {
                session = null;
            }
            //判断session是否为空及是否过期超时
            if ((session != null) && !session.isValid())
                session = null;
            if (session != null) {
                //session对象有效,记录此次访问时间
                session.access();
                return (session);
            }
        }

        // 如果参数是false,则不创建新session对象了,直接退出了
        if (!create)
            return (null);
        if ((context != null) && (response != null) &&
            context.getCookies() &&
            response.getResponse().isCommitted()) {
            throw new IllegalStateException
              (sm.getString("coyoteRequest.sessionCreateCommitted"));
        }

        // 开始创建新session对象
        if (connector.getEmptySessionPath() 
                && isRequestedSessionIdFromCookie()) {
            session = manager.createSession(getRequestedSessionId());
        } else {
            session = manager.createSession(null);
        }

        // 将新session的jsessionid写入cookie,传给browser
        if ((session != null) && (getContext() != null)
               && getContext().getCookies()) {
            Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,
                                       session.getIdInternal());
            configureSessionCookie(cookie);
            response.addCookieInternal(cookie);
        }
        //记录session最新访问时间
        if (session != null) {
            session.access();
            return (session);
        } else {
            return (null);
        }
    }


尽管不能贴出所有代码,但是上述的核心逻辑还是很清晰的。从中也可以看出,我们经常在servlet中这两种调用方式的不同;
新创建session
引用
request.getSession(); 或者request.getSession(true);

不创建session
引用
request.getSession(false);

接下来,看一下StandardManager的createSession方法,了解一下session的创建过程;
    public Session createSession(String sessionId) {
	//这是个session数量控制逻辑,超过上限则抛异常退出
        if ((maxActiveSessions >= 0) &&
            (sessions.size() >= maxActiveSessions)) {
            rejectedSessions++;
            throw new IllegalStateException
                (sm.getString("standardManager.createSession.ise"));
        }
        return (super.createSession(sessionId));
    }

这个最大支持session数量maxActiveSessions是可以配置的,先不管这个安全控制逻辑,看其主逻辑,即调用其基类的createSession方法;
public Session createSession(String sessionId) {
        
        // 创建一个新的StandardSession对象
        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(this.maxInactiveInterval);
        if (sessionId == null) {
        	//设置jsessionid
            sessionId = generateSessionId();
        }
        session.setId(sessionId);
        sessionCounter++;
        return (session);
    }

关键是jsessionid的产生过程,接着看generateSessionId方法;
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);
                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) {
                buffer.append('.').append(jvmRoute);
            }
            result = buffer.toString();
        //注意这个do…while结构
        } while (sessions.containsKey(result));
        return (result);
    }

这里主要说明的不是生成jsessionid的算法了,而是这个do…while结构。把这个逻辑抽象出来,可以看出;

如图所示,创建jsessionid的方式是由tomcat内置的加密算法算出一个随机的jsessionid,如果此jsessionid已经存在,则重新计算一个新的,直到确保现在计算的jsessionid唯一。
好了,至此一个session就这么创建了,像上面所说的,返回时是将jsessionid以HTTP response的header:“Set-cookie”发给客户端。
注销session
  • 主动注销
  • 超时注销

Session创建完之后,不会一直存在,或是主动注销,或是超时清除。即是出于安全考虑也是为了节省内存空间等。例如,常见场景:用户登出系统时,会主动触发注销操作。
主动注销
主动注销时,是调用标准的servlet接口:
引用
session.invalidate();

看一下tomcat提供的标准session实现(StandardSession)
public void invalidate() {
        if (!isValidInternal())
            throw new IllegalStateException
                (sm.getString("standardSession.invalidate.ise"));
        // 明显的注销方法
        expire();
    }

Expire方法的逻辑稍后再说,先看看超时注销,因为它们调用的是同一个expire方法。
超时注销
Tomcat定义了一个最大空闲超时时间,也就是说当session没有被操作超过这个最大空闲时间时间时,再次操作这个session,这个session就会触发expire。
这个方法封装在StandardSession中的isValid()方法内,这个方法在获取这个request请求对应的session对象时调用,可以参看上面说的创建session环节。也就是说,获取session的逻辑是,先从manager控制的session池中获取对应jsessionid的session对象,如果获取到,就再判断是否超时,如果超时,就expire这个session了。
看一下tomcat提供的标准session实现(StandardSession)
public boolean isValid() {
        ……
		//这就是判断距离上次访问是否超时的过程
        if (maxInactiveInterval >= 0) { 
            long timeNow = System.currentTimeMillis();
            int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
            if (timeIdle >= maxInactiveInterval) {
                expire(true);
            }
        }
        return (this.isValid);
    }

Expire方法
是时候来看看expire方法了。
public void expire(boolean notify) { 

        synchronized (this) {
            ......
            //设立标志位
            setValid(false);

            //计算一些统计值,例如此manager下所有session平均存活时间等
            long timeNow = System.currentTimeMillis();
            int timeAlive = (int) ((timeNow - creationTime)/1000);
            synchronized (manager) {
                if (timeAlive > manager.getSessionMaxAliveTime()) {
                    manager.setSessionMaxAliveTime(timeAlive);
                }
                int numExpired = manager.getExpiredSessions();
                numExpired++;
                manager.setExpiredSessions(numExpired);
                int average = manager.getSessionAverageAliveTime();
                average = ((average * (numExpired-1)) + timeAlive)/numExpired;
                manager.setSessionAverageAliveTime(average);
            }

            // 将此session从manager对象的session池中删除
            manager.remove(this);
            ......
        }
    }

不需要解释,已经很清晰了。
这个超时时间是可以配置的,缺省在tomcat的全局web.xml下配置,也可在各个app下的web.xml自行定义;
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>

单位是分钟。
Session持久化及启动初始化
这个功能主要是,当tomcat执行安全退出时(通过执行shutdown脚本),会将session持久化到本地文件,通常在tomcat的部署目录下有个session.ser文件。当启动tomcat时,会从这个文件读入session,并添加到manager的session池中去。
这样,当tomcat正常重启时, session没有丢失,对于用户而言,体会不到重启,不影响用户体验。
看一下概念图吧,觉得不是重要实现逻辑,代码就不说了。

总结
由此可以看出,session的管理是容器层做的事情,应用层一般不会参与session的管理,也就是说,如果在应用层获取到相应的session,已经是由tomcat提供的,因此如果过多的依赖session机制来进行一些操作,例如访问控制,安全登录等就不是十分的安全,因为如果有人能得到正在使用的jsessionid,则就可以侵入系统。
分享到:
评论
3 楼 zjhgx 2018-09-07  
  写的不错
2 楼 那家渔村 2013-06-22  
如果该系列完结以后,lz能不能做成一个mini book?
1 楼 husan_3 2013-02-23  
写的很好,请问下那个BS访问模型图是用什么软件做的?

相关推荐

    nginx+tomcat+memcached服务架构实现session共享所需jar包

    nginx+tomcat+memcached服务架构实现session共享所需jar包

    tomcat-redis-session-manager-1.2.jar

    tomcat-redis-session-manager-1.2.jar redis+tomcat session共享需要的jar包,包括tomcat6和tomcat7

    tomcat集群基于redis共享session使用到的jar包

    tomcat集群基于redis共享session使用到的所有jar包,放到tomcat的lib下即可使用 apache-tomcat-7.0.56+nginx-1.8.0+redis-3.0.6集群部署所需JAR包,session共享 tomcat-redis-session-manager1.2.jar jedis-2.6.2....

    nginx+tomcat+session+ssl_https+http

    基于window版本下,使用nginx+tomcat搭建好的集群架构,解压既可以使用。 nginx使用的是当前最新版本(1.7.0),memcached for windown版本是1.4.4,也是目前最新版本。 包含了http的集群环境,https的集群环境,同时...

    tomcat8+redis保持session共享,实现真正意义上的负载均衡

    本资源包含有nginx+tomcat+mysql主从+redis部署详细文档以及安装包,因开源组件tomcat-redis-session-manage-tomcat8.jar目前不支持tomcat8,本人亲自对该jar包对源码进行过修改可支持tomcat8 亲测有效,非常适合不...

    nginx-redis-tomcat 集群session共享方案

    nginx-redis-tomcat 集群session共享方案 包括Nginx安装,redis安装,Nginx负载均衡配置,redis+双tomcat集群session共享等方案

    tomcat-redis-session-manager-8.5-master-2.0.0-8.5.5.jar

    这种架构的主要目的是 如果我们在一个网站上登录了账号和密码,登录成功的信息存放在后端服务器A上,那么如果我们刷新后,将会登录到服务器B上 这个时候,我们需要重新登录,如果做了session共享后,登录信息会缓存...

    tomcat6/7/8 集群部署解决Session同步问题的完美解决方案

    网上很多说是支持的,其实都不行,基本上是各个依赖包之间的版本不兼容或者依赖包不全,我这个包是一个一个依赖下载的,完美同步Seesion,解决老架构下的单Web应用,利用集群支持大并发,补救方案。

    Session Cache Server 及Tomcat集群架构概念

    NULL 博文链接:https://hawthorstein.iteye.com/blog/758749

    性能调优 海量并发 系统架构

    Apache+Tomcat+Session+Memcache 高性能群集搭建 J2EE性能调优 Jboss的优化配置 Memcached分布式缓存 Nginx+Tomcat 动静分离 Nginx+tomcat集群Memcached+Session复制 高性能高并发服务器架构 基于nginx的tomcat负载...

    用Memcached 实现简单Session Cache Server

    Session Cache Server 及Tomcat集群架构之二:用Memcached 实现简单Session Cache Server

    通过 Spring Session 实现新一代的 Session 管理.docx

    在原生的云应用架构中,会假设应用能够进行扩展,这是通过在 Linux 容器中运行更多的应用程序实例实现的,这些容器会位于一个大型的虚拟机池中。例如,我们可以很容易地将一个“.war”文件部署到位于 Cloud Foundry ...

    nginx+keepalived+tomcat+redis文档

    一个稳定Web服务器架构必须实现高可用与负载均衡。这里配置Keepalived + Nginx + Tomcat + Redis的架构,其中:keepalived用于管理Virtual IP,与nginx一起...后端使用Tomcat管理web服务,并利用Redis实现session共享。

    详解SpringSession架构与设计

    开始进行Web开发时,您可能在使用...容器(例如Tomcat、Jetty)包含Session的实现,当服务器重启之后,之前的登录状态会失效需要重新登录。 我们先从HTTP协议说起。HTTP协议有个特点,是无状态的,意味着请求与请求是没

    Session Cookie的HttpOnly和secure属性

    一、属性说明: 1 secure属性 ... 2 HttpOnly属性 如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息,这样能有效的防止XSS攻击。...项目架构环境:jsp+servlet+applet

    JAVA高并发高性能高可用高扩展架构视频教程

    Java开发技术之(项目工程的日志管理) 数据库连接池原理详解 Java企业级框架之核心技术(反射) Java-Base64算法(创新_防止表单重复提交) 揭开springAOP神秘面纱之动态代理 网络爬虫之JAVA正则表达式

    尚硅谷Redis入门视频

    Redis(REmote DIctionary Server)是一个key-value存储系统,是当下互联网公司最常用的NoSQL数据库之一,是进入互联网行业的Java开发工程师必备技术。... 5 Redis集群实现Tomcat集群的Session共享等

    仿当当网mvc架构设计

    开发环境:Linux + Tomcat + MySQL + MyEclipse 项目描述:主要实现网上购物系统功能模块:用户登录 注册、商品显示、购物车、订单处理等。 具体描述:独立完成项目的各个开发模块,本系统采用了典型MVC的三层架构,...

Global site tag (gtag.js) - Google Analytics