分布式场景下Session解决方案-SpringSession

摘要本文主要研究基于 spring-seesion 解决分布式 session 的共享问题。首先聊一下session与cookie的做用与工做原理,而后步入主题,讲述session 共享问题的产生背景以及常见的解决方案;接着讲述了 spring-session 的两种管理 sessionid 的方式以及对应的使用场景;再接着对后台保存数据到 redis 上的数据结构进行了分析;而后对 spring-session 的核心源代码进行了解读,方便理解 spring-session 框架的实现原理。html

一:Session与Cookie

1.Cookie:

由于HTTP协议是无状态的,即服务器不知道用户上一次作了什么,没法建立同一用户请求的关联性,所以须要浏览器提供一个机制供服务端识别,这时Cookie便出现了。 经过引入cookie和session体系机制来维护状态信息。即用户第一次访问服务器的时候,服务器响应报头一般会出现一个Set-Cookie响应头,这里其实就是在本地设置一个cookie,当用户再次访问服务器的时候,http会附带这个cookie过去,cookie中存有sessionId这样的信息来到服务器这边确认是否属于同一次会话。web

Cookie的属性:

name:cookie的名字,Cookie一旦建立,名称便不可更改redis

value:cookie值spring

comment:该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明。数据库

domain:能够访问该Cookie的域名。若是设置为“.baidu.com”,则全部以“baidu.com”结尾的域名均可以访问该Cookie;也就是只有一级域名一致的状况下才能够访问同一cookie。后端

maxAge:Cookie失效的时间,单位秒。跨域

正数,则超过maxAge秒以后失效。 负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。 为0,表示删除该Cookie。 path:该Cookie的使用路径。例如:浏览器

path=/,说明本域名下contextPath均可以访问该Cookie。tomcat

path=/app/,则只有contextPath为“/app”的程序能够访问该Cookie。安全

path设置时,其以“/”结尾。

secure: 该Cookie是否仅被使用安全协议传输。这里的安全协议包括HTTPS,SSL等。默认为false。

Cookie是不支持跨域的,对于Cookie来讲,Cookie的同源只关注域名,是忽略协议和端口的。因此通常状况下,https://localhost:80/和http://localhost:8080/的Cookie是共享的。

Cookie是不可跨域的;在没有通过任何处理的状况下,二级域名不一样也是不行的。(wenku.baidu.com和baike.baidu.com)。只有当domainname设置为.baidu.com时才能够访问同一cookie。

Cookie数量&大小限制及处理策略 www.cnblogs.com/henryhappie…

2.Session

Cookie机制弥补了HTTP协议无状态的不足。在Session出现以前,基本上全部的网站都采用Cookie来跟踪会话。 与Cookie不一样的是,session是以服务端保存状态的。

当客户端请求建立一个session的时候,服务器会先检查这个客户端的请求里是否已包含了一个session标识 - sessionId,

若是已包含这个sessionId,则说明之前已经为此客户端建立过session,服务器就按照sessionId把这个session检索出来使用(若是检索不到,可能会新建一个) 若是客户端请求不包含sessionId,则为此客户端建立一个session而且生成一个与此session相关联的sessionId

sessionId的值通常是一个既不会重复,又不容易被仿造的字符串,这个sessionId将被在本次响应中返回给客户端保存。保存sessionId的方式大多状况下用的是cookie。

扩展:session的生命周期

session建立:在第一次使用resquest的getSession方法,web服务器会建立一个session

session使用:session在服务端建立完成后,内存会给session分配必定的空间,而且会生成一个临时cookie返回给用户,浏览器经过set-cookie建立cookie并保存到本地,此后访问都经过此cookieid找到对应的session。

session的销毁:
1.默认销毁:若是与服务端30分钟内没有交互,默认销毁。
2.手动销毁:当调用session的invalidate方法时候会销毁此session。
3.关闭服务器:内存空间被回收了,天然就不存在session了。
复制代码

解决方案-SpringSession

HttpSession 是经过 Servlet 容器建立和管理的,像 Tomcat/Jetty 都是保存在内存中的。而若是咱们把 web 服务器搭建成分布式的集群,而后利用Nginx 作负载均衡,那么来自同一用户的 Http 请求将有可能被分发到两个不一样的 web 站点中去。那么问题就来了,如何保证不一样的 web 站点可以共享同一份 session 数据呢?

最简单的作法是把session从容器中抽离出来。 第一种是使用容器扩展来实现,你们比较容易接受的是经过容器插件来实现,好比基于 Tomcat 的 tomcat-redis-session-manager ,基于 Jetty 的 jetty-session-redis等等。好处是对项目来讲是透明的,无需改动代码。不过前者目前还不支持 Tomcat 8 ,或者说不太完善。可是因为过于依赖容器,一旦容器升级或者更换意味着又得重新来过。而且代码不在项目中,对开发者来讲维护也是个问题。

第二种是本身写一套会话管理的工具类,包括 Session 管理和 Cookie 管理,在须要使用会话的时候都从本身的工具类中获取,而工具类后端存储能够放到 Redis 中。很显然这个方案灵活性最大,但开发须要一些额外的时间。而且系统中存在两套 Session 方案,很容易弄错而致使取不到数据。

第三种是使用框架的会话管理工具,也就是以下介绍的 spring-session ,能够理解是替换了 Servlet 那一套会话管理,接管建立和管理 Session 数据的工做。既不依赖容器,又不须要改动代码,而且是用了 spring-data-redis 那一套链接池,能够说是最完美的解决方案。固然除了redis管理存储外,spring-session也可经过数据库经过jdbc存储

1.spring Session 提供了 API 和实现,用于管理用户的 Session 信息。除此以外,它还提供了以下特性:

2.将 session 所保存的状态卸载到特定的外部 session 存储汇总,如 Redis 中,他们可以以独立于应用服务器的方式提供高质量的集群。

3.控制 sessionid 如何在客户端和服务器之间进行交换,这样的话就能很容易地编写 Restful API ,由于它能够从 HTTP 头信息中获取 sessionid ,而没必要再依赖于 cookie。

4.在非 Web 请求的处理代码中,可以访问 session 数据,好比在 JMS 消息的处理代码中。

5.支持每一个浏览器上使用多个 session,从而可以很容易地构建更加丰富的终端用户体验。

当用户使用 WebSocket 发送请求的时候,可以保持 HttpSession 处于活跃状态。

方案一:由 cookie 管理 sessionid(默认管理方式) 在springboot中集成springsession很是简单,引入

因为spring-session默认采用cookie管理策略,因此使用spring-session只须要在启动类添加@EnableRedisHttpSession注解,参数对应可设置session过时时间,以及redis存放空间位置,刷新模式以及定时清除。 maxInactiveIntervalInSeconds - 会话将在几秒钟后到期的时间

redisNamespace - 容许为会话配置特定于应用程序的命名空间。 Redis键和通道ID将以 spring:session:: 的前缀开头。

redisFlushMode - 容许指定什么时候将数据写入Redis。默认值仅在 SessionRepository 上调用 save 时。值 RedisFlushMode.IMMEDIATE 将尽快写入Redis。

SpringSession 提供了CookieSerializer接口的默认实现DefaultCookieSerializer,固然在实际应用中,咱们也能够本身实现这个接口,而后经过CookieHttpSessionIdResolver#setCookieSerializer(CookieSerializer)方法来指定咱们本身的实现方式。

方案二:经过HttpHeader管理sessionid

spring-session支持请求头来管理session,当cookie被禁用的状况下能够经过请求头携带token来匹配对应session。Spring Session容许在 Headers 中提供会话ID以使用 RESTful APIs

下面就是如何使用这两种管理方式: SpringSession中对于sessionId的解析相关的策略是经过HttpSessionIdResolver这个接口来体现的。HttpSessionIdResolver有两个实现类:

`public interface HttpSessionIdResolver {

List<String> resolveSessionIds(HttpServletRequest request);
void setSessionId(HttpServletRequest request, HttpServletResponse response,String sessionId);
void expireSession(HttpServletRequest request, HttpServletResponse response);
复制代码

}

resolveSessionIds:解析与当前请求相关联的sessionId。sessionId可能来自Cookie或请求头。

setSessionId:将给定的sessionId发送给客户端。这个方法是在建立一个新session时被调用,并告知客户端新sessionId是什么。

expireSession:指示客户端结束当前session。当session无效时调用此方法,并应通知客户端sessionId再也不有效。好比,它可能删除一个包含sessionId的Cookie,或者设置一个HTTP响应头,其值为空就表示客户端再也不提交sessionId。

咱们能够经过建立HttpSessionIdResolver的自定义实现类来选择合适的session管理策略。

SpringSession源码解析

在这里spring-session是经过redis来管理的,若是须要了解redis是如何操做的,就须要了解一下RedisOperationsSessionRepository这个类了。

RedisOperationsSessionRepository 是使用Spring Data的 RedisOperations 实现的 SessionRepository 。在Web环境中,这一般与 SessionRepositoryFilter 结合使用。该实现支持 SessionDestroyedEvent 和 SessionCreatedEvent 至 SessionMessageListener 。

Spring Session使用 Session 的最基本的API是 SessionRepository 。这个API有意很是简单,所以很容易提供具备基本功能的其余实现。 一些 SessionRepository 实现也能够选择实现 FindByIndexNameSessionRepository 。例如,Spring的Redis支持实现 FindByIndexNameSessionRepository 。 FindByIndexNameSessionRepository 添加了一种方法来查找特定用户的全部会话。这是经过确保使用用户名填充名称为 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 的会话属性来完成的。开发人员有责任确保填充属性,由于Spring Session不知道正在使用的身份验证机制。下面是一个如何使用它的示例:

String username = "username"; this.session.setAttribute( FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, username); FindByIndexNameSessionRepository 的某些实现将提供钩子以自动索引其余会话属性。对于例如,许多实现将自动确保使用索引名称 FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME 索引当前的Spring Security用户名。

String username = "username";

Map<String, Session> sessionIdToSession = this.sessionRepository .findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,username);

相关文章
相关标签/搜索