集群实现session共享

集群实现session共享

简介

集群实现session共享目前主流共有以下几种方式,分别是 ip_hash基于数据库的Session共享,基于缓存的Session共享,基于cookie的Session共享,以下是各个方式的优缺点。

23179ebff58f46c7679ad1154927e45a4e1.jpg

基于缓存的session共享
 
由于目前缓存插件中redis相对高效,成熟、稳定,支持分布式,同时掌上医讯目前就以此作为缓存插件,所以此次
缓存的选型是redis,确切的说,此次的分享就是 基于redis的session共享,总共分两种方案,一种是基于容器实现session共享,一种基于应用实现session共享,分别是《 tomcat-redis-session》、《spring session》,前者是依托tomcat容易实现的集群session共享,后者依据web应用代码层面实现的session共享,下面进入正题一一讲解。
 

tomcat-redis-session

认识tomcat 

上世纪八十年代,当互联网开始在美国大学流行的时候,美国计算机名校伊利诺伊大学香槟分校(UIUC)的国家超级计算应用中心(National Center for Supercomputing Applications, NCSA)组织了一些研究生开始编写基于HTTP通信协议的服务器端和客户端程序。客户端端程序叫做mosaic,是第一个被普遍使用的网页浏览器,也是Netscape(网景)浏览器的前身,之后演变为Mozilla Firefox。而服务器端程序就是最早的Web服务器软件之一,名叫NCSA HTTPd,它完整地实现了HTTP协议,整个实验获得了成功。然而伊利诺伊大学香槟分校也许仅出于学术研究目的,在实验成功后开发工作就没有继续下去,研究小组也随之解散,但他们将这两个软件开源,其代码可以自由下载修改并发布。
 
此时的互联网对HTTP服务器软件的需求越来越大,公开源代码的NCSA HTTPd成了进一步发展的极好起点。很多研究者不断地给它添加功能、增加代码,并对不断出现的Bug打补丁。但因为缺乏规划和管理,出现了越来越多的重复劳动,随之而来的则是越多的补丁带来越多的Bug。1995年2月,为解决这种单打独斗的现象,8名志同道合的开发者决定成立一个小组,一起重写整个NCSA HTTPd程序,发布一个基于NCSA HTTPd的可靠的服务器软件。开发工作完成后,他们将软件命名为Apache,全称Apache HTTP Server。Apache本是美洲原住民印第安人一支部落的名字,这个部落因为高超的作战策略和无穷的耐性而闻名,同时也是最后一个屈服于美国政府的民族。开发小组以此寓意软件高效、可靠,同时表达了大公司迟早会参与竞争并“教化”这块最早的开源网络之地的担心。另外,因为整个软件是在NCSA HTTPd基础上打了很多补丁程序,他们也戏称它是“A Patchy Web Server”,意为一个打了很多补丁的Web服务器软件。“A Patchy”与Apache谐音,故以Apache命名一语双关。
 
Apache HTTP Server发布后,由于其具有坚实的稳定性、异常丰富的功能和灵活的可扩展性,得到了极大的成功。1999年6月,为有效支持Apache HTTP Server以及相关软件的发展,Apache开发小组成员们成立了一个非盈利性的Apache软件基金会(Apache Software Foundation)。大家对Apache这个名字的熟悉大概也是因为这个基金会,它支持开发了诸多享誉全球的开源软件,这些软件的名字前都会加上Apache,其中就包括Apache Tomcat。
 

tomcat的这个单词的意思是“公猫”,因为它的开发者姆斯·邓肯·戴维森希望用一种能够自己照顾自己的动物代表这个软件,于是命名为tomcat,它的Logo兼吉祥物也被设计成了一只公猫形象。Tomcat是1999年Apache 软件基金会与Sun等其他公司一起合作的Jakarta(雅加达)项目中的一个子项目,作为服务器的容器支持基于Java语言编写的程序在服务器上运行,这样的程序被称为Servlet,因为它是运行在“Server”上的“Applet(Applet是采用Java编程语言编写的小应用程序)”。理论上讲这样一个容器并不是一个完整的服务器软件,因为它只能运行Java程序而不能生成HTML页面数据,也不能处理并发事务。但它集成了HTTP服务器程序,也就可以单独作为一个服务器软件来部署以处理HTTP请求,但tomcat核心技术并不在于此,所以除了用于开发过程中的调试以及那些对速度和事务处理只有很小要求的用户,很少会将Tomcat单独作为Web服务器。通常开发者会让tomcat与其他对Web服务器一起协同工作,比如Apache HTTP Server。Apache HTTP Server负责接受所有来自客户端的HTTP请求,然后将Servlets和JSP的请求转发给Tomcat来处理。Tomcat完成处理后,将响应传回给Apache,最后Apache将响应返回给客户端。于是在tomcat中运行Java程序也就是Servlet的那个模块因为体现了tomcat最核心特点而引起了大家的重视,而这个模块的名字叫做Catalina。

Catalina是美国西海岸靠近洛杉矶22英里的一个小岛,因为其风景秀丽而著名。Servlet运行模块的最早开发者Craig McClanahan因为喜欢Catalina岛故以Catalina命名他所开这个模块,尽管他从来也没有去过那里。另外在开发的早期阶段,Tomcat是被搭建在一个叫Avalon的服务器框架上,而Avalon则是Catalina岛上的一个小镇的名字,于是想一个与小镇名字相关联的单词也是自然而然。还有一个原因来自于Craig McClanahan养的猫,他养的猫在他写程序的时候喜欢在电脑周围闲逛。但这与Catalina有什么关系呢?我想可能是Catalina岛是个悠闲散步的好地方,猫的闲逛让Craig McClanahan想起了那里。

tomcat 系统架构

一、Tomcat顶层架构

先上一张Tomcat的顶层结构图(图A),如下:

d57fa062aaf8e295a64531e1ba346b758dc.jpg

 

 

tomcat中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server可以包含至少一个Service,用于具体提供服务。

Service主要包含两个部分:Connector和Container。从上图中可以看出 Tomcat 的心脏就是这两个组件,他们的作用如下:

1、Connector用于处理连接相关的事情,并提供Socket与Request和Response相关的转化; 2、Container用于封装和管理Servlet,以及具体处理Request请求;

一个Tomcat中只有一个Server,一个Server可以包含多个Service,一个Service只有一个Container,但是可以有多个Connectors,这是因为一个服务可以有多个连接,如同时提供Http和Https链接,也可以提供向相同协议不同端口的连接,示意图如下(Engine、Host、Context下边会说到):

73315293ae9b62aec06397687698c298f40.jpg

 

多个 Connector 和一个 Container 就形成了一个 Service,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非 Server 莫属了!所以整个 Tomcat 的生命周期由 Server 控制。

 

另外,上述的包含关系或者说是父子关系,都可以在tomcat的conf目录下的server.xml配置文件中看出,下图是删除了注释内容之后的一个完整的server.xml配置文件(Tomcat版本为8.0)

feb090d870acd3388d0c45cb7148b47ca31.jpg
 

 

 

详细的配置文件文件内容可以到Tomcat官网查看:http://tomcat.apache.org/tomcat-8.0-doc/index.html

上边的配置文件,还可以通过下边的一张结构图更清楚的理解:

b020ffaa36daa880d92e315953b48787f8d.jpg

 

 

二、Tomcat顶层架构小结:

(1)Tomcat中只有一个Server,一个Server可以有多个Service,一个Service可以有多个Connector和一个Container; 
(2) Server掌管着整个Tomcat的生死大权; 
(4)Service 是对外提供服务的; 
(5)Connector用于接受请求并将请求封装成Request和Response来具体处理; 
(6)Container用于封装和管理Servlet,以及具体处理request请求;

知道了整个Tomcat顶层的分层架构和各个组件之间的关系以及作用,对于绝大多数的开发人员来说Server和Service对我们来说确实很远,而我们开发中绝大部分进行配置的内容是属于Connector和Container的,所以接下来介绍一下Connector和Container。

三、Connector和Container的微妙关系

由上述内容我们大致可以知道一个请求发送到Tomcat之后,首先经过Service然后会交给我们的Connector,Connector用于接收请求并将接收的请求封装为Request和Response来具体处理,Request和Response封装完之后再交由Container进行处理,Container处理完请求之后再返回给Connector,最后在由Connector通过Socket将处理的结果返回给客户端,这样整个请求的就处理完了!

Connector最底层使用的是Socket来进行连接的,Request和Response是按照HTTP协议来封装的,所以Connector同时需要实现TCP/IP协议和HTTP协议!

Tomcat既然处理请求,那么肯定需要先接收到这个请求,接收请求这个东西我们首先就需要看一下Connector!

四、Connector架构分析

Connector用于接受请求并将请求封装成Request和Response,然后交给Container进行处理,Container处理完之后在交给Connector返回给客户端。

因此,我们可以把Connector分为四个方面进行理解:

(1)Connector如何接受请求的? 

(2)如何将请求封装成Request和Response的? 

(3)封装完之后的Request和Response如何交给Container进行处理的? 

(4)Container处理完之后如何交给Connector并返回给客户端的?
 

首先看一下Connector的结构图(图B),如下所示:

34c49d4efe55b881bf19565f097c038732c.jpg

 Connector就是使用ProtocolHandler来处理请求的,不同的ProtocolHandler代表不同的连接类型,比如:Http11Protocol使用的是普通Socket来连接的,Http11NioProtocol使用的是NioSocket来连接的。

 

其中ProtocolHandler由包含了三个部件:Endpoint、Processor、Adapter。

(1)Endpoint用来处理底层Socket的网络连接,Processor用于将Endpoint接收到的Socket封装成Request,Adapter用于将Request交给Container进行具体的处理。

(2)Endpoint由于是处理底层的Socket网络连接,因此Endpoint是用来实现TCP/IP协议的,而Processor用来实现HTTP协议的,Adapter将请求适配到Servlet容器进行具体的处理

(3)Endpoint的抽象实现AbstractEndpoint里面定义的Acceptor和AsyncTimeout两个内部类和一个Handler接口。Acceptor用于监听请求,AsyncTimeout用于检查异步Request的超时,Handler用于处理接收到的Socket在内部调用Processor进行处理。

至此,我们应该很轻松的回答(1)(2)(3)的问题了,但是(4)还是不知道,那么我们就来看一下Container是如何进行处理的以及处理完之后是如何将处理完的结果返回给Connector的?

五、Container架构分析

Container用于封装和管理Servlet,以及具体处理Request请求,在Connector内部包含了4个子容器,结构图如下(图C):

91e14cf66ba0a39e615bbcaa209e765afcf.jpg

 4个子容器的作用分别是:

 

(1)Engine:引擎,用来管理多个站点,一个Service最多只能有一个Engine; 

(2)Host:代表一个站点,也可以叫虚拟主机,通过配置Host就可以添加站点; 

(3)Context:代表一个应用程序,对应着平时开发的一套程序,或者一个WEB-INF目录以及下面的web.xml文件; 

(4)Wrapper:每一Wrapper封装着一个Servlet;

六、Container如何处理请求的

 

Container处理请求是使用Pipeline-Value管道来处理的!

Pipeline-Value是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,,处理完之后每个处理者负责做自己相应的处理将处理后的请求返回,再让下一个处理着继续处理。

我们知道Container包含四个子容器,而这四个子容器对应的BaseValue分别在:StandardEngineValue、StandardHostValue、StandardContextValue、StandardWrapperValue。

 

Pipeline的处理流程图如下(图D):

cd408056734b884a0b3f226b702e2f8db7e.jpg

  (1)Connector在接收到请求后会首先调用最顶层容器的Pipeline来处理,这里的最顶层容器的Pipeline就是EnginePipeline(Engine的管道);

(2)在Engine的管道中依次会执行EngineValue1、EngineValue2等等,最后会执行StandardEngineValue,在StandardEngineValue中会调用Host管道,然后再依次执行Host的HostValue1、HostValue2等,最后在执行StandardHostValue,然后再依次调用Context的管道和Wrapper的管道,最后执行到StandardWrapperValue。

(3)当执行到StandardWrapperValue的时候,会在StandardWrapperValue中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法,这样请求就得到了处理!

(4)当所有的Pipeline-Value都执行完之后,并且处理完了具体的请求,这个时候就可以将返回的结果交给Connector了,Connector在通过Socket的方式将结果返回给客户端

 

tomcat-redis-session使用

1、开源项目地址:

 

https://github.com/jcoleman/tomcat-redis-session-manager

 

2、下载代码之后需要进行重新编译,生成所需要的jar,任意创建maven项目,将src下的代码拷贝到具体位置,如下:

d7be0a752a23e8c57b6747c331f7f5841f6.jpg

 

 

 

 

3、然后打开terminal,执行mvn clean 和mvn install 将编译好的代码打包为:tomcat-redis-session-1.0-SNAPSHOT.jar

 

4、将**tomcat-redis-session-1.0-SNAPSHOT.jar、jedis-2.7.2.jar、commons-pool2-2.0.jar** 三个jar包分别放在tomcat1和tomcat2实例下的lib目录下。

 

 

5、修改tomcat实例下conf/contex.xml文件

 

 

<?xml version='1.0' encoding='utf-8'?>  <Context><WatchedResource>WEB-INF/web.xml</WatchedResource><!-- tomcat-redis-session共享配置 --><Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /><Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"host="192.168.1.149"port="6379"database="0"maxInactiveInterval="60" />  </Context> 

 

tomcat-redis-session原理

一、概要原理

tomcat-redis-session-manager是一个对用户完全透明的分布式session存储框架,用户只需要在tomcat中进行简单的配置,就可以使用,我们的业务代码是完全和单实例的时候的代码是一样的的,也就是写代码的时候完全不用担心你写的是一个多tomcat实例的代码,完全透明。

 

 

如何对框架的原理进行简单的理解,我们首先要知道,在请求过程中的session操作,首先要解析请求中的sessionId信息,然后将sessionId存储到request的参数列表中。然后再从 request获取session的时候,如果存在sessionId那么就根据Id从session池中获取session,如果sessionId不存在或者session失效,那么则新建session并且将session信息放入session池,供下次使用。

 

如果我们想自己写一个类似于tomcat-redis-session-manager的项目,我们应该知道Tomcat的Session管理机制,在默认的情况下Tomcat的Session管理,如果不进行设置的话是由Tomcat自带的StandardManager类进行控制的,我们可以根据这个类自定义一个Manager,**主要重写的就是org.apache.catalina.session.ManagerBase里边的具体写的操作**,这也是tomcat-redis-session-manager的基本原理,将tomcat的session存储位置指向了Redis

a00ac415d374afa12a3e2aaf714d5afcc8c.jpg

 
 
RedisSessionManager继承了org.apache.catalina.session.ManagerBase并重写了add、findSession、createEmptySession、remove等方法,并将对session的增删改查操作指向了对Redis数据存储的操作有兴趣可参考一篇Tomcat中session的管理机制:

二、详细原理

本文目的在介绍tomcat中session相关的架构以及session的查询。

在Servlet开发中,Session代表用户会话,开发人员经常使用Session来临时存储一些信息,那么Session到底是什么,Tomcat中是如何对Session进行管理的,我们今天到源码中查看下。

查看相关资料,我们先看下Session相关的类图

658e727185cedd40d0b746a34a334e6f1ee.jpg

从图上可以看到Session对应的接口有两个SessionHttpSessionStandardSession实现了这2个接口,StandardSessionFacade实现了HttpSessionStandardSessionFacede包含了StandardSession。看起来有点饶,我们详细讲解下每个类的含义。

  • HttpSession

我们都知道Tomcat是支持Servlet规范的web容器,所以内部会包含一些Servlet的内容。HttpSession就是在Servlet规范中标准的Session定义。注意HttpSession的全路径是javax.servlet.http.HttpSession

  • Session

Session接口是Catalina内部的Session的标准定义。全路径org.apache.catalina.Session

  • StandardSession

Catalina内部Session接口的标准实现,基础功能的提供类,需要重点分析,全路径org.apache.catalina.session.StandardSession

  • StandardSessionFacade

StandardSession的外观类,也是Catalina内部的类,之所以存在这个类是因为不想直接让开发人员操作到StandardSession,这样可以保证类的安全。该类的全路径org.apache.catalina.session.StandardSession

在Catalina中,Session由Session管理器组件负责管理,例如创建和销毁Session管理器是org.apache.catalina.Manager接口的实例。我们来看看Manager管理器相关的类图。

f2bb00445d920549a8a3215084ca903266e.jpg

Manager

Catalina中session管理器概念的顶层接口。其中定义了一些对session的基本操作,如创建,查找,添加,移除以及对特定session对象的属性的修改。除了基本操作以外还定义了一些特殊的例如session的序列化反序列化等等。

ManagerBase

抽象类,对Manager接口作了基本实现,方便继承类的复写以及实现,就像其他xxxBase类起到了一样的作用。

StandardManager

Catalina中默认的Session管理器的实现类,具体功能我们下面分析。

PersistentManagerBase

Session管理器中打标持久化Session的父类,虽然StandardManager也可以将Session持久化,但是只是将Session持久化为一个文件(后面会说到),PersistentManagerBase类和StandardManager类的区别在于前者的存储器的表现形式可以有多种,比如数据库,文件等(具体后面讨论)

PersistentManager

PersistentManager基础上增加了两个属性。

DistributedManager

PersistentManagerBaseBackupManager类中抽象出来的一个接口,这个接口表示了两者一个共同的属性:不会持有所有session的对象,但是能找到所有的session。例如PersistentManagerBase可以将session同时存放在内存和持久化介质中。

ClusterManager

分布式集群session处理器父类,定义了一些基本方法例如获取所有tomcat集群的机器,获取其他集群的信息等基本功能。

ClusterManagerBase

抽象类,对ClusterManager作了基本实现。

BackupManager

集群间session复制策略的一种实现,会话数据只有一个备份节点,这个备份节点的位置集群中所有节点都可见。

DeltaManager

集群建session复制策略的一种实现,采用的方式是只复制差异部分,是分布式集群session同步中最好的同步方式。

了解一些基本信息后,我们来查看我们最常用的getSession()方法,从而来了解session运作的方式。

在Servlet中我们使用HttpServletRequestgetSession()方法来获取session对象,而真正执行getSession方法的其实是org.apache.catalina.connector.RequestFacade对象

public class RequestFacade implements HttpServletRequest

RequestFacade对象实现了HttpServletRequest内部封装了org.apache.catalina.connector.Request对象,我们查看getSession()方法:

 @Override
public HttpSession getSession() {
    if (request == null) {
        throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
    }
    return getSession(true);
}

@Override
public HttpSession getSession(boolean create) {
    if (request == null) {
        throw new IllegalStateException(sm.getString("requestFacade.nullRequest"));
    }

    if (SecurityUtil.isPackageProtectionEnabled()){
        return AccessController.doPrivileged(new GetSessionPrivilegedAction(create));
    } else {
        return request.getSession(create);
    }
}

可以看出最终调用的还是org.apache.catalina.connector.Request对象的getSession()方法,源码如下:

    @Override
public HttpSession getSession(boolean create) {
    Session session = doGetSession(create);
    if (session == null) {
        return null;
    }

    return session.getSession();
}

下面我们看doGetSession()方法,由于源码过长,删减了部分内容,保留了核心代码。

protected Session doGetSession(boolean create) {
    //判断context
    if (context == null) {
        return (null);
    }
    //判断session是否有效
    if ((session != null) && !session.isValid()) {
        session = null;
    }
    if (session != null) {
        return (session);
    }
    //获取跟context绑定的sessionManager  这里默认返回的是StandardManager
    Manager manager = context.getManager();
    if (manager == null) {
        return null;        // Sessions are not supported
    }
    if (requestedSessionId != null) {
        try {
            //根据requestSessionId 查询指定的session
            //111111111111
            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);
        }
    }
    
    //如果没有找到已存在的session并且 要求创建新session
    //获取此次request对应的sessionid
    String sessionId = getRequestedSessionId();
    //。。。略部分代码
    //222222222222
    session = manager.createSession(sessionId);

    //创建cookie
    Cookie cookie =ApplicationSessionCookieConfig.createSessionCookie(context, session.getIdInternal(),isSecure());
    //将cookie设置到response 中
    response.addSessionCookieInternal(cookie);
    
    if (session == null) {
        return null;
    }
    //增加访问时间和次数
    session.access();
    return session;
}

代码很简单,思路也很清晰,很容易理解,这里有3处需要关注下。

在代码1的地方,调用了manager.findSession(),requestedSessionId是个String类型的字符串(稍后说如何创建一个这样的字符串)。在Catalina中默认的是StandardManager,所以查看StandardManagerfindSession()方法,最后在其父类ManagerBase中找到相关方法:

    @Override
public Session findSession(String id) throws IOException {
    if (id == null) {
        return null;
    }
    return sessions.get(id);
}

 /**
 * The set of currently active Sessions for this Manager, keyed by
 * session identifier.
 */
protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();

从源码可以得出结论,一个类型的session管理器,他都有一个总的session容器,就是一个ConcurrentHashMap实例,key是sessionId,value是对应的session对象。

在代码2的地方由于在已有的session的map中没有找到session,所以需要创建一个新的session,createSession()源码如下(在ManagerBase类中):

 @Override
public Session createSession(String sessionId) {
    //判断可容纳session数量
    if ((maxActiveSessions >= 0) &&
            (getActiveSessions() >= maxActiveSessions)) {
        rejectedSessions++;
        throw new TooManyActiveSessionsException(
                sm.getString("managerBase.createSession.ise"),
                maxActiveSessions);
    }
    //创建一个全新的session对象
    Session session = createEmptySession();

    //设置基本属性
    session.setNew(true);
    session.setValid(true);
    session.setCreationTime(System.currentTimeMillis());
    session.setMaxInactiveInterval(((Context) getContainer()).getSessionTimeout() * 60);
    String id = sessionId;
    //设置sessionId 唯一标识负
    if (id == null) {
        //如果id为空 新生成一个sessionId
        id = generateSessionId();
    }
    session.setId(id);
    //session数量+1
    sessionCounter++;
    
    SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
    synchronized (sessionCreationTiming) {
        sessionCreationTiming.add(timing);
        sessionCreationTiming.poll();
    }
    return (session);

}

需要关注下创建新的session对象其实就是new 了一个StandardSession对象,还有generateSessionId()方法是生成一个唯一的sessionId,有兴趣的可以自行查看。最后在代码session.setId(id)中往下查看可以看到:

sessions.put(session.getIdInternal(), session);

也就是把新创建的session放入到管理器的session容器中(ConcurrentHashMap)对象。

doGetSession方法中最后需要关注的就是requestedSessionId是如何生成的,我们先看下定义

    /**
 * The requested session ID (if any) for this request.
 */
protected String requestedSessionId = null;

可以看出是个字符串类型的标识符并且和request有关,由于tomcat的请求流程我们还没梳理到,所以这里直接给出生成的方法。

首先,这个所谓的requestedSessionId对应的就是我们每次请求都有一个唯一session标识符,为了以后同一个用户再次请求的时候可以使用同一个session(会话)。既然跟request有关,那么就可以查看request相关方法,而之前在讲解启动的时候提到过Connector是专门来处理请求的,而对应http请求的ConnectorHttp11Processor。首先用户发送一个http请求传递给Http11Processor,经由Http11Processor解析封装在 org.apache.coyote.Request然后传递给CoyoteAdaptercoyoteAdapter是一个适配器,将coyote框 架封装的org.apache.coyote.Request适配给org.apache.catalina.connector.Request,而解析的过程就在CoyoteAdapter中。

解析requestSessionId分为两步,一个是从请求url中解析,另一步是从cookie中解析,之所以需要解析两次是因为有的用户会禁用cookie,而禁用cookie的时候sessionId则会带在请求url中(其实就是我们熟知的jsessionId)

从请求url中解析(只保留了核心代码)

首先如果sessionId保留在url中是以如下形式

http://xxx.com?a=1&b=1&c=1;k1=v1;k2=v2;jsessionId=xxxx

可以看出requestSessionId是保存在;之后的,之所以解释下 是方便理解。

代码比较简单,以上就是requestSessionId是如何设置的。

看到这里,doGetSession()方法我们就看完了,可以看到它返回的是一个StandardSession对象,最后在getSession()方法中返回的是StandardSession对象的getSession()方法,查看下源码。

  @Override
public HttpSession getSession() {
    Session session = doGetSession(true);
    if (session == null) {
        return null;
    }

    return session.getSession();
}

 /**
 * Return the <code>HttpSession</code> for which this object
 * is the facade.
 */
@Override
public HttpSession getSession() {
    if (facade == null){
        if (SecurityUtil.isPackageProtectionEnabled()){
            final StandardSession fsession = this;
            facade = AccessController.doPrivileged(
                    new PrivilegedAction<StandardSessionFacade>(){
                @Override
                public StandardSessionFacade run(){
                    return new StandardSessionFacade(fsession);
                }
            });
        } else {
            facade = new StandardSessionFacade(this);
        }
    }
    return (facade);

}

可以看出最后getSession()方法返回的并不是StandardSession对象,而是StandardSession对象的外观类StandardSessionFacade。这也和我们一开始介绍session类图的时候描述的一致。

 

 

spring session

spring session使用

Spring Session提供了一套创建和管理Servlet HttpSession的方案。Spring Session提供了集群Session(Clustered Sessions)功能,默认采用外置的Redis来存储Session数据,以此来解决Session共享的问题。

一、特性

Spring Session提供以下特性:

  1. API和用于管理用户会话的实现;

  2. HttpSession - 允许以应用程序容器(即Tomcat)中性的方式替换HttpSession; 

    1. Clustered Sessions - Spring Session让支持集群会话变得不那么繁琐,并且不和应用程序容器金习性绑定到。

    2. Multiple Browser Sessions - Spring会话支持在单个浏览器实例中管理多个用户的会话。

    3. RESTful APIs - Spring Session允许在headers 中提供会话ID以使用RESTful API。

二、基于XML配置方式的Spring Session案例实现

基于SSM框架的一个小案例,Git OS项目代码地址:http://git.oschina.net/xuliugen/spring-session-demo

项目展示:

f26195ebf51115e3f6668ef27ff8b3b1576.jpg

 

 

(1)基本环境需求

进行使用Spring Session的话,首先的是已经安装好的有一个 Redis服务器!

(2)添加项目依赖(最基本的依赖使用)

b2e65b29e8ab028667c23af61984bf08112.jpg

 

 (3)添加Spring配置文件

 

添加了必要的依赖之后,我们需要创建相应的Spring配置。Spring配置是要创建一个Servlet过滤器,它用Spring Session支持的HttpSession实现来替换容器本身HttpSession实现。这一步也是Spring Session的核心。

36cbd692aa4b4205e2c670650a8adc9dc20.jpg
  LettuceConnectionFactory实例是配置Redis的ConnectionFactory。
注意:
1c0083dbdb025a0e63182713af450034716.jpg
 
 (4)查看源代码可以看到,默认的Redis链接配置为:

08da3b62f98720561648c6e000d97d4b694.jpg

 因此,如果有自己的Redis配置,请修改,例如下边的配置:

68999b5487707fc28f192b3d9e1d1dc0696.jpg
 (5)关于Error creating bean with name ‘enableRedisKeyspaceNotificationsInitializer’错误的处理:

添加如下配置让Spring Session不再执行config命令:

456f4306e4865096495d0926a2a879c61ff.jpg

1105dac8d1cdc063b6d03020ab4527170cf.jpg

 (5)在web.xml中添加DelegatingFilterProxy
e953142ce766218b41ff8a4663772f513f0.jpg
 DelegatingFilterProxy将通过springSessionRepositoryFilter的名称查找Bean并将其转换为过滤器。对于调用DelegatingFilterProxy的每个请求,也将调用springSessionRepositoryFilter。

 

(6)Spring MVC controller代码用于测试:

5b191e7326cb21ac9b5bbba30f2c1f37e47.jpg

 (7)测试

访问链接:http://localhost:8080/spring/session/setSession.do?name=test&value=123456

 

 

spring session原理

我们知道Tomcat再启动的时候首先会去加载web.xml 文件,Tomcat启动的时候web.xml被加载的顺序:context-param -> listener -> filter -> servlet

我们在使用Spring Session的时候,我们配置了一个filter,配置代码如下:

bb755aa70a7caa577df13579d80ff0f2d14.jpg

 介绍一下 DelegatingFilterProxy 这个类:

 

DelegatingFilterProxy 类将通过springSessionRepositoryFilter 这个名称去查找Spring容器中配置的Bean并将其转换为过滤器,对于调用DelegatingFilterProxy的每个请求,将调用springSessionRepositoryFilter这个过滤器。

如果未指定init-param参数的话,DelegatingFilterProxy就会把filter-name作为要查找的Bean对象,这也是DelegatingFilterProxy类的作用。可以看出每一个请求都会经过该filter,经过该filter的请求也会相应的经过springSessionRepositoryFilter这个过滤器,那么我们就接着看一下springSessionRepositoryFilter这个过滤器。

springSessionRepositoryFilter过滤器的创建

 

07a9316e6ca620376d9cfb6c75ebb0f98f6.jpg
 们在Spring的配置文件中手动注入了RedisHttpSessionConfiguration,这是因为我们默认的使用Redis进行存储Session的。

 

RedisHttpSessionConfiguration 这个类加了Configuration注解,作为配置文件注入。

RedisHttpSessionConfiguration的作用是创建名为springSessionRepositoryFilter 的Spring Bean,继承自Filter。springSessionRepositoryFilter替换容器默认的HttpSession支持为Spring Session,将Session实例存放在Redis中。

(1)RedisHttpSessionConfiguration 继承关系如下:

09d4bb9d595292f27b86c48051f1f37b63c.jpg

 

 

(3)RedisHttpSessionConfiguration通过@Bean的方式将RedisMessageListenerContainer、RedisTemplate、RedisOperationsSessionRepository 等注入到Spring容器中。

(4)RedisHttpSessionConfiguration继承了SpringHttpSessionConfiguration这个类,这个类很重要,SpringHttpSessionConfiguration通过@Bean的方式将springSessionRepositoryFilter注入到容器中:

b6f31be4d4941b426a6d7a480d5d9d51d2f.jpg

 

 

pringSessionRepositoryFilter这个过滤器就是前边DelegatingFilterProxy查找的过滤器!

(6)可以看出他是SessionRepositoryFilter类型的,SessionRepositoryFilter的作用就是替换容器默认的javax.servlet.http.HttpSession支持为org.springframework.session.Session。

SessionRepositoryFilter的主要方法和属性如下:

8f366e8147a1d362215b1e80a65783673aa.jpg
(7)其中SessionRepositoryResponseWrapper、SessionRepositoryRequestWrapper、HttpSessionWrapper为内部类,这个也是很关键的。例如SessionRepositoryRequestWrapper类:
283c0983e3fed1930d96c59c10a3399ff64.jpg
 可以看出SessionRepositoryRequestWrapper继承了javax.servlet.http.HttpServletRequestWrapper这个类,我们知道HttpServletRequest接口的默认实现是有HttpServletRequestWrapper的,如下:
 

 

(8)因为SessionRepositoryRequestWrapper继承了HttpServletRequestWrapper,而HttpServletRequestWrapper实现了HttpServletRequest接口,在SessionRepositoryRequestWrapper又重写了HttpServletRequest接口中的一些方法,所以才会有:getSession、changeSessionId等这些方法。

到此,我们应该大致明白了,原有的request请求和response都被重新进行了包装。我们也就明白了原有的HttpSeesion是如何被Spring Session替换掉的。

 

 

 

 
 

 

tomcat-redis-session与 spring session 比较

 
 
名称优点缺点总结

tomcat-redis-session

目前掌上医讯使用的都是tomcat容器,修改起来比较方便配置相对还是有一点繁琐的,需要人为的去修改Tomcat的配置,需要耦合Tomcat等Servlet容器的代码结合,掌上医讯现有项目和环境来说,tomcat-redis-session,实现起来比较容易,也比较方便,
但从长远角度考虑,使用spring session比较好,优点比较突出,但是需要调研,jfinal项目与之是否存在jar冲突的可能性。

spring session

spring Session不依赖于Servlet容器,而是Web应用代码层面的实现,直接在已有项目基础上加入spring Session框架来实现Session统一存储在Redis中。如果你的Web应用是基于Spring框架开发的,只需要对现有项目进行少量配置,即可将一个单机版的Web应用改为一个分布式应用,由于不基于Servlet容器,所以可以随意将项目移植到其他容器。对于基于spring项目,配置起来比较方便,因为该方案是基于spring实现的,而对于jfinal项目jar包是否存在冲突还有待于进一步调研。
 
 

转载于:https://my.oschina.net/u/2269430/blog/3017530

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值