关于Tomcat的session共享引起的思考

起因

由于在部署项目的时候发现,每次部署都要往tomcat的lib加 `jedis.jar,redis-session.jar,commons-pool.jar,而且只能使用tomcat7x的版本来部署。于是想了解一下怎么回事,了解一下tomcat和redis-session的源码。

关于session管理器

tomcat的session通过session管理器产生。内置的session管理器类图结构如下:

关于Tomcat的session共享引起的思考_第1张图片

可以看到,tomcat内置的有主要三种session管理器。

StandardManager :默认的session管理器,单机并且重启之后即session即丢失
PersistentManager:提供了session的持久化功能,可以实现重启tomcat之后还保持session。在内部维持了一个Store,决定将session持久化到文件还是数据库。单机。简单配置如下:
<Context docBase="ecm" path="/ecm">
	<Manager className="org.apache.catalina.session.PersistentManager" debug="0" saveOnRestart="true" maxActiveSessions="-1" minIdleSwap="-1" maxIdleSwap="5" maxIdleBackup="3" >
	    <Store className="org.apache.catalina.session.FileStore" directory="/home/sessions/"/>
	Manager>
Context>

其中Store可换为如下:

<Store calssName="org.apache.catalina.JDBCStore" driverName="com.mysql.jdbc.Driver" 
connectionURL="jdbc:mysql://localhost/session?usename=xxx&password=xxx" 
sessionTable="session" sessionIdCol="session_id" sessionDataCol="session_data" 
sessionValidCol="sessionValid" sessionMaxInactiveCol="maxInactive" 
sessionLastAccessedCol="lastAccess" sessionAppCol="app" checkInterval="60" debug="99" /> 

session表如下

create table sessions
(
    id varchar(100) not null primary key,
    valid char(1) not null,
    maxinactive int not null,
    lastaccess bigint,
    app varchar(100),
    data mediumblob
);
-- http://svn.apache.org/repos/asf/tomcat/archive/tc4.1.x/trunk/container/catalina/docs/JDBCStore-howto.html
ClusterManagerBase:支持cluster功能的session管理器,即支持session复制功能以支持多个tomcat之间同步session。

为什么说session基于cookies

看源码就很明白了,org.apache.catalina.connector.Request类下

protected Session doGetSession(boolean create) {
        ....
        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);
            }
        }
        ...
    }

manager.findSession(id)为如下。

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

其中requestedSessionId就是从cookie里获取来的。

    protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {
    	//如果禁用了cookies,则直接结束(后续步骤通过URL获取)
                Context context = (Context) request.getMappingData().context;
        if (context != null && !context.getServletContext()
                .getEffectiveSessionTrackingModes().contains(
                        SessionTrackingMode.COOKIE)) {
            return;
        }

        Cookies serverCookies = req.getCookies();
        int count = serverCookies.getCookieCount();
        if (count <= 0) {
            return;
        }

        String sessionCookieName = SessionConfig.getSessionCookieName(context);
		//默认的sessionCookieName为 "JSESSIONID"
		
        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());
                    }
                }
            }
        }

    }

可以很明显的知道session是基于cookie的,即服务器的session是根据客户端带过来的cookie来识别的。

回到正题:回到我们原来的题目上,在使用nginx等做tomcat集群时,如何实现session的共享。

  • Tomcat容器本身支持的session复制功能(web容器一般会有),如上。这个缺点是,消耗资源,复制同步session需要占用网络,并且每个tomcat都维持了很多session,配置也比较麻烦。一般适用于比较少,比较小的情况。并且 个人认为,应该开启异步复制,这样可以尽量使session复制不影响到应用,同时负载启用会话粘滞功能,防止会话丢失。
  • 实现一个session管理器。该session管理器使用关系DB如mysql,非关系DB如redis或mongo等来来统一存储和查找session信息。我们现在项目配置的redis-session-manager就是这种。主要要在findSession需要在db里查找,session创建时存储到db,当然,还有其它很多需要处理。详情可以查看 https://github.com/jcoleman/tomcat-redis-session-manager 以及https://github.com/simplicityitself/Mongo-Tomcat-Sessions (mongo),https://github.com/magro/memcached-session-manager/(memcached)。这个缺点是和特定web容器甚至版本耦合在一起,应用要换一个容器或者版本就有问题了。
  • 通过装饰或代理增强request和session,在filter进行拦截替换。使得request.getSession方法能够到mysql,redis等地方写入和读取session。猜测spring-session是这么操作的。这种方式的不依赖于特定的web容器,适应性很好。现在应该大部分使用这种方式。可以看看 https://docs.spring.io/spring-session/docs/2.1.0.M2/reference/html5/#httpsession

p.s 关于怎么调试tomcat源码,可以查看:
https://blog.csdn.net/xiongyouqiang/article/details/78941077
https://github.com/apache/tomcat/tree/7.0.94

你可能感兴趣的:(程序,JAVA)