不知道下面的场景有没有出现在你的面前:开发Z哥对着运维小哥Y哥喊道:“Y哥,现在系统卡死了,活动一波才刚刚开始,快帮我加几台机器顶一下吧。”Y哥回答:“没问题,分分钟搞定。”然后发现数据库的压力在飞速上升,DBA大叫:“Z哥,你干什么?数据库要被你搞坏了。”随后客服端的接待箱也炸了。是否还在做生意,这些问题都是由一个“Sessionlost”问题引起的。1.什么是Sessionloss?相信大部分Coder应该都知道Session,它是为了识别同一个访问的多次访问而产生的概念用户作为系统中的“同一用户”。此外,还可以用于减少重复访问DB或远程服务以获取与用户相关的信息,从而提高性能。在我们已经完成的场景中负载均衡,如果选择的负载策略是hash策略,会造成Session的一个副作用,这个副作用就像上面的案例,一旦用户从原来访问的服务器A变成访问服务器B的时候,就会出现这样的问题作为“丢失的登录状态”和“缓存渗透”就会出现。为什么hash策略会出现这个问题呢?首先,有必要了解哈希是如何工作的。哈希策略是一个哈希函数,如下图所示。当函数不变时,A始终对应01,B对应04,C对应08。以nginx中的ip_hash策略为例。因为我们认为正常情况下用户的ip不会在短时间内发生变化,所以当我们选择使用ip_hash策略进行负载均衡时,就意味着我们期望同一个用户始终访问同一个服务器,如图下图中,图中的哈希函数就是最简单的随机例子。这样我们只需要将进程中的用户相关信息缓存在这台服务器上,就可以达到非常划算的提升性能??的效果。这时候客户端和服务端就相当于建立了信任,互相认识了。这种信任就是“会话”。但是,当我们添加服务器时,情况发生了变化。图中的哈希函数就是最简单的随机例子。这时候,我们原本的期待破灭了。因为用户和序号为0的节点之间的链路变成了序号为3的链路,所以出现了上面提到的“Sessionloss”的问题。同时序号0节点上的进程内缓存失效,序号3节点上没有用户相关的缓存,导致需要从下游DB获取大量数据或远程服务。要知道,一旦涉及到网络通信,性能必然会大幅下降,而I/O和序列化都是耗时的工作。更重要的是,一旦同时有大量用户,可能会因为后端DB和远程服务无法瞬间应对激增的高密度请求而挂掉。这还没有结束。如果当前程序没有一些故障隔离或者降级策略,会进一步产生蝴蝶效应,导致整个大系统响应变慢。可以说是“一粒老鼠屎坏了一锅粥”。2、Nginx是如何解决这个问题的既然以nginx为例,那我们就从nginx说起。这个问题可以通过在nginx中引入nginx-sticky-module模块来解决。整个求解过程如下。可以看到,客户端在第一次进入nginx匹配节点时,在为其分配一个节点的同时,会将该节点的唯一标识md5写入cookie中并返回。如果下次发起请求时发现有这个cookie值,则直接转发给该值对应的节点。这种机制在专业上被称为“Sessionretention”。虽然cookies可以用来解决这个问题,但是cookies也有一个潜在的问题。如果客户端没有开启cookie功能,这个机制就会失效。幸运的是,目前主流的浏览器都默认启用了cookie。题外话:nginx是2004年发布的,nginx-sticky-module出现之前的七年也是nginx相比竞品HAProxy最大的短板,因为HAProxy支持sessionretention。3、除了cookies之外,还有另外两种保持session的方式可以达到类似的效果。它们分别称为“会话复制”和“会话共享”。1.会话复制这是最简单粗暴的方式。根据第一节的案例,问题的原因是节点3没有用户的Session。那么就很容易想到在节点3运行之前复制Session相关的Cache数据。并且在多个节点之间持续保证数据的同步,也就是说每个节点上都有每个用户的session数据。实现方案有很多,尤其是不同宿主程序提供或多或少的入口点,甚至是开箱即用的方案,比如Tomcat的DeltaManager和BackupManager,Tomcat和IISFilter机制等,这里不展开.这类解决方案的特点是:优点:天然的高可用性,即使某些节点宕机。因为每个节点都存储了所有连接用户的会话信息。缺点:因为每台电脑的内存都有上限,所以只适用于session相关数据量较小的场景。而且,由于需要在多个节点之间同步数据,还需要解决额外的数据一致性问题。同时,随着节点数量的增加,损失(延迟、带宽等)增加,存在广播风暴的风险。2.Session共享我们也可以通过将Session信息存储在一个全局共享的存储介质中来达到同样的效果,比如数据库、远程缓存等,这是一种集中式的解决方案。这类方案的特点是它的优点:无论节点如何增加或减少,都不会发生100%的会话丢失。缺点:每次读写请求都需要增加额外的共享存储调用,增加了网络I/O、序列化等操作,性能下降明显。此外,共享存储介质除了增加额外的维护成本外,还需要解决单点问题,规避系统性风险。对比之前的“SessionPersistence”方案的优缺点和适用场景。一句话概括这三种解决方案:会话保持。原件在哪里或去哪里。会话复制。无论哪里都有相同的数据。会话共享。所有节点共享一份数据。系统越大,最终会采用“会话共享”的方案,因为共享存储只要横向扩展,理论上可以支持无限多的用户。比如Redis,一系列的NOSQL和NEWSQL等。就像下面,它把“规模大”、“高可用”、“效果好”集于一身。4.结论现在你应该清楚会话丢失的问题以及如何处理它了。但是,我们也需要明白一个事实:严格来说,“Sessionretention”本质上是在破坏“负载均衡”的初衷。举一个极端的场景:一共有10个session连接到节点A,而且都是active的。所以这个时候,即使多了一个节点B上线,只要没有新的会话进来,节点B上的活跃连接数就永远为0,起不到分担压力的作用。不过,在系统的初始阶段,使用这么简单的方案也是极好的。
