
做后端的第七个年头,我碰上了一个怪问题:一套刚上负载均衡没多久的系统,用户反馈每隔几分钟就要被迫重新登录一次,把登录逻辑查了个底朝天,愣是没查出任何毛病,末了定位到了负载均衡的会话保持配置上,真相这才水落石出。这个问题前前后后折腾了整整三天,把团队里几个人都熬红了眼,压力不小,也逼着我把接入层的配置从里到外、从分发到健康检查彻底摸了一遍。这一篇就把会话保持会话保持这个专题完整地复盘一遍:问题怎么发生的、几种保持方式怎么选、配置怎么做、上线前后怎么验,一节一节往下讲。
先把那次故障的来龙去脉讲清楚,还原一下现场,比直接干巴巴讲原理要有画面感得多。
用户反馈的症状很统一:"登录状态总掉",填了一半的表单也被踢回登录页,火气不小。起初团队都怀疑是登录凭证的过期逻辑写错了,把过期时间的判断逻辑翻来覆去查了个遍,代码逻辑完全正常,一点毛病挑不出来。接着又怀疑是浏览器Cookie策略的问题,拿自己的电脑环境反复复现,还是时好时坏。这种时好时坏的故障是格外难查的,没有稳定的复现路径,你盯着它就好的,你一走它就犯。
后来是在接入层日志里发现的线索:同一个用户的请求被分发到了两台不同的后端服务器,而登录状态存在服务器本地内存里——那时候系统还没上集中的会话存储,两台机器各存各的账,用户在A机上登录,下一个请求被分到B机,B机根本不认识他,自然把他一脚踢回登录页。负载均衡默认做的是轮询分发,请求一轮一圈地打到不同的机器上,登录态就这么被反复冲掉了,后端机器越多,冲得越勤快。
定位到原因后,解决问题的路径就清楚了:要么让同一用户总落到同一台机器,要么让登录状态不依赖任何一台具体的机器。前者叫会话保持,后者叫会话共享,名字听着像,路数完全不同,实际做的时候,两者不但不冲突,还能配合着用。
按来源IP做保持,同一个IP来的请求,总是转发给同一台后端。配置简单,接入层开个开关就完事,前后五分钟就能搞定。但它的软肋也明显:一个公司出口就一个IP,几百人共享一个出口,流量全压到一台机器的身上,负载均衡直接名存实亡;手机在移动网络和无线网络之间一切换、IP一变,保持关系也就跟着断了。这种方式只适合人少、出口分散的内部小系统,上生产要掂量。
接入层给头一回访问的用户种下一个Cookie,里面记着后端机器的标识,后续请求带着这个Cookie来,就转发给同一台机器,来来回回总能见到同一台机器,老熟人一样。这是实际用得多的方式,对应用完全透明,应用代码一行都不用改,后端也不需要任何配合。接入层的配置大致长这样:
upstream app_servers {
# 会话保持:给后端打标记,Cookie里始终记住分配的结果
hash $cookie_route consistent;
server 10.0.2.11:8080 max_fails=2 fail_timeout=10s;
server 10.0.2.12:8080 max_fails=2 fail_timeout=10s;
server 10.0.2.13:8080 max_fails=2 fail_timeout=10s;
keepalive 64;
}配置里同时配好了健康检查参数,某台机器连续两次探测失败就摘出去, fail_timeout内不再尝试,带着旧标记来的用户被重新分配到活着的机器上,服务不中断。
第三种是干脆不做保持,把会话从机器本地内存里搬出去,集中放到共享存储里,集群里任何一台机器都能读到它。这种方案让负载均衡回归纯粹的轮询分发,机器增减、宕机切换、流量调度都不影响登录态,是三种里头彻底的解法,但改造成本比前两种都高,要求应用主动适配集中式会话,改造动静不小,排期要单独申请。
那台系统当时的改造预算有限,后来走了两步:先上Cookie保持把血止住,再择机做会话共享来治本,节奏一步一步来。
上了Cookie保持的当天,"掉登录"的用户反馈就归零了,投诉电话一个都没再来过。半年后系统改造,我们把会话搬进了集中存储,Cookie保持降级成兜底手段继续留着,两道保险叠着用。两步走的好处是每一步的风险都攥在手里、可控可退:头一步不改应用代码,随时可回退;第二步改应用时接入层行为不变,出了问题范围也好圈定,不会两头一起查。
Cookie保持有两个细节容易踩。一是Cookie的名字要避开应用自己已经在用的名字,重名会互相覆盖,我们头一回上线就撞上这个坑,两个Cookie打架,两个Cookie打架,排查了大半天才发现。二是Cookie的生效范围和有效期要跟应用会话对齐对好,应用会话两小时过期,Cookie却设成了一天,用户拿着旧Cookie来,照样要重新登录,用户的体验就乱了套。这两个参数定下来之后要原样写进配置的注释里,换人接手维护的时候能少走不少弯路。
配置改完不等于完事,怎么证明会话保持真的在生效、一直在生效,得有一套验证和监控的办法,靠感觉是不行的。
我们的验证脚本写得很土但格外管用:写个循环请求,每个请求都打印出后端机器的标识,连续发它一百个请求,看分配结果是不是稳稳指向同一台机器。再模拟机器宕机,把其中一台停掉,看请求是不是平滑地漂移到别的机器上,Cookie有没有按预期更新。模拟宕机这一关尤其重要,平时不演练,真出事的时候才知道坑有多深。两关都过了,配置才敢推到生产环境。
上线后盯着三个指标看:会话保持命中率、后端机器的请求分布、登录状态相关的报错数。命中率稳定在九成以上,机器分布大体均匀,登录相关报错没有抬头,说明配置正在健康地运行,没有暗病。有一次分布指标显示一台机器的流量占比异常偏高,顺藤摸瓜查下去,发现是一个大客户的出口IP后面挂了几百个终端设备,Cookie在某些老设备上存不住,末了是给这个客户单独开了客户端证书方案才彻底解决。
会话保持只是接入层配置里的一小块,这次故障之后我把接入层的配置从头到尾彻底梳理了一遍,几点心得记在这里,给后来人递个手电。
接入层软件的默认配置是面向通用场景调过的,直接拿去跑生产,等于穿着拖鞋去跑马拉松,不出事是运气,出事是必然。超时时间、分发策略、健康检查、会话保持,每一项都要对照自己的业务特点重新过一遍,一项一项打钩。那次故障的根子,就是接入层上线的时候,压根没有人想过会话这件事,配置照着模板抄完就跑。
复盘的时候光看应用代码是不够的,请求从用户浏览器到应用服务器,中间要过接入层这座桥,桥上的行为不查清楚,桥的行为不盘清楚,问题就只查了一半。打那以后我们定了一条铁规矩:凡是"时好时坏"类故障,接入层日志是必查项,而且要和应用日志按请求标识串起来看,两头时间轴对齐,真相才浮得出来,光看一头,永远在瞎子摸象。
一次"掉登录"故障,牵出来接入层配置这一整块的知识,也牵出来一套排查此类问题的通用方法,一举两得。会话保持的三种方式各有适用场景:源地址保持适合小规模内网,Cookie插入保持是通用解,会话共享是彻底解,各有各的位置,谁也替代不了谁。我们的两步走验证了一个思路:先止血再治本,每一步的风险都攥在自己手里,进可攻退可守。配置这件事从来就没有银弹,把自己业务的流量特点搞清楚,比死记任何一条具体配置都重要得多。上线前的验证、上线后的监控,一道都不能省。这两道关卡守住了,接入层才算真正靠得住,睡觉才踏实。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。