分布式如何实现session共享

         最近,在工做中遇到一个问题,问题描述:一个用户在登陆成功之后会把用户信息存储在session当中,这时session所在服务器为server1,那么用户在session失效以前若是再次使用app,那么可能会被路由到server2,这时问题来了,server没有该用户的session,因此须要用户从新登陆,这时的用户体验会很是很差,因此咱们想如何实现多台server之间共享session,让用户状态得以保存。web

         固然业界已经有不少成熟的解决方案,我罗列以下:redis

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做为备用,不影响应用提供服务,下面我简单介绍方案中session共享的实现方式和原理。cookie

        这里主要讲解redis和cookie方案,gemfire比较复杂你们能够自行查看gemfire工做原理。利用redis作session共享,首先须要与业务逻辑代码解耦,否则session共享将没有意义,其次支持动态切换到客户端cookie模式。redis的方案是,重写服务器中的HttpSession和HttpServletRequest,首先实现HttpSession接口,重写session的全部方法,将session以hash值的方式存在redis中,一个session的key就是sessionID,setAtrribute重写以后就是更新redis中的数据,getAttribute重写以后就是获取redis中的数据,等等须要将HttpSession的接口一一实现。session

        实现了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共享作成一项可插拔的功能,没有任何侵入性。app

         这个时候已经实现了一套可插拔的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以后,全部服务器都会订阅到修改以后的配置,那么切换就会当即生效,固然可能会有短暂的时间延迟,但这是能够接受的。加密

         方案大致分享完了,在此将个人收获记录下来,咱们已经实现了一个版本,你们也能够发挥想象,分享不一样的方案,咱们一块儿进步,文中有不足之处,还望各位不吝赐教。