实现多台server之间共享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 之后,所有服务器都会订阅到修改之后的配置,那么切换就会立即生效,当然可能会有短暂的时间延迟,但这是可以接受的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值