来源 | 经授权转载自哔哩哔哩技术公众号
至暗时刻2021 年 7 月 13 日 22:52,SRE 收到大量服务和域名的接入层不可用报警,客服侧开始收到大量用户反馈 B 站无法使用,同时内部同学也反馈 B 站无法打开,甚至 APP 首页也无法打开。基于报警内容,SRE 第一时间怀疑机房、网络、四层 LB、七层 SLB 等基础设施出现问题,紧急发起语音会议,拉各团队相关人员开始紧急处理(为了方便理解,下述事故处理过程做了部分简化)。
初因定位22:55 远程在家的相关同学登陆 VPN 后,无法登陆内网鉴权系统(B 站内部系统有统一鉴权,需要先获取登录态后才可登陆其他内部系统),导致无法打开内部系统,无法及时查看监控、日志来定位问题。
22:57 在公司 Oncall 的 SRE 同学(无需 VPN 和再次登录内网鉴权系统)发现在线业务主机房七层 SLB(基于 OpenResty 构建) CPU 100%,无法处理用户请求,其他基础设施反馈未出问题,此时已确认是接入层七层 SLB 故障,排除 SLB 以下的业务层问题。
23:07 远程在家的同学紧急联系负责 VPN 和内网鉴权系统的同学后,了解可通过绿色通道登录到内网系统。
23:17 相关同学通过绿色通道陆续登录到内网系统,开始协助处理问题,此时处理事故的核心同学(七层 SLB、四层 LB、CDN)全部到位。
故障止损23:20 SLB 运维分析发现在故障时流量有突发,怀疑 SLB 因流量过载不可用。因主机房 SLB 承载全部在线业务,先 Reload SLB 未恢复后尝试拒绝用户流量冷重启 SLB,冷重启后 CPU 依然 100%,未恢复。
23:22 从用户反馈来看,多活机房服务也不可用。SLB 运维分析发现多活机房 SLB 请求大量超时,但 CPU 未过载,准备重启多活机房 SLB 先尝试止损。
23:23 此时内部群里同学反馈主站服务已恢复,观察多活机房 SLB 监控,请求超时数量大大降低,业务成功率恢复到 50% 以上。此时做了多活的业务核心功能基本恢复正常,如 APP 推荐、APP 播放、评论 & 弹幕拉取、动态、追番、影视等。非多活服务暂未恢复。
23:25 - 23:55 未恢复的业务暂无其他立即有效的止损预案,此时尝试恢复主机房的 SLB。
我们通过 Perf 发现 SLB CPU 热点集中在 Lua 函数上,怀疑跟最近上线的 Lua 代码有关,开始尝试回滚最近上线的 Lua 代码。
近期 SLB 配合安全同学上线了自研 Lua 版本的 WAF,怀疑 CPU 热点跟此有关,尝试去掉 WAF 后重启 SLB,SLB 未恢复。
SLB 两周前优化了 Nginx 在 balance_by_lua 阶段的重试逻辑,避免请求重试时请求到上一次的不可用节点,此处有一个最多 10 次的循环逻辑,怀疑此处有性能热点,尝试回滚后重启 SLB,未恢复。
SLB 一周前上线灰度了对 HTTP2 协议的支持,尝试去掉 H2 协议相关的配置并重启 SLB,未恢复。
新建源站 SLB00:00 SLB 运维尝试回滚相关配置依旧无法恢复 SLB 后,决定重建一组全新的 SLB 集群,让 CDN 把故障业务公网流量调度过来,通过流量隔离观察业务能否恢复。
00:20 SLB 新集群初始化完成,开始配置四层 LB 和公网 IP。
01:00 SLB 新集群初始化和测试全部完成,CDN 开始切量。SLB 运维继续排查 CPU 100% 的问题,切量由业务 SRE 同学协助。
01:18 直播业务流量切换到 SLB 新集群,直播业务恢复正常。
01:40 主站、电商、漫画、支付等核心业务陆续切换到 SLB 新集群,业务恢复。
01:50 此时在线业务基本全部恢复。
恢复 SLB01:00 SLB 新集群搭建完成后,在给业务切量止损的同时,SLB 运维开始继续分析 CPU 100% 的原因。
01:10 - 01:27 使用 Lua 程序分析工具跑出一份详细的火焰图数据并加以分析,发现 CPU 热点明显集中在对 lua-resty-balancer 模块的调用中,从 SLB 流量入口逻辑一直分析到底层模块调用,发现该模块内有多个函数可能存在热点。
01:28 - 01:38 选择一台 SLB 节点,在可能存在热点的函数内添加 debug 日志,并重启观察这些热点函数的执行结果。
01:39 - 01:58 在分析 debug 日志后,发现 lua-resty-balancer 模块中的 _gcd 函数在某次执行后返回了一个预期外的值:nan,同时发现了触发诱因的条件:某个容器 IP 的 weight=0。
01:59 - 02:06 怀疑是该 _gcd 函数触发了 jit 编译器的某个 bug,运行出错陷入死循环导致 SLB CPU 100%,临时解决方案:全局关闭 jit 编译。
02:07 SLB 运维修改 SLB 集群的配置,关闭 jit 编译并分批重启进程,SLB CPU 全部恢复正常,可正常处理请求。同时保留了一份异常现场下的进程 core 文件,留作后续分析使用。
02:31 - 03:50 SLB 运维修改其他 SLB 集群的配置,临时关闭 jit 编译,规避风险。
根因定位11:40 在线下环境成功复现出该 bug,同时发现 SLB 即使关闭 jit 编译也仍然存在该问题。此时我们也进一步定位到此问题发生的诱因:在服务的某种特殊发布模式中,会出现容器实例权重为 0 的情况。
12:30 经过内部讨论,我们认为该问题并未彻底解决,SLB 仍然存在极大风险,为了避免问题的再次产生,最终决定:平台禁止此发布模式;SLB 先忽略注册中心返回的权重,强制指定权重。
13:24 发布平台禁止此发布模式。
14:06 SLB 修改 Lua 代码忽略注册中心返回的权重。
14:30 SLB 在 UAT 环境发版升级,并多次验证节点权重符合预期,此问题不再产生。
15:00 - 20:00 生产所有 SLB 集群逐渐灰度并全量升级完成。
原因说明 背景B 站在 19 年 9 月份从 Tengine 迁移到了 OpenResty,基于其丰富的 Lua 能力开发了一个服务发现模块,从我们自研的注册中心同步服务注册信息到 Nginx 共享内存中,SLB 在请求转发时,通过 Lua 从共享内存中选择节点处理请求,用到了 OpenResty 的 lua-resty-balancer 模块。到发生故障时已稳定运行快两年时间。
在故障发生的前两个月,有业务提出想通过服务在注册中心的权重变更来实现 SLB 的动态调权,从而实现更精细的灰度能力。SLB 团队评估了此需求后认为可以支持,开发完成后灰度上线。
诱因在某种发布模式中,应用的实例权重会短暂的调整为 0,此时注册中心返回给 SLB 的权重是字符串类型的"0"。此发布模式只有生产环境会用到,同时使用的频率极低,在 SLB 前期灰度过程中未触发此问题。
SLB 在 balance_by_lua 阶段,会将共享内存中保存的服务 IP、Port、Weight 作为参数传给 lua-resty-balancer 模块用于选择 upstream server,在节点 weight = "0" 时,balancer 模块中的 _gcd 函数收到的入参 b 可能为 "0"。
根因Lua 是动态类型语言,常用习惯里变量不需要定义类型,只需要为变量赋值即可。
Lua 在对一个数字字符串进行算术操作时,会尝试将这个数字字符串转成一个数字。
在 Lua 语言中,如果执行数学运算 n % 0,则结果会变为 nan(Not A Number)。
_gcd 函数对入参没有做类型校验,允许参数 b 传入:"0"。同时因为"0" != 0,所以此函数第一次执行后返回是 _gcd("0",nan)。如果传入的是 int 0,则会触发 [ if b == 0 ] 分支逻辑判断,不会死循环。
_gcd("0",nan) 函数再次执行时返回值是 _gcd(nan,nan),然后 Nginx worker 开始陷入死循环,进程 CPU 100%。
问题分析为何故障刚发生时无法登陆内网后台?
事后复盘发现,用户在登录内网鉴权系统时,鉴权系统会跳转到多个域名下种登录的 Cookie,其中一个域名是由故障的 SLB 代理的,受 SLB 故障影响当时此域名无法处理请求,导致用户登录失败。流程如下:
图片事后我们梳理了办公网系统的访问链路,跟用户链路隔离开,办公网链路不再依赖用户访问链路。
为何多活 SLB 在故障开始阶段也不可用?
多活 SLB 在故障时因 CDN 流量回源重试和用户重试,流量突增 4 倍以上,连接数突增 100 倍到 1000W 级别,导致这组 SLB 过载。后因流量下降和重启,逐渐恢复。此 SLB 集群日常晚高峰 CPU 使用率 30% 左右,剩余 Buffer 不足两倍。如果多活 SLB 容量充足,理论上可承载住突发流量, 多活业务可立即恢复正常。此处也可以看到,在发生机房级别故障时,多活是业务容灾止损最快的方案,这也是故障后我们重点投入治理的一个方向。
为何在回滚 SLB 变更无效后才选择新建源站切量,而不是并行?
我们的 SLB 团队规模较小,当时只有一位平台开发和一位组件运维。在出现故障时,虽有其他同学协助,但 SLB 组件的核心变更需要组件运维同学执行或 review,所以无法并行。
为何新建源站切流耗时这么久?
我们的公网架构如下:
此处涉及三个团队:
SLB 团队:选择 SLB 机器、SLB 机器初始化、SLB 配置初始化
四层 LB 团队:SLB 四层 LB 公网 IP 配置
CDN 团队:CDN 更新回源公网 IP、CDN 切量
SLB 的预案中只演练过 SLB 机器初始化、配置初始化,但和四层 LB 公网 IP 配置、CDN 之间的协作并没有做过全链路演练,元信息在平台之间也没有联动,比如四层 LB 的 Real Server 信息提供、公网运营商线路、CDN 回源 IP 的更新等。所以一次完整的新建源站耗时非常久。在事故后这一块的联动和自动化也是我们的重点优化方向,目前一次新集群创建、初始化、四层 LB 公网 IP 配置已经能优化到 5 分钟以内。
后续根因定位后证明关闭 jit 编译并没有解决问题,那当晚故障的 SLB 是如何恢复的?
周三(2024年3月6日),A股市场早盘低开,午后开始反弹。上证指数涨上证指数跌0.26%,报3039...
2 金股挖掘| 绑定大众集团实现业务腾飞,电车时代来临,这家车2023年我国汽车产业发展取得突破性进展,全年产销均超3000万辆,创历史新高,汽车出口首次跃...
3 调研早知道| 自有品牌战略进入全面收获期,这家企业海外市场界面新闻记者 | 袁颖琪 跟随着我国白电“走出去”的步伐,有一家企业的优势正日益凸显。这...
4 盘中必读|今日共105股涨停,三大指数小幅下跌,新质生产力概念3月6日,大盘午后震荡回落,三大指数均小幅下跌。截至收盘,沪指跌0.26%,深成指跌0.22%,创...
5 重大事项停牌前一度大涨17%,“量子通信第一股”国盾量子发生界面新闻记者 | 冯雨晨 一番大涨之后,国盾量子(688027 .SH )宣布筹划重大事项停牌,引起市...
周三(2024年3月6日),A股市场早盘低开,午后开始反弹。上证指数涨上证指数跌0.26%,报3039...
2 金股挖掘| 绑定大众集团实现业务腾飞,电车时代来临,这家车2023年我国汽车产业发展取得突破性进展,全年产销均超3000万辆,创历史新高,汽车出口首次跃...
3 调研早知道| 自有品牌战略进入全面收获期,这家企业海外市场界面新闻记者 | 袁颖琪 跟随着我国白电“走出去”的步伐,有一家企业的优势正日益凸显。这...
4 盘中必读|今日共105股涨停,三大指数小幅下跌,新质生产力概念3月6日,大盘午后震荡回落,三大指数均小幅下跌。截至收盘,沪指跌0.26%,深成指跌0.22%,创...
5 重大事项停牌前一度大涨17%,“量子通信第一股”国盾量子发生界面新闻记者 | 冯雨晨 一番大涨之后,国盾量子(688027 .SH )宣布筹划重大事项停牌,引起市...
撤稿申请|
备案号:鄂ICP备2022006215号 Copyright © 2002-2022 metaversezj.com.cn 元宇宙之家 版权所有