分布式cookie-session的实现(spring-session)

分布式cookie-session的实现(spring-session)

本文使用的spring-session版本为 1.0.0,地址为: https://github.com/spring-projects/spring-session

1     session存储策略

存储,即在后台使用session的setAttribute,getAttribute等方法时,这些内部存放的数据最终存储至什么位置。比如在默认的tomcat实现中,相应的数据即存储在内存中,并在停止之后会序列化至磁盘中。
可以使用内存存储和第三方存储,如redis。对于集群式的session存储,那么肯定会使用第三方存储的实现。

在spring-session中,对session存储单独作了一个定义,但定义上基本保证与http session一致,主要的目的在于它可以支持在非http的环境中模拟使用。因此不直接使用http session接口。
先看定义:

1
2
3
4
5
6
7
8
public interface Session {
     /** 获取惟一的id,可以理解为即将每个session当作一个实体对象 */
    String getId();
   <T> T getAttribute(String attributeName);
    Set<String> getAttributeNames();
    void setAttribute(String attributeName, Object attributeValue);
    void removeAttribute(String attributeName);
}

从这个定义来看,如果要使用httpSession,如果实现了这个session接口,那么实现上就可以全部实现httpSession对于存储的要求。对于非httpSession场景,使用这个也可以达到session存储的目的。
当然,为了保证在会话场景中会使用到失效,最后访问时间,最大不活跃时间的目的,spring-session也有一个继承于session接口的expiringSession接口。

1.0    id主键的生成

对于id,即理解为session实体对象惟一键,可以采用任意的一种惟一key生成策略。比如,使用uuid来生成惟一键。同时,也可以将这个id认为就是在http中sessionCookie的值

1.1     map存储

如果是map来存储,那么 set,get,remove就可以直接使用map的相应实现即可。在spring-session实现中MapSession,即采用内部的一个hashmap来进行存储。

1.2     redis存储

在sprng-session中,将属性的存储和整体的存储分开,使用专门的仓库来处理session实体的处理。对于从领域模型的概念来说,set,get,removeAttribute只是对属性的处理,处理的是一个内部状态的变化,对于整体的实体来说,并没有整体上的处理。具体到实现,在redis的存储实现中,spring-session内部使用了一个 mapSession来存储相应的属性信息。
在一个实体被创建之后,相应的属性信息被全部设置至mapSession中,然后接下来的属性访问均通过mapSession来处理。为了最终存储相应的数据值,redis实现使用了一个额外的deltaMap来存放变化了的数据,并在重新保存时,一次性地将所有属性put至redis中。
从这个实现来看,spring-session的实现并不是实时地与redis进行交互,这种实现方式在解决同一session设置冲突属性时可能存在问题。因此存在这种场景的,需要重新实现相应的逻辑。

2     session仓库策略

之所以将存储策略和仓库策略分开,也是spring-session的一个设计思想。spring将session设计为一个实体,因此将实体对象的操作和属性的操作进行拆分。在前面的存储策略已经描述了属性的操作,因此这里的仓库策略主要处理实体的创建,获取,更新,删除等,即CRUD。如下的定义所示:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public interface SessionRepository<S extends Session> {
 
     /** 新建 */
    S createSession();
 
     /** 保存或更新 */
    void save(S session);
 
     /** 获取 */
    S getSession(String id);
 
     /** 删除 */
    void delete(String id);
}

2.1     本地仓库
如果是本地仓库,那么可以使用一个本地全局的globalMap,然后使用sessionId作为key,mapSession作为value来实现即可。

2.2     redis仓库

如果是resi仓库,那么在新建时,只是在本地创建一个redisSession对象,然后在redis存储中使用一个 hash结构来进行存储。即<I,<K,V>>的结构。其中I即表示sessionId,<k,V>表示具体的redisSession对象。其中getSession,delete都是针对I的操作,而save则在在指定I的情况下,直接保持<K,V>即可。

为了在一定程度上进行优化,以及解决在一定程序上的覆盖问题,在redis实现时。spring-session只对修改过的属性作存储,即当使用setAttribute处理属性时,spring-session使用额外的delta来保存修改过的属性,最后进行save时,只操作这部分数据即可。并且redis也支持调用hSet来保存或更新修改过的数据。

3     session传输策略

3.1     cookie传输

常规的实现也是使用cookie传输,比如tomcat,jetty等。在这种情况下,后端在创建session时,如果这是新的session,会自动调用原生response.addCookie以创建一个新的cookie信息。为保证安全,采用httpOnly进行创建。在发送给客户端之后的每次交互,浏览器会自动将这些信息传输至服务器端。然后服务器端直接从cookie中获取到sessionId,再进一步操作即可。
对于这种实现,应用程序开发不需要作任何的调整,浏览器会自动处理cookie的传输。

3.2     header传输

header传输,即在创建新session时,往response header中使用addHeader添加一个新的响应头,如session头,headerValue中存放相应的sessionId值。
在客户端实现中,客户端代码需要手动地将响应值进行记录,并在请求时加入到请求头当中。并且需要监听响应头值的变化,比如sessionId值的变化处理。相比cookie传输来说,这种实现对于开发更复杂一点。

4     http协议兼容

4.1     为保证http协议兼容,在session信息准备好之后,其必须在响应头之后进行处理

不管是使用cookie传输还是header传输,在响应时都需要保证在响应body输出时,相应的头信息都应该先被处理。(cookie也是一种特殊的header)。如果像以下的代码,就不能保证session响应被正确地处理

1
2
3
4
X implements Filter {
     filterChain.doFilter(request,response)
     // do session logic
}

在这种实现中,如果在应用代码中直接使用response的outputStream进行输出时,这里的过滤器实现中的do Session操作就不能实现相应的逻辑。因为http协议中的消息机制已经不再允许再输出body之后输出header信息了。

在spring-session的实现中,我们只需要保证在调用response处理响应body时,能够处理session逻辑即可。那么可以使用一种hack的技术,针对在response输出时,判定是否在处理body或应该结束响应时,加入特定的hack以处理我们的do session操作。因此提供了 OnCommittedResponseWrapper 以便在特定的时机进行处理,在具体的 onResponseCommitted中,处理session的delete响应和save等即可。

4.2     为支持多浏览器访问,因此在特定的路径信息处理时,应当自动地处理相应的browser参数

关于多browser支持,可以先看看此篇文章。
http://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-multi

在多browser支持时,主要即在请求参数中增加相应的浏览器识别参数_s,那么在需要进行界面跳转或其它地址处理时,后台应该能够自动地处理相应的参数信息。在spring-session中,为了对应用程序透明,通过重写response的encodeURL和encodeRedirectURL时,增加对相应_s的处理即达到相应的处理透明即可。

转载请标明出处:i flym
本文地址:http://www.iflym.com/index.php/code/201503170001.html

上一篇:Spark API编程动手实战-04-以在Spark 1.2版本实现对union、groupByKey、join、reduce、lookup等操作实践


下一篇:[转]在C#中使用托管资源和非托管资源的区别,以及怎样手动释放非托管资源: