前言阿粉公司有个web管理系统,使用tomcat部署。由于是后台管理系统,所有网页都需要登录授权后才能进行相应的操作。起初,没有多少人使用这个系统。为了节省资源,本系统只部署在单机上。后来用的人越来越多,单机有点受不了,阿芬决定再部署一台机器。此时后台系统中有两个服务,所以我们使用Nginx作为反向代理。整体架构图如下:这个架构图想必大家都很熟悉,主流的web系统应该都是这样部署的。经过一番调试,阿芬在夜深人静时将系统部署到生产环境中。我觉得没什么问题,就交给测试小姐姐开始测试了。这次测试出了大问题!测试小姐姐反映登录后过一会需要重新登录,几次都是这样操作。阿芬检查了一下,发现系统应用和配置都没有问题,那到底是哪里出了问题呢?这时组长正要下班,看到这里有问题,就过来看看。简单了解了基本情况后,很快就找到了问题的原因,然后在Nginx端修改了配置,重启解决了问题。分布式一致性会话解决问题后,组长坐下来给阿芬解释了问题的原因:分布式一致性会话。本来我们登录后,会把用户登录信息放在Session中。对于每一次操作,用户首先检查Session中是否有用户信息。如果不存在,将强制用户先登录。在原来的架构中,我们只有一个应用系统,所有的操作都在一个Tomcat上进行,这当然是没有问题的。但是现在我们部署了两个系统。由于Nginx采用默认的负载均衡策略(roundrobin),请求会按照时间顺序一个一个分发到后端应用。也就是说,我们一开始登录Tomcat1后,用户信息是放在Tomcat1的Session中的。一段时间后,请求被Nginx分发到Tomcat2。这时候Tomcat2上的Session里面已经没有用户信息了,只好重新登录。另外,由于我们的系统采用的是单点登录的方式,Tomcat2登录后,Tomcat1的登录信息会失效,所以当Nginx再次向Tomcat1分发流量时,session中的用户登录信息已经失效,并且你必须重新登录。知道了问题,阿芬当然想知道解决方案,于是组长就分布式一致性环节教了阿芬四种解决方案。以DistributedConsistencySession的形式,讲解解决方案。Sessionreplicationgroupleader:如果此时Tomcat1Session中有用户信息,而Tomcat2中没有。这时候如果我们把Tomcat1的Session复制到Tomcat2,后面Nginx会把请求转发给Tomcat2。由于Tomcat2有Session,此时不需要重新登录。架构图如下:ConsistencySession-SessionCopyTomcat的Session副本的配置。网上有很多例子。这里,我就不贴了。感兴趣的同学可以自行搜索。阿芬:对,这个方法很好。Tomcat支持这种方式,我们只需要修改Tomcat的配置,我们的应用代码不需要修改。领队:你说的对,但是这个方法还有很多缺点。首先,会话复制和传输需要占用内网带宽。第二,我们的例子只有两台机器,复制性能还不错。但是假设我们有N台机器,每个副本都要复制到N-1台机器上。如果机器多,可能会形成网络风暴,复制性能会呈指数级下降。第三,Tomcat需要保存所有会话数据。该方案中的session是保存在内存中的,很容易受到机器总内存的限制。我们不能通过增加机器来横向扩展。我们唯一能做的就是增加机器的内存。但是机器的内存越大,价格真的很贵!!!所以不推荐这种方案。Session前端存储阿芬:嗯,这个方案确实有点不靠谱~嘿嘿,有!我们的Session其实是存储用户信息的,所以我现在不存储在TomcatSession中,我把信息取出来保存在你浏览器的cookie中。这样每个用户的浏览器都保存了自己的cookie信息,服务器端不需要保存,解决了Session复制方案的缺陷。接下来用户每次请求都给我发这个cookie,我判断cookie里面的用户信息不够。架构图如下:ConsistentSession-SessionFront-endstorageteamleader,请看我:是的,你的方案确实可行。但是,如果使用这种方案,首先要考虑加密方案。用户信息是我们的敏感数据,不能轻易被他人窃取或篡改。除此之外,这种方案每次请求都要携带cookie传输,会占用外网带宽。如果cookie太大,会增加网络开销。此外,我们存储的数据大小很容易受到cookie的限制。所以这个还是不是很常用,但是也是一种思路。我推荐以下两个选项。会话粘性(StickySessions)组长:你刚才应该已经看到了。我只是对Nginx的配置做了一些修改,然后这个问题就解决了。其实这是因为我修改了Nginx默认的负载均衡策略,改为使用IPHash。Nginx会使用请求者的IP做Hash,然后分发到一台机器上,这样来自同一个IP的请求都会落到同一个Tomcat上。架构图如下:SessionSticky-IPHash在上面的方法中,我们使用了Nginx的四层负载均衡方法。其实Nginx也可以实现七层负载均衡的方法,即利用Http协议中的一些业务属性做Hash。常见的有userId、loginId等。架构图如下:ConsistentSession-SessionSticky-七层范:这个方案看起来很简单,我们只需要修改Nginx的配置,应用端的配置不需要改动。只要请求的源IP足够随机,IPHASH后两个应用上的流量就足够随机了。另外,如果后面两台机器搞不定,我们也可以横向扩展,增加更多的机器,只需要修改Nginx的配置即可。领队:你说的这几点都对!但是大家有没有想过,像我们公司一样,大家出口的IP都是一样的。那么我们公司的所有请求只会到一台机器上,我们的情况又变成了单点。另外,如果重启Tomcat,由于Session是放在内存中的,这部分Session会丢失,从而导致这部分用户重新登录。最后,如果我们临时添加机器,修改Nginx配置,重启,Nginx会重新计算Hash分布请求。这种情况会导致一些用户被重新路由到新机器上。由于没有Session,他们需要重新登录。不过Tomcat不会重启,也不会加很多机器,所以这个问题问题不大,用户体验稍差。今天我们解决问题的办法就是先用这个。但是,我们稍后将其更改为以下方法。后端集中存储组长:在上述方法中,我们将session存储在应用内存中。只要重启应用机器,session就会丢失。为了解决这个问题,我们将Session单独保存,保存在Redis或者MySQL中。但是由于Session需要过期失效,不需要持久化,所以这里建议使用Redis来保存。这样架构就变成了这样:ConsistentSession-Session后端存储我们采用这种方案,不存在session丢失的风险,当然前提是Redis不能宕机。另外如果后面应用的话,可以直接横向扩展。如果以后应用的请求量太大,一个Redis无法支撑,那我们其实可以扩容集群,根据缓存key进行路由。阿芬:对对对,这个方法不错~领导:你别高兴得太早,我们使用这个方案是要付出一定代价的。首先,我们需要为每个请求调用一次Redis,这增加了一次网络的开销。另外引入Redis需要修改相应的代码,所以复杂度变高了。因此,这种解决方案既有优点也有缺点。当然,对于我们的场景来说,利大于弊。阿芬:嗯,好像是这样。领队:好了,时间不早了,问题解决了,我们去拿串,我请客!阿芬:老大,??!组长拍了拍阿芬的脑袋:我这顿饭可不是白吃的,下周可以修改现在的方法,改成Session集中存储的方法。给你一点提示,你可以使用spring-session。阿芬:嗯,食人术很短,我下周去研究。总结最后,阿芬总结一下,当我们的后端web应用扩展到多台服务器时,我们会遇到分布式一致性会话的问题。主流方案有四种:Session复制:使用Tomcat等Web容器同步复制Session前端存储:使用用户浏览器中的cookie保存Session信息Session粘性方案:使用Nginx做四层Hash或者七层Hash特性保证用户请求落在同一台机器上Session后端集中存储方案:使用Redis集中存储Session,即使Web应用重启或扩容,Session也不需要丢失。以上四种方案,首先推荐第四种。当然第四种方案需要一定的开发工作量,前期没有改造过的流程可以选择第三种方案进行中间过渡。嗯,后面阿奋会使用Session后端存储的方案来改造这个项目,稍后阿奋会和大家分享spring-session。
