前不久,哔哩哔哩(一般常称为 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 行问题代码中,作为吃瓜群众的咱们,能得到些什么收获呢?到底是涨见识了,仍是“又学废了”呢?
以上便是本次共享的一切内容,假如你觉得文章还不错,欢迎关注大众号:Python编程学习圈,每日干货共享,内容掩盖Python电子书、教程、数据库编程、Django,爬虫,云计算等等。或是前往编程学习网,了解更多编程技能知识。