• 7 行代码搞崩溃 B 站,原因令人唏嘘!


    △点击上方“Python猫”关注 ,回复“1”领取电子书

    f6a5f82ebc7b0ce70c8ec70add25a63e.jpeg

    作者:豌豆花下猫

    来源:Python猫

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

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

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

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

    9c7242f66121f2d194aeef12f9dc5a10.jpeg

    简单而言,这个函数预期接收的参数是两个数字(普通的数字或者字符串类型的数字,即两种类型都可以),然而,它的 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'

    3463933c6be6dc1a83396a49edfd9a56.jpeg

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

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

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

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

    7f272e4f535b618da31a289cda405153.jpeg

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

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

    18749b26962abae1a089da513ba6f2d6.jpeg

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

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

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

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

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

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

    a94d7a383a70b823e7c895c1bae6cbd2.jpeg

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

    27d4206a0577cee668e6cb185818f7f1.jpeg

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

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

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

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

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

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

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

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

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

    b256c44449e404095759e3933ab16de8.gif

    Python猫技术交流群开放啦!群里既有国内一二线大厂在职员工,也有国内外高校在读学生,既有十多年码龄的编程老鸟,也有中小学刚刚入门的新人,学习氛围良好!想入群的同学,请在公号内回复『交流群』,获取猫哥的微信(谢绝广告党,非诚勿扰!)~

    还不过瘾?试试它们

    Python 为什么能支持任意的真值判断?

    Python到底是强类型语言,还是弱类型语言?

    Python猫 2021 文章小结,翻译竟比原创多!

    Python 为什么不设计 do-while 循环结构?

    Python 之父汇报进展:CPython 3.11 比 3.10 快 25%

    从 Python 之父的对话聊起,关于知识产权、知识共享与文章翻译

    如果你觉得本文有帮助

    请慷慨分享点赞,感谢啦

  • 相关阅读:
    关于自己DIY配置电脑
    saltstack学习2grains&pillar
    A* AcWing 178. 第K短路
    江西股权转让项目:萍乡市天子山铁矿有限公司32%股权转让
    Win11如何开启剪贴板自动复制?Win11开启剪贴板自动复制的方法
    mysql主从和mycat读写分离的安装及验证
    Java中的线程
    Java中的内存泄漏及其排查方法
    前端性能测试工具WebPagetest
    2022.8.26小结 ABAP 语法记录
  • 原文地址:https://blog.csdn.net/chinesehuazhou2/article/details/126066308