session共享怎么做的(分布式如何实现session共享)?
问题描述:一个用户在登录成功以后会把用户信息存储在session当中,这时session所在服务器为server1,那
么用户在 session 失效之前如果再次使用 app,那么可能会被路由到 server2,这时问题来了,server 没有该用户的
session,所以需要用户重新登录,这时的用户体验会非常不好,所以我们想如何实现多台 server 之间共享 session,
让用户状态得以保存。
1、服务器实现的session复制或session共享,这类型的共享session是和服务器紧密相关的,比如webSphere
或JBOSS在搭建集群时候可以配置实现session复制或session共享,但是这种方式有一个致命的缺点,就是不好扩
展和移植,比如我们更换服务器,那么就要修改服务器配置。
2、利用成熟的技术做session复制,比如12306使用的gemfire,比如常见的内存数据库如redis或memorycache,
这类方案虽然比较普适,但是严重依赖于第三方,这样当第三方服务器出现问题的时候,那么将是应用的灾难。
3、将 session维护在客户端,很容易想到就是利用cookie,但是客户端存在风险,数据不安全,而且可以存放的
数据量比较小,所以将session维护在客户端还要对session中的信息加密。
我们实现的方案可以说是第二种方案和第三种方案的合体,可以利用 gemfire 实现 session 复制共享,还可以将
session维护在redis中实现session共享,同时可以将session维护在客户端的cookie中,但是前提是数据要加密。
这三种方式可以迅速切换,而不影响应用正常执行。我们在实践中,首选 gemfire 或者 redis 作为 session 共享的载
体,一旦session不稳定出现问题的时候,可以紧急切换cookie维护session作为备用,不影响应用提供服务。
这里主要讲解 redis 和 cookie 方案,gemfire 比较复杂大家可以自行查看 gemfire 工作原理。利用 redis 做
session共享,首先需要与业务逻辑代码解耦,不然session共享将没有意义,其次支持动态切换到客户端cookie模
式。redis的方案是,重写服务器中的HttpSession和HttpServletRequest,首先实现HttpSession接口,重写session
的所有方法,将session以hash值的方式存在redis中,一个session的key就是sessionID,setAtrribute重写之
后就是更新 redis 中的数据,getAttribute 重写之后就是获取 redis 中的数据,等等需要将 HttpSession 的接口一一
实现。
实现了HttpSesson,那么我们先将该session类叫做MySession(当然实践中不是这么命名的),当MySession
出现之后问题才开始,怎么能在不影响业务逻辑代码的情况下,还能让原本的 request.getSession()获取到的是
MySession,而不是服务器原生的session。这里,我决定重写服务器的HttpServletRequet,这里先称为MyRequest,
但是这可不是单纯的重写,我需要在原生的request基础上重写,于是我决定在filter中,实现request的偷梁换柱,
我的思路是这样的,MyRequest的构建器,必须以request作为参数,于是我在filter中将服务器原生的request(也
有可能是框架封装过的 request),当做参数 new 出来一个 MyRequest,并且 MyRequest 也实现了
HttpServletRequest接口,其实就是对原生request的一个增强,这里主要重写了几个request的方法,但是最重要
的是重写了request.getSession(),写到这里大家应该都明白为什么重写这个方法了吧,当然是为了获取MySession,
于是这样就在filter中,偷偷的将原生的request换成MyRequest了,然后再将替换过的request传入chan.doFilter(),
这样 filter 时候的代码都使用的是 MyRequest 了,同时对业务代码是透明的,业务代码获取 session 的方法仍然是
request.getSession(),但其实获取到的已经是MySession了,这样对session的操作已经变成了对redis的操作。
这样实现的好处有两个,第一开发人员不需要对session共享做任何关注,session共享对用户是透明的;第二,filter
是可配置的,通过filter的方式可以将session共享做成一项可插拔的功能,没有任何侵入性。
这个时候已经实现了一套可插拔的session共享的框架了,但是我们想到如果redis服务出了问题,这时我们该怎
么办呢,于是我们延续redis 的想法,想到可以将 session 维护在客户端内(加密的 cookie),当然实现方法还是一
样的,我们重写 HttpSession 接口,实现其所有方法,比如 setAttribute 就是写入 cookie,getAttribute 就是读取
cookie,我们可以将重写的session 称作 MySession2,这时怎么让开发人员透明的获取到 MySession2呢,实现方
法还是在filter内偷梁换柱,在MyRequest加一个判断,读取sessionType配置,如果sessionType是redis的,那
么getSession的时候获取到的是MySession,如果sessionType是coolie的,那么getSession的时候获取到的是
MySession2,以此类推,用同样的方法就可以获取到MySession 3,4,5,6等等。
这样两种方式都有了,那么我们怎实现两种 session 共享方式的快速切换呢,刚刚我提到一个 sessionType,这
是用来决定获取到session的类型的,只要变换sessionType就能实现两种session共享方式的切换,但是sessionType
必须对所有的服务器都是一致的,如果不一致那将会出现比较严重的问题,我们目前是将 sessionType 维护在环境变
量里,如果要切换 sessionType就要重启每一台服务器,完成 session共享的转换,但是当服务器太多的时候将是一
种灾难。而且重启服务意味着服务的中断,所以这样的方式只适合服务器规模比较小,而且用户量比较少的情况,当服
务器太多的时候,务必需要一种协调技术,能够让服务器能够及时获取切换的通知。基于这样的原因,我们选用
zookeeper 作为配置平台,每一台服务器都会订阅 zookeeper 上的配置,当我们切换 sessionType 之后,所有服务
器都会订阅到修改之后的配置,那么切换就会立即生效,当然可能会有短暂的时间延迟,但这是可以接受的。
文章来自www.wityx.com,转载请注明出处!原文地址http://www.wityx.com/post/1321_1_1.html