前不久,哔哩哔哩(一般常称为 B 站)发布了一篇文章《2021.07.13 我们是这样崩的》,详细回顾了他们在 2021.07.13 晚上全站崩溃约 3 小时的至暗时刻,以及万分紧张的故障定位与恢复过程。

那篇文章将定位过程、问题分析、优化改进等方面写得很详细,在我印象中,国内互联网大厂在发生类似事故后,能够如此开诚布公地“检讨”“还债”的并不多见。(值得送上一键三连~~~)

对于搞技术的同学来说,这篇文章是不错的学习材料。而我最为关注的内容,其实是关于编程语言的特性,也就是在代码层面上的细节问题。

在关于问题根因的分析中,我们看到了罪魁祸首的 7 行代码,它是用 Lua 语言写的一个求最大公约数的函数:

简单而言,这个函数预期接收的参数是两个数字(普通的数字或者字符串类型的数字,即两种类型都可以),然而,它的 if 语句却只判断了一种类型(普通数字),忽略了字符串类型的“0”。

在故障发生时,它的第二个参数传入的是字符串类型“0”而不是数字类型 0,导致 if 语句判断失效!

由于 Lua 是动态类型语言,只有在程序运行时才知道传入的参数是什么类型。这属于是所有动态类型语言的特色,在 Python、JavaScript、PHP、Ruby 等动态类型语言中,也会有同样的表现。这不是啥新鲜事物。

然而,真正该死的问题在于,Lua 还是一门弱类型语言,它不像 Python、Ruby、Java 等强类型语言那样,它竟支持隐式类型转换!

在 Lua 中,数字字符串在与普通数字作算术运算时,会将字符串类型隐式地转换成数字类型,如上图所示的“a % b”,如果 b 是字符串类型的数字,那它就会被转换成数字类型!

而在 Python 这种强类型动态类型语言中,这样的转换是不可思议的,数字与字符串作算术运算,能得到的只会是报错:TypeError: unsupported operand type(s) for %: 'int' and 'str'

Lua 语言的这种“字符串隐式变数字”的行为,即使在大意不察觉的情况下,似乎也不会造成太大问题。在 B 站代码中,除了出事故时传的字符串“0”以外,估计它一直接收的都是其它字符串数字,一直也没出问题,显然程序员是把这当成一种便利手段了(因为不需作类型转换)。

然而,不幸的是,Lua 中还有一个特殊的“nan”,它会进一步将这一个“小小的错误”传递下去,直至传到了地老天荒不受控制的死循环里……

在大多数编程语言中,除零操作都是不可饶恕的错误,这跟我们在小学数学课堂上就掌握的常识相吻合:数字零不允许作为除数

掏出手机,打开计算器,看看它是怎么说的:

看到了吧!不能除以0!!!

继续看看 Python 对于这种操作的反应:

ZeroDivisionError 除零错误,这是在捍卫我们根深蒂固的数学常识。

那么,Lua 语言在除零操作后得到的 nan 到底是个什么东西呢?

nan 一般也被称为“NaN”,是“No a Number”的缩写,表示“不是一个数”。它来头不小,是在 1985 年的 IEEE 754 浮点数标准中首次引入的。

直白地讲,它也是数字类型中的一个值,但是表示的是一个“不可表示的值”。也就是说,它表示的是一个非常抽象概念的数。

也许我们比较容易理解另一个抽象的数“无穷大”,因为在中学数学课上就经常接触到,而 nan 也是类似的一种特殊的数,只不过它较为少用且更难以捉摸罢了。

Python 中也有这两个数的存在,即 float('inf') 表示无穷大、float('nan') 表示非数。它们就像是两个黑洞,会吞噬掉任何试图前来“搭讪”的数:

那么,当这两个黑洞相互靠近时,谁的引力更大些呢?请看示例:

看来还是 nan 的优先级更高一筹啊。

然而,尽管 Python 中有 nan,但它并不因为这个数而抛弃前文提到的常识。而同为脚本语言的 Lua 却抛弃了常识, 在出现除零这种非法操作时,它不是报错,而是得到 nan 的结果。

这样的特性简直是自由得过分,也许在某些时候会挺有用吧,但它也会埋下未知的隐患。

回到 B 站的问题代码,弱类型的 Lua 语言由于太过自由,它放行了字符串数字与普通数字的运算,又因为对 nan 过于自由的使用,它放行了数字除零的操作,两次的放行,使得短短几行代码一路畅行不止,一路消耗服务器资源,直到 CPU 100%,直到牵动服务集群故障,直到高可用的多活机房服务不可用,导致全站崩溃 3 小时的事故……

当然了,如果当初写下这段代码的程序员多加一个条件判断,这一次的事故就完全可以避免。从另外的视角看,这就是程序员在递归程序的终止条件上处理不当,不能甩锅给编程语言那两项自由不羁的语言特性。

但是,我相信写下那段代码的程序员大概率是长期使用其它编程语言,现学现卖上手写 Lua,尽管知道 Lua 语言动态弱类型的特点,但思维习惯上仍深受其它语言影响,这才“一时失足、小河翻船”……程序员内心有苦说不出!!

短短的 7 行代码,说简单就简单,说不简单也不简单。本文就不展开说辗转相除法求最大公约数了(说来话长),单单是前面提及的隐式类型转换加上除零得 nan 的细节问题,就足够导致一场大事故了。

从 7 行问题代码中,作为吃瓜群众的我们,能得到些什么收获呢?到底是涨见识了,还是“又学废了”呢?

人生苦短,不求无 Bug,但求读者老爷们赏个一键三连吧~~~

7 行代码搞崩溃 B 站,原因令人唏嘘!的更多相关文章

  1. 30行代码搞定WCF并发性能测试

    [以下只是个人观点,欢迎交流] 30行代码搞定WCF并发性能 轻量级测试. 1. 调用并发测试接口 static void Main()         {               List< ...

  2. 10行代码搞定移动web端自定义tap事件

    发发牢骚 移动web端里摸爬滚打这么久踩了不少坑,有一定移动web端经验的同学一定被click困扰过.我也不列外.一路走来被虐的不行,fastclick.touchend.iscroll什么的都用过, ...

  3. [Unity Editor]10行代码搞定Hierarchy排序

    在日常的工作和研究中,当给我们的场景摆放过多的物件的时候,Hierarchy面板就会变得杂乱不堪.比如这样:    过多的层次结构充斥在里面,根层的物件毫无序列可言,整个层次面板显示非常的杂乱不堪,如 ...

  4. BaseHttpListActivity,几行代码搞定Android Http列表请求、加载和缓存

    Android开发中,向服务器请求一个列表并显示是非常常见的需求,但实现起来比较麻烦,代码繁杂. 随着应用的更新迭代,这种需求越来越多,我渐渐发现了实现这种需求的代码的共同点. 于是我将Activit ...

  5. python爬煎蛋妹子图--20多行代码搞定煎蛋妹子图库

    如果说一个人够无聊的话... 就会做一些十分美(wei)丽(suo)的事情啦哈哈哈... 好的,话不多说,进入正题. 正如标题所示,我们今天的目标很简单: 代码要少,妹子要好. 步骤如下: 1. 首先 ...

  6. 100行代码搞定抖音短视频App,终于可以和美女合唱了。

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由视频咖 发表于云+社区专栏 本文作者,shengcui,腾讯云高级开发工程师,负责移动客户端开发 最近抖音最近又带了一波合唱的节奏,老 ...

  7. 如何用Python统计《论语》中每个字的出现次数?10行代码搞定--用计算机学国学

    编者按: 上学时听过山师王志民先生一场讲座,说每个人不论干什么,都应该学习国学(原谅我学了计算机专业)!王先生讲得很是吸引我这个工科男,可能比我的后来的那些同学听课还要认真些,当然一方面是兴趣.一方面 ...

  8. python入门机器学习,3行代码搞定线性回归

    本文着重是重新梳理一下线性回归的概念,至于几行代码实现,那个不重要,概念明确了,代码自然水到渠成. “机器学习”对于普通大众来说可能会比较陌生,但是“人工智能”这个词简直是太火了,即便是风云变化的股市 ...

  9. 7行代码搞定WEB服务

    作为一个 Java 程序猿,写代码久了,各种技术也就都尝试了一个遍. 先从 SSH1(Spring.Struts1.Hibernate)摸爬滚打转变到 SSH2(Spring.Struts2.Hibe ...

随机推荐

  1. HCNP Routing&Switching之MSTP

    前文我们了解了RSTP保护相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/16255918.html:今天我们来了解下MSTP相关话题: MSTP技术背 ...

  2. php魔术方法小结

    php魔术方法 __construct() __construct(mixed ...$values = ""): void PHP 允许开发者在一个类中定义一个方法作为构造函数. ...

  3. 一、全新安装搭建redis主从集群

    前言· 这里分为三篇文章来写我是如何重新搭建redis主从集群和哨兵集群的及原本服务器上有单redis如何通过升级脚本来实现redis集群.(redis结构:主-从(备)-从(备)) 至于为什么要搭建 ...

  4. cefsharp + winform 内嵌网页的触屏输入焦点问题

    原文 现象 我正在使用 cefsharp + winform 建立一个桌面程序用于显示网页.但程序启动后触屏点击网页中的输入框,使用键盘输入,文字输入不进去.win + D 最小化程序后,再恢复窗口才 ...

  5. 12┃音视频直播系统之 WebRTC 实现1对1直播系统实战

    一.搭建 Web 服务器 前面我们已经实现过,但是没有详细说HTTPS服务 首先需要引入了 express 库,它的功能非常强大,用它来实现 Web 服务器非常方便 同时还需要引入 HTTPS 服务, ...

  6. MongoDB 分片规则

    每日一句 生命本身毫无意义,只有死亡才能让你邃晓人性的真谛! 每日一句 Ideal is the beacon. Without ideal, there is no secure direction ...

  7. 聚类--DBSCN

    1.什么是DBSCN DBSCAN也是一个非常有用的聚类算法. 它的主要优点:它不需要用户先验地设置簇的个数,可以划分具有复杂形状的簇,还可以找出不属于任何簇的点. DBSCAN比凝聚聚类和k均值稍慢 ...

  8. DYOJ 【20220303模拟赛】最少分组 题解

    最少分组 题意 \(n\) 个点 \(m\) 条边的无向图,可以删掉 0 条或多条边,求满足条件的最小连通块数量: 对每个顶点对 \((a,b)\) ,若 \(a\) 和 \(b\) 同属于一个连通块 ...

  9. c++ 超大整数除法 高精度除法

    c++ 超大整数除法 高精度除法 解题思路 计算a/b,其中a为大整数,b为普通整数,商为c,余数为r. 根据手算除法的规则,上一步的余数记为r,则本次计算的被除数为t=r*10+被除数的本位数值a[ ...

  10. 第五章、Linux网络服务之yum仓库

    目录 一.yum仓库简介 二.yum配置文件 1yum主配置文件 2日志文件 三.yum命令详解 1查询软件包命令 2查询软件包组命令 3yum安装升级 4 软件卸载 四.搭建yum仓库 本地仓库 h ...