做了负载均衡就可以随便加服务器了吗?
你是否遇到过这样的场景?开发Z哥紧急呼叫运维Y弟:“Y弟,系统又卡住了!刚刚上线了一波活动,赶紧帮我加几台机器应对一下。”Y弟迅速回应:“明白,马上搞定。”紧接着数据库压力剧增,DBA愤怒地吼道:“Z哥,你在搞什么鬼?数据库快撑不住了!”客服那边也炸开了锅,大量用户抱怨登录后不久就遭遇退出,反复登录又退出,让人怀疑系统是否还能正常运营。
这一切问题的背后,都源于一个令人头疼的“Session丢失”问题。
一、揭开Session丢失的神秘面纱
对于大多数Coder来说,Session并不陌生。它是一个为了让同一用户的多次访问在系统中被识别为“同一个用户”而诞生的概念。除此之外,Session还能帮助我们减少重复获取用户相关信息到数据库或远程服务的次数,从而提升系统性能。
当我们采用负载均衡策略时,尤其是选择了hash策略时,Session可能会带来一些意想不到的副作用。想象一下,当用户由于某种原因从原先的服务器A跳转到服务器B时,就会出现“登录状态丢失”、“缓存穿透”等一系列问题。
那么,为什么hash策略会引发这些问题呢?让我们先了解一下hash函数的运作原理。在不变的函数作用下,每个输入对应一个固定的输出。例如,A对应01,B对应04,C对应08。以nginx中的ip_hash策略为例,我们假设用户的IP在短时间内不会变化,因此选择ip_hash策略进行负载均衡时,期望同一用户始终访问同一台服务器。这样,我们只需在这台服务器上缓存用户相关信息,就能大幅提升性能。
这时,客户端与服务端之间建立了一种“信任”,这种信任就是“Session”。当我们增加一台服务器时,这种美好的预期就被打破了。用户的链接从序号0节点转移到了序号3节点,导致了前面提到的“Session丢失”问题。序号0节点上的进程内缓存失效,而序号3节点上又没有用户相关的任何缓存,导致大量数据需要从下游的数据库或远程服务中获取。这不仅涉及网络通信,导致I/O、序列化等耗时操作,更可能使后端数据库和远程服务因无法承载激增的请求而挂起。如果没有适当的故障隔离或降级策略,还可能产生连锁反应,影响整个系统的响应速度。
二、Nginx如何应对这一问题?
以nginx为例,我们可以通过引入nginx-sticky-module模块来解决这一问题。这个模块的工作过程相当巧妙。当客户端第一次进入nginx并匹配到某个节点时,除了分配节点外,还会将该节点的标识进行md5处理后写入cookie中返回给客户端。当客户端下次发起请求时,如果带有这个特定的cookie值,就直接转发到该值对应的节点上。这种机制被称为“Session保持”。
虽然cookie解决方案很有效,但也存在潜在问题:如果客户端未启用cookie功能,这个机制就会失效。好在目前主流浏览器默认都开启了cookie功能。值得一提的是,在nginx-sticky-module出现之前的七年里,这也是nginx相比竞品HAProxy的一个显著短板,因为HAProxy本身就支持Session保持。
三、其他Session保持方案
除了利用cookie外,还有两种方案可以实现类似效果,即“Session复制”和“Session共享”。
1、Session复制:这是一种简单直接的方式。针对之前提到的案例中的问题根源——节点3缺少用户Session——我们可以通过复制Session相关的Cache数据来解决。在多个节点之间保持数据同步,确保每台节点上都存有每个用户的Session数据。在软件开发领域,针对会话管理有多种解决方案,它们在不同场景和需求下各有优劣。
一种方案是“Session保持”,即用户会话信息保存在哪个节点上,用户就被引导向那个节点。这种方式对于初期系统来说可能是个不错的选择,易于实施且维护成本低。它的优点是简单直观,缺点则是可能违背负载均衡的初衷,导致某些节点承担过多负载。
另一种方案是“Session复制”。在这种方案中,每个节点都拥有完全相同的用户会话信息,从而确保高可用性。即使部分节点宕机,也不会导致用户会话丢失。复制会话信息需要解决数据一致性问题,并且随着节点数量的增加,可能会遇到延迟、带宽损耗乃至广播风暴等问题。这种方案的优点是天然高可用,缺点则是受到内存限制和额外的数据同步开销。
再来看“Session共享”方案。该方案通过全局共享的存储介质(如数据库、远程缓存等)来存储会话信息,实现所有节点共享同一份数据。这种中心化思想的解决方案能有效避免会话丢失,无论节点如何变化,用户会话始终存在。它也有自己的缺点:每次读写请求都需要额外的网络I/O和序列化操作,性能可能受到影响;同时需要解决共享存储的单点问题和额外的维护成本。在大规模系统中,Session共享往往是更优的选择,因为它可以通过横向扩展来支持更多用户。
“Session保持”、“Session复制”和“Session共享”三种方案各有长短,适用场景也各不相同。在实际应用中,需要根据系统规模、性能和可靠性需求来选择合适的方案。在系统的起步阶段,“Session保持”可能是一个简单有效的选择;而在系统规模扩大时,“Session共享”则更有可能成为更优的解决方案。(作者:Zachary;来源:Java后端技术)