一个小小字符“0”
,行代竟引得B站全面崩溃。码让 不知你是崩溃否还记得那一夜 ,B站“大楼停电”、竟因计多“服务器爆炸”、个诡“程序员删库跑路”的行代彻夜狂欢。(手动狗头) 时隔一年,码让背后“真凶”现在终于被阿B披露出来—— 没想到吧 ,崩溃就是竟因计多这么简单几行代码,直接干趴B站两三个小时
,个诡搞得B站程序员彻夜无眠头发狂掉。行代 你可能会问,码让这不就是崩溃个普普通通用来求最大公约数的香港云服务器函数吗
,怎么就有如此大的竟因计多威力? 背后一桩桩一件件,归根结底其实就一句话:0 ,个诡它真的不兴除啊。 具体详情,咱们还是一起来看看“事故报告”。 先来说道说道引发惨案的根本原因,也就是开头贴出的这个gcd函数
。 学过一点编程知识的源码库小伙伴应该都知道,这是一种用辗转相除法来计算最大公约数的递归函数 。 跟我们手算最大公约数的方法不同,这个算法是酱婶的
: 也就是说,a和b反复相除取余数,直到b=0,函数中: if b==0 then return a end 这个判断语句生效
,结果就算出来了。 基于这样的亿华云数学原理,我们再来看这段代码,似乎没什么问题 : 但如果输入的b是个字符串“0”呢? B站的技术解析文章中提到,这段出事的代码是用Lua写的。Lua具有这么几个特点
: 我们来模拟一下这个过程: 这下就完犊子了,判定语句中b=0的云计算条件永远没法达到 ,于是,死循环出现了
。 也就是说
,这个程序开始疯狂地原地转圈,并且为了一个永远得不到的结果,把CPU占了个100%,别的用户请求自然就处理不了了
。 那么问题来了 ,这个“0”它到底是怎么进去的呢? 官方说法是: 在某种发布模式中,应用的实例权重会短暂地调整为0
,此时注册中心返回给SLB(负载均衡)的权重是字符串类型的“0”。此发布环境只有生产环境会用到
,同时使用的频率极低,在SLB前期灰度过程中未触发此问题。 SLB在balance_by_lua阶段
,会将共享内存中保存的服务IP
、Port
、Weight作为参数传给lua-resty-balancer模块用于选择upstream server,在节点weight=“0”时
,balancer模块中的_gcd函数收到的入参b可能为“0”。 以“事后诸葛亮”的视角来看 ,这个引发B站全面崩溃的根本原因多少有点让人直呼“就这”。 但从当事程序员的视角来看 ,事情确实没有辣么简单 。 当天晚上22:52分——大部分程序员才刚下班或者还没下班的节骨眼(doge) ,B站运维收到服务不可用的报警
,第一时间怀疑机房 、网络
、四层LB、七层SLB等基础设施出现问题。 然后立马和相关技术人员拉了个紧急语音会议开始处理。 5分钟后,运维发现承载全部在线业务的主机房七层SLB的CPU占用率达到了100%
,无法处理用户请求 ,排除其他设施后,锁定故障为该层。 (七层SLB是指基于URL等应用层信息的负载均衡。负载均衡通过算法把客户请求分配到服务器集群
,从而减少服务器压力
。) 万般紧急之时
,小插曲还现了:远程在家的程序员登上VPN却没法进入内网,只好又去call了一遍内网负责人
,走了个绿色通道才全部上线(因为其中一个域名是由故障的SLB代理的) 。 此时已经过去了25分钟
,抢修正式开始 。 首先,运维先热重启了一遍SLB
,未恢复;然后尝试拒绝用户流量冷重启SLB,CPU依然100%
,还是未恢复。 接着,运维发现多活机房SLB请求大量超时,但CPU未过载
,正准备重启多活机房SLB时,内部群反应主站服务已恢复
,视频播放
、推荐
、评论、动态等功能已基本正常
。 此时是23点23分,距离事故发生31分钟
。 值得一提的是
,这些功能恢复其实是事发之时被网友们吐槽的“高可用容灾架构”发挥了作用
。 至于这道防线为啥一开始没发挥作用,里头可能还有你我一点锅。 简单来说,就是大家伙点不开B站就开始疯狂刷新,CDN流量回源重试 + 用户重试 ,直接让B站流量突增4倍以上,连接数突增100倍到千万级别
,多活SLB就给整过载了。 不过
,并不是所有服务都搞了多活架构,至此事情并没完全解决。 接下来的半个小时里,大家做了很多操作
,回滚了最近两周左右上线的Lua代码,都没把剩余的服务恢复。 时间来到了12点,没有办法了
,“先不管bug是怎么出来的
,把服务全恢复了再说”。 简单+粗暴
:运维直接耗时一小时重建了一组全新的SLB集群 。 凌晨1点,新集群终于建好
: 在他们用分析工具跑出一份详细的火焰图数据后
,那个搞事的“0”才终于露出了一点端倪: CPU热点明显集中在一个对lua-resty-balancer模块的调用中 。而该模块的_gcd函数在某次执行后返回了一个预期外的值 :NaN。 同时 ,他们也发现了触发诱因的条件
:某个容器IP的weight=0。 他们怀疑是该函数触发了jit编译器的某个bug,运行出错陷入死循环导致SLB CPU 100%。 于是就全局关闭了jit编译,暂时规避了风险。一切都解决完后
,已经快4点
,大家终于暂时睡了个好觉。 第二天大家也没闲着,马不停蹄地在线下环境复现了bug后
,发现并不是jit编译器的问题
,而是服务的某种特殊发布模式会出现容器实例权重为0的情况,而这个0是个字符串形式。 正如前面所说,这个字符串“0”在动态语言Lua中的算术操作中
,被转成了数字
,走到了不该走的分支,造成了死循环,引发了b站此次前所未见的大崩溃事件 。 不少网友都还对这次事故记忆犹新,有回想起自己就是以为手机不行换电脑也不行的,也有人还记得当时5分钟后此事就上了热搜
。 大家都很诧异,就这么一个简单的死循环就能造成如此大的网站崩服。 不过
,有人指出,死循环不罕见,罕见的是在SLB层、在分发过程出问题,它还不像在后台出问题很快能重启解决。 为了避免这种情况发生, 有人认为要慎用递归,硬要用还是设置一个计数器,达到一个业务不太可能达到的值后直接return掉。 还有人认为这不怪递归,主要还是弱类型语言的锅。 以此还导致了“诡计多端的‘0’”这一打趣的说法 。 另外,由于事故实在是耽误了太久、太多事儿
,当时B站给所有用户补了一天大会员。 有人就在此算了一笔账 ,称就是这7行代码 ,让b站老板一下亏了大约1,5750,0000元 。(手动狗头) 对于这个bug,你有什么想吐槽的?





