

7 行代码搞崩溃 B 站,原因令人唏嘘! - 豌豆花下猫
source link: https://www.cnblogs.com/pythonista/p/16533376.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

7 行代码搞崩溃 B 站,原因令人唏嘘!
前不久,哔哩哔哩(一般常称为 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,但求读者老爷们赏个一键三连吧~~~
Recommend
-
43
随着乐视生态的崩盘,乐视线上线下的业务都受到了无可估量的影响,有乐视线下门店竟沦为水果批发的场所。今天,虎嗅晒出一张照片显示,一家乐视生态旗舰店的大门上,赫然贴着水果批发四个大字。透过玻璃窗,也能看到内部成堆的水果包装盒,让人不禁唏嘘。
-
9
按照发布计划,Python 3.11.0 将于 2022 年 10 月 24 日发布。 据测试,3.11 相比于 3.10,将会有 10-60% 的性能提升,这个成果主要归功于“Faster CPython”项目,即“香农计划”。 关于“香农计划”的详情,可查看 Python 之父的
-
2
生鲜电商第一股现状令人唏嘘!每日优鲜全职员工仅剩55人|全品类|运营|每日优鲜公司解散_网易订阅 近日,生鲜电商每日优鲜发布2021年财报显示,2021年每日优鲜...
-
14
Python 为什么如此设计? 大概两年半前,我萌生了要...
-
11
这一次,Python 真的有望告别 GIL 锁了? Python 中...
-
10
在编程时,我们经常要作条件判断,并根据条件的结果选择执行不同的语句块。在许多编程语言中,最常见的写法是三元运算符,但是,Python 并不支持三元运算符,无独有偶,两个最热门的新兴语言 Go 和 Rust 也不支持! 为什么 Python 不支持三元运算符呢?本文将...
-
6
你好,我是豌豆花下猫。这里记录每周值得分享的 Python 及通用技术内容,部分为英文,已在小标题注明。(标题取自其中一则分享,不代表全部内容都是该主题,特此声明。) 文章&教程 1、
-
9
你好,我是猫哥。这里记录每周值得分享的 Python 及通用技术内容,部分为英文,已在小标题注明。(标题取自其中一则分享,不代表全部内容都是该主题,特此声明。) 首发于我的博客,
-
11
Python 潮流周刊第 38 期(摘要)+赠书5本 本周刊由 Pyt...
-
5
Python 潮流周刊第 39 期(摘要) 本周刊由 Python猫 出...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK