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是怎么进去的?
官方说法是:
bug如何定位?在某种发布模式下,应用程序的实例权重将短暂调整为0,注册中心将返回SLB(负载均衡)权重为字符串类型的0。本发布环境仅用于生产环境,使用频率极低SLB这个问题在早期灰度过程中没有触发。
SLB在balance_by_lua在共享内存中保存的服务阶段IP、Port、Weight作为参数传递lua-resty-balancer选择模块upstream server,在节点weight=“0”时,balancer模块中的_gcd函数收到的入参b可能是0。
从事后诸葛亮的角度来看,造成哔哩哔哩全面崩溃的根本原因有点仅此而已。
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,你想吐槽什么?
参考链接:
https://mp.weixin.qq.com/s/nGtC5lBX_Iaj57HIdXq3Qg
— 完 —量子位 QbitAI · 头条号签约关注我们,第一次了解前沿科技动态