B站713崩盘、复原、处理、优化全过程实录|TakinTalks推荐阅读
The following article is from 哔哩哔哩技术 Author 来还债的
著名的B站713故障想必大家都记忆犹新吧,本文详细讲述了713故障背后的故事,值得细细品味。此外本周六我们邀请了B站老师来讲讲《B站713后的多活容灾建设》,欢迎大家来直播间交流!
2021年7月13日22:52,SRE收到大量服务和域名的接入层不可用报警,客服侧开始收到大量用户反馈B站无法使用,同时内部同学也反馈B站无法打开,甚至APP首页也无法打开。基于报警内容,SRE第一时间怀疑机房、网络、四层LB、七层SLB等基础设施出现问题,紧急发起语音会议,拉各团队相关人员开始紧急处理(为了方便理解,下述事故处理过程做了部分简化)。
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,未恢复。
新建源站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%。
SLB团队:选择SLB机器、SLB机器初始化、SLB配置初始化
四层LB团队:SLB四层LB公网IP配置
CDN团队:CDN更新回源公网IP、CDN切量
多活建设
在23:23时,做了多活的业务核心功能基本恢复正常,如APP推荐、APP播放、评论&弹幕拉取、动态、追番、影视等。故障时直播业务也做了多活,但当晚没及时恢复的原因是:直播移动端首页接口虽然实现了多活,但没配置多机房调度。导致在主机房SLB不可用时直播APP首页一直打不开,非常可惜。通过这次事故,我们发现了多活架构存在的一些严重问题:
机房与业务多活定位关系混乱。 CDN多机房流量调度不支持用户属性固定路由和分片。 业务多活架构不支持写,写功能当时未恢复。 部分存储组件多活同步和切换能力不足,无法实现多活。
哪个业务做了多活? 业务是什么类型的多活,同城双活还是异地单元化? 业务哪些URL规则支持多活,目前多活流量调度策略是什么? 上述信息当时只能用文档临时维护,没有平台统一管理和编排。
多活切量依赖CDN同学执行,其他人员无权限,效率低。 无切量管理平台,整个切量过程不可视。 接入层、存储层切量分离,切量不可编排。 无业务多活元信息,切量准确率和容灾效果差。
优化多活基础组件的支持能力,如数据层同步组件优化、接入层支持基于用户分片,让业务的多活接入成本更低。 重新梳理各机房在多活架构下的定位,梳理Czone、Gzone、Rzone业务域。 推动不支持多活的核心业务和已实现多活但架构不规范的业务改造优化。
统一管控所有多活业务的元信息、路由规则,联动其他平台,成为多活的元数据中心。 支持多活接入层规则编排、数据层编排、预案编排、流量编排等,接入流程实现自动化和可视化。 抽象多活切量能力,对接CDN、存储等组件,实现一键全链路切量,提升效率和准确率。 支持多活切量时的前置能力预检,切量中风险巡检和核心指标的可观测。
SLB治理
故障前一个机房内一套SLB统一对外提供代理服务,导致故障域无法隔离。后续SLB需按业务部门拆分集群,核心业务部门独立SLB集群和公网IP。 跟CDN团队、四层LB&网络团队一起讨论确定SLB集群和公网IP隔离的管理方案。 明确SLB能力边界,非SLB必备能力,统一下沉到API Gateway,SLB组件和平台均不再支持,如动态权重的灰度能力。
SLB管理平台实现Lua代码版本化管理,平台支持版本升级和快速回滚。 SLB节点的环境和配置初始化托管到平台,联动四层LB的API,在SLB平台上实现四层LB申请、公网IP申请、节点上线等操作,做到全流程初始化5分钟以内。 SLB作为核心服务中的核心,在目前没有弹性扩容的能力下,30%的使用率较高,需要扩容把CPU降低到15%左右。 优化CDN回源超时时间,降低SLB在极端故障场景下连接数。同时对连接数做极限性能压测。
运维团队做项目有个弊端,开发完成自测没问题后就开始灰度上线,没有专业的测试团队介入。此组件太过核心,需要引入基础组件测试团队,对SLB输入参数做完整的异常测试。 跟社区一起,Review使用到的OpenResty核心开源库源代码,消除其他风险。基于Lua已有特性和缺陷,提升我们Lua代码的鲁棒性,比如变量类型判断、强制转换等。 招专业做LB的人。我们选择基于Lua开发是因为Lua简单易上手,社区有类似成功案例。团队并没有资深做Nginx组件开发的同学,也没有做C/C++开发的同学。
故障演练
本次事故中,业务多活流量调度、新建源站速度、CDN切量速度&回源超时机制均不符合预期。所以后续要探索机房级别的故障演练方案:
模拟CDN回源单机房故障,跟业务研发和测试一起,通过双端上的业务真实表现来验收多活业务的容灾效果,提前优化业务多活不符合预期的隐患。 灰度特定用户流量到演练的CDN节点,在CDN节点模拟源站故障,观察CDN和源站的容灾效果。 模拟单机房故障,通过多活管控平台,演练业务的多活切量止损预案。
应急响应
B站一直没有NOC/技术支持团队,在出现紧急事故时,故障响应、故障通报、故障协同都是由负责故障处理的SRE同学来承担。如果是普通事故还好,如果是重大事故,信息同步根本来不及。所以事故的应急响应机制必须优化:
优化故障响应制度,明确故障中故障指挥官、故障处理人的职责,分担故障处理人的压力。 事故发生时,故障处理人第一时间找backup作为故障指挥官,负责故障通报和故障协同。在团队里强制执行,让大家养成习惯。 建设易用的故障通告平台,负责故障摘要信息录入和故障中进展同步。
跟监控团队协作,建设平台控制面事件上报能力,推动更多核心平台接入。 SLB建设面向底层引擎的数据面事件变更上报和查询能力,比如服务注册信息变更时某个应用的IP更新、weight变化事件可在平台查询。 扩展事件查询分析能力,除面向应用外,建设面向不同用户、不同团队、不同平台的事件查询分析能力,协助快速定位故障诱因。
活动时间:2022年7月16日,14:00-17:00
活动地点:线上直播(infoQ视频号线上直播、B站直播间)
进群方式:扫码添加小树,回复「沙龙」进群交流