win7系统奔溃

(win7系统崩溃怎么办?

鱼羊 丰色 发自 凹非寺
量子位 | 公众号 QbitAI

一个小字符0实际上导致了B站的全面崩溃。

我不知道你是否还记得那天晚上,B站在大楼停电、服务器爆炸、程序员删跑路的彻夜狂欢。(手动狗头)

时隔一年,背后的真凶终于被阿B披露了——

没想到,就是这么简单的几行代码,直接干了B站两三个小时,让B站程序员彻夜无眠,头发疯了。

你可能会问,这不是一个普通的函数来寻求最大的公约数吗?为什么它如此强大?

归根结底,背后的一个接一个,其实是一句话:0,它真的不兴除啊。

让我们来看看事故报告。

字符串“0”引发的“血案”

先说说悲剧的根本原因,也就是开头贴的。gcd函数

学过一点编程知识的朋友应该知道,这是一种用途辗转相除法计算最大公约数递归函数

不同于我们手算最大公约数的方法,这个算法是酱阿姨的:

举个简单的例子,a=24,b=18,求a和b最大公约数;

a除以b,得到的余数是6,然后让a=18,b=然后往下算;

18除以6,这次余数为0,所以6是24和18的最大公约数。

也就是说,a和b反复相除取余数,直到b=0,函数中:

if b==0 then return a end

这个判断句子有效,结果就算出来了。

基于这样的数学原理,我们来看看这个代码,似乎没有问题:

但是如果输入的b是字符串0呢?

B站技术分析文章提到,事故代码是使用的Lua写的。Lua有几个特点:

这是一种动态语言,常用的习惯变量不需要定义类型,直接赋值变量。Lua算术操作数字字符串时,会尝试将数字字符串转换成数字。在Lua语言中,数学运算n%0的结果是nan(Not A Number)

让我们模拟一下这个过程:

1.当b是字符串0时,因为这个gcd函数没有验证其类型,所以0不等于0,代码中的return _gcd(b, a%b)触发,返回_gcd(“0”, nan)。

2、_gcd(“0”, nan)再次执行,返回值变成_gcd(nan, nan)。

这就结束了小牛,判断句子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。

bug如何定位?

从事后诸葛亮的角度来看,造成哔哩哔哩全面崩溃的根本原因有点仅此而已。

bug如何定位?

从事后诸葛亮的角度来看,造成哔哩哔哩全面崩溃的根本原因有点仅此而已。

但从程序员的角度来看,事情真的不辣吗?

当晚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到100万,多活SLB就给整过载了。

然而,并非所有的服务都有多活架构,到目前为止还没有完全解决。

在接下来的半个小时里,每个人都做了很多操作,并在过去两周左右上线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站此次前所未见的大崩溃事件。

递归锅还是弱型语言锅?

许多网民仍然记得这次事故。他们回忆说,他们认为手机不能更换电脑。有些人还记得五分钟后,这件事就被热搜了。

大家都很惊讶,这么简单的死循环会导致这么大的网站崩溃。

然而,有人指出,死循环并不少见,而是罕见的SLB在分发过程中出现问题,不像在后台出现问题,很快就能重启解决。

为避免这种情况, 有些人认为应该谨慎使用递归,坚持使用或设置计数器,直接达到业务不可能达到的值return掉。

还有人认为这不怪递归,主要是弱语言的锅。

这也导致了诡计多端的‘0’这搞笑。

另外,因为事故耽误了太久,事情太多,B站给所有用户补了一天大会员。

有人在这里算了一笔账,说是这七行代码,让哔哩哔哩老板一下子亏了1.575万元左右。(手动狗头)

对于这个bug,你想吐槽什么?
参考链接:

[1]《2021.07.13 我们是这样崩溃的。by 比利技术

https://mp.weixin.qq.com/s/nGtC5lBX_Iaj57HIdXq3Qg

— 完 —

量子位 QbitAI · 头条号签约关注我们,第一次了解前沿科技动态