0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

算法与数据结构 来源:AI新媒体量子位 作者:AI新媒体量子位 2022-08-03 11:26 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

一个小小字符“0”,竟引得B站全面崩溃。

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

时隔一年,背后“真凶”现在终于被阿B披露出来——

376e1b9e-12db-11ed-ba43-dac502259ad0.png

没想到吧,就是这么简单几行代码,直接干趴B站两三个小时,搞得B站程序员彻夜无眠头发狂掉

你可能会问,这不就是个普普通通用来求最大公约数的函数吗,怎么就有如此大的威力?

背后一桩桩一件件,归根结底其实就一句话:0,它真的不兴除啊。

具体详情,咱们还是一起来看看“事故报告”。

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

先来说道说道引发惨案的根本原因,也就是开头贴出的这个gcd函数

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

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

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

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

18除以6,这回余数是0,那么6也就是24和18的最大公约数了。

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

if b==0 then return a end

这个判断语句生效,结果就算出来了。

基于这样的数学原理,我们再来看这段代码,似乎没什么问题:

376e1b9e-12db-11ed-ba43-dac502259ad0.png

但如果输入的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是如何定位的

以“事后诸葛亮”的视角来看,这个引发B站全面崩溃的根本原因多少有点让人直呼“就这”。

但从当事程序员的视角来看,事情确实没有辣么简单。

当天晚上22:52分——大部分程序员才刚下班或者还没下班的节骨眼(doge),B站运维收到服务不可用的报警,第一时间怀疑机房、网络、四层LB、七层SLB等基础设施出现问题。

然后立马和相关技术人员拉了个紧急语音会议开始处理。

5分钟后,运维发现承载全部在线业务的主机房七层SLB的CPU占用率达到了100%,无法处理用户请求,排除其他设施后,锁定故障为该层。

(七层SLB是指基于URL等应用层信息的负载均衡。负载均衡通过算法把客户请求分配到服务器集群,从而减少服务器压力。)

万般紧急之时,小插曲还现了:远程在家的程序员登上VPN却没法进入内网,只好又去call了一遍内网负责人,走了个绿色通道才全部上线(因为其中一个域名是由故障的SLB代理的)

37980cb0-12db-11ed-ba43-dac502259ad0.png

此时已经过去了25分钟,抢修正式开始。

首先,运维先热重启了一遍SLB,未恢复;然后尝试拒绝用户流量冷重启SLB,CPU依然100%,还是未恢复。

接着,运维发现多活机房SLB请求大量超时,但CPU未过载,正准备重启多活机房SLB时,内部群反应主站服务已恢复,视频播放、推荐、评论、动态等功能已基本正常。

此时是23点23分,距离事故发生31分钟

值得一提的是,这些功能恢复其实是事发之时被网友们吐槽的“高可用容灾架构”发挥了作用。

37a6903c-12db-11ed-ba43-dac502259ad0.png

至于这道防线为啥一开始没发挥作用,里头可能还有你我一点锅。

简单来说,就是大家伙点不开B站就开始疯狂刷新,CDN流量回源重试 + 用户重试,直接让B站流量突增4倍以上,连接数突增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站此次前所未见的大崩溃事件。

递归的锅还是弱类型语言的锅?

不少网友都还对这次事故记忆犹新,有回想起自己就是以为手机不行换电脑也不行的,也有人还记得当时5分钟后此事就上了热搜。

大家都很诧异,就这么一个简单的死循环就能造成如此大的网站崩服。

不过,有人指出,死循环不罕见,罕见的是在SLB层、在分发过程出问题,它还不像在后台出问题很快能重启解决。

37b0daa6-12db-11ed-ba43-dac502259ad0.png

为了避免这种情况发生, 有人认为要慎用递归,硬要用还是设置一个计数器,达到一个业务不太可能达到的值后直接return掉。

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

以此还导致了“诡计多端的‘0’”这一打趣的说法。

37bc89e6-12db-11ed-ba43-dac502259ad0.png

另外,由于事故实在是耽误了太久、太多事儿,当时B站给所有用户补了一天大会员。

有人就在此算了一笔账,称就是这7行代码,让b站老板一下亏了大约1,5750,0000元。(手动狗头)

37caf58a-12db-11ed-ba43-dac502259ad0.png

对于这个bug,你有什么想吐槽的?

参考链接:

[1]《2021.07.13 我们是这样崩的》by 哔哩哔哩技术
https://mp.weixin.qq.com/s/nGtC5lBX_Iaj57HIdXq3Q

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 字符串
    +关注

    关注

    1

    文章

    594

    浏览量

    23044
  • 函数
    +关注

    关注

    3

    文章

    4406

    浏览量

    66858
  • 代码
    +关注

    关注

    30

    文章

    4942

    浏览量

    73160

原文标题:7行代码让B站崩溃3小时

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    飞凌嵌入式ElfBoard-标准IO接口之格式化输出

    ( const char *format, ... );3)参数format:表示C 字符串,包含了要打印的格式化数据。...:表示附加可变参数,根据不同的 format 字符串,函数可能需要一系列的附加
    发表于 11-11 08:43

    如何使用 NuMaker 板和 Mbed OS 上的连接字符串连接到 Azure IoT?

    使用 NuMaker 板和 Mbed OS 上的连接字符串连接到 Azure IoT
    发表于 09-04 07:46

    LM3466 多 LED 电流平衡器技术手册

    到电源的数或每个 LED 的正向电压 字符串。 如果任何 LED 灯在运行过程中打开,LM3466 会自动平衡通过所有剩余活动 LED 灯的电源电流。 如 因此,即使一些 LED
    的头像 发表于 08-29 14:27 858次阅读
    LM3466 多<b class='flag-5'>串</b> LED 电流平衡器技术手册

    labview如何生成一个带字符串返回的dll

    labview如何生成一个dll,如下图,要求一个输入,类型是字符串,返回类型也是字符串
    发表于 08-28 23:20

    在Python中字符串逆序有几种方式,代码是什么

    对于一个给定的字符串,逆序输出,这个任务对于python来说是一种很简单的操作,毕竟强大的列表和字符串处理的一些列函数足以应付这些问题 了,今天总结了一下python中对于字符串的逆序输出的几种常用
    的头像 发表于 08-28 14:44 788次阅读

    SQL 通用数据类型

    如何与存储的数据进行交互。 下面的表格列出了 SQL 中通用的数据类型: 数据类型 描述 CHARACTER(n) 字符/字符串。固定长度 n。 VARCHAR(n) 或 CHARACTER VARYING(n) 字符/
    的头像 发表于 08-18 09:46 579次阅读

    harmony-utils之StrUtil,字符串工具类

    harmony-utils之StrUtil,字符串工具类 harmony-utils 简介与说明 [harmony-utils] 一款功能丰富且极易上手的HarmonyOS工具库,借助众多实用工具类
    的头像 发表于 07-03 11:32 379次阅读

    STM32C031C6使用的是UART2通讯,通过printf()函数发送字符串时,汉字错码怎么解决?

    使用的是UART2通讯,通过printf()函数发送字符串时,汉字错码(见下图),应该是KEIL哪里没有设置好的问题。 启用了UART2的中断接收,可以接收到串口调试助手的数据,但是缓存区的指针没有归零,下次接收时缓存区中的内容接续(如下图所示),不知道用什么命令来清除缓存区(即让指针归零)。
    发表于 03-07 12:30

    请问如何用Verilog实现将ascaii码数值字符串转换成定点数?

    我需要接收一个ascaii码字符串,内容是12位有效数字的数值,带小数。我不知道怎么把小数部分转换成定点数。
    发表于 01-23 21:57

    字符串在数据库中的存储方式

    数据库是现代信息技术中存储和管理数据的核心组件。字符串作为最常见的数据类型之一,在数据库中的存储方式对其性能和可扩展性有着重要影响。 数据类型 固定长度字符串 :如CHAR类型,它为每个字符串分配
    的头像 发表于 01-07 15:41 1259次阅读

    字符串在编程中的应用实例

    字符串在编程中有着广泛的应用,它们被用于表示文本数据、处理用户输入、构建动态内容等。以下是一些字符串在编程中的应用实例: 1. 用户输入与输出 用户输入 :程序通常需要从用户那里获取输入,这些输入通
    的头像 发表于 01-07 15:33 1141次阅读

    字符串字符数组的区别

    在编程语言中,字符串字符数组是两种基本的数据结构,它们都用于存储和处理文本数据。尽管它们在功能上有一定的重叠,但在内部表示、操作方式和使用场景上存在显著差异。 1. 内部表示 字符串 字符串
    的头像 发表于 01-07 15:29 1695次阅读

    字符串反转的实现方式

    在编程中,字符串反转是一个基础而重要的操作,它涉及到将一个字符串中的字符顺序颠倒过来。这个操作在多种编程语言中都有不同的实现方式,本文将探讨几种常见的字符串反转方法。 1. 递归方法
    的头像 发表于 01-07 15:27 1252次阅读

    字符串处理方法 字符串转数字的实现

    在编程中,将字符串转换为数字是一个常见的需求。不同的编程语言有不同的方法来实现这一功能。以下是一些常见编程语言中的字符串转数字的实现方法: Python 在Python中,可以使用内置的 int
    的头像 发表于 01-07 15:26 1410次阅读

    养成良好的编程习惯|堆内存初值不一定是0

    ;} 代码很简单,使用 malloc 申请一段堆内存,假设内存空间足够大。 通过 getchar 配合 while 循环,从标准输入获取一个字符串,直到遇到换行符结束。 最后就是把获取的字符串输出。 乍一看,好像程序没什么问题。 然后把程序运行起来,多测试几遍,也确实
    的头像 发表于 12-18 09:14 574次阅读