代码|7行代码让B站崩溃3小时 竟因“一个诡计多端的0”( 二 )


(七层SLB是指基于URL等应用层信息的负载均衡 。负载均衡通过算法把客户请求分配到服务器集群,从而减少服务器压力 。)
万般紧急之时,小插曲还现了:远程在家的程序员登上VPN却没法进入内网,只好又去call了一遍内网负责人,走了个绿色通道才全部上线(因为其中一个域名是由故障的SLB代理的) 。
代码|7行代码让B站崩溃3小时 竟因“一个诡计多端的0”
文章图片

此时已经过去了25分钟,抢修正式开始 。
首先,运维先热重启了一遍SLB,未恢复;然后尝试拒绝用户流量冷重启SLB,CPU依然100%,还是未恢复 。
接着,运维发现多活机房SLB请求大量超时,但CPU未过载,正准备重启多活机房SLB时,内部群反应主站服务已恢复,视频播放、推荐、评论、动态等功能已基本正常 。
此时是23点23分,距离事故发生31分钟 。
值得一提的是,这些功能恢复其实是事发之时被网友们吐槽的“高可用容灾架构”发挥了作用 。
代码|7行代码让B站崩溃3小时 竟因“一个诡计多端的0”
文章图片

至于这道防线为啥一开始没发挥作用,里头可能还有你我一点锅 。
简单来说,就是大家伙点不开B站就开始疯狂刷新,CDN流量回源重试 + 用户重试,直接让B站流量突增4倍以上,连接数突增100倍到千万级别,多活SLB就给整过载了 。
代码|7行代码让B站崩溃3小时 竟因“一个诡计多端的0”
文章图片

不过,并不是所有服务都搞了多活架构,至此事情并没完全解决 。
接下来的半个小时里,大家做了很多操作,回滚了最近两周左右上线的Lua代码,都没把剩余的服务恢复 。
时间来到了12点,没有办法了,“先不管bug是怎么出来的,把服务全恢复了再说” 。
简单+粗暴:运维直接耗时一小时重建了一组全新的SLB集群 。
凌晨1点,新集群终于建好:
一边,有人负责陆续将直播、电商、漫画、支付等核心业务流量切换到新集群,恢复全部服务(凌晨1点50分全部搞定,暂时结束了崩了逼近3个小时的事故);
另一边,继续分析bug原因 。
在他们用分析工具跑出一份详细的火焰图数据后,那个搞事的“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层、在分发过程出问题,它还不像在后台出问题很快能重启解决 。
代码|7行代码让B站崩溃3小时 竟因“一个诡计多端的0”
文章图片

为了避免这种情况发生,有人认为要慎用递归,硬要用还是设置一个计数器,达到一个业务不太可能达到的值后直接return掉 。
还有人认为这不怪递归,主要还是弱类型语言的锅 。
以此还导致了“诡计多端的‘0’”这一打趣的说法 。
代码|7行代码让B站崩溃3小时 竟因“一个诡计多端的0”
文章图片

另外,由于事故实在是耽误了太久、太多事儿,当时B站给所有用户补了一天大会员 。
有人就在此算了一笔账,称就是这7行代码,让b站老板一下亏了大约1,5750,0000元 。(手动狗头)


推荐阅读