前不久,哔哩哔哩(一般常称为 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. 通过Swagger接口导出模板文件时报错:URL.createObjectURL: Argument 1 is not valid for any of the 1-argument overloads.

    问题描述:通过Swagger接口导出Excel模板文件时,报错:URL.createObjectURL: Argument 1 is not valid for any of the 1-argume ...

  2. linux篇-Centos7jdk安装

    2.1查看现有JDK #rpm -qa|grep jdk (如果有其他版本的JDK建议卸载) 卸载其他版本的JDK命令 #yum –y remove java-1.6.0 #yum –y remov ...

  3. MongoDB启动报错:Unrecognized option: storage try 'mongod --help' for more information(已解决)

    问题说明: 今天在使用配置文件方式启动MongoDB时,一直启动失败,报错显示:Unrecognized option: storage try 'mongod --help' for more in ...

  4. 关于我学git这档子事(3)

    对于如下报错: hint: Updates were rejected because a pushed branch tip is behind its remote hint: counterpa ...

  5. Probabilistic two-stage detection

  6. 三面阿里,被Java面试官虐哭!现场还原真实的“被虐”场景

    前言 人人都有大厂梦,我也不例外,从大三开始,就一直想进入阿里工作,大毕竟是大厂,想想也没那么容易,不过好在自己学历还过得去,项目经验也有得讲,所以今年也斗胆尝试了一下,直接就投了阿里云计算.简历是过 ...

  7. C++:最大子数组差

    最大子数组差 内存限制:128 MiB        时间限制:1000 ms 题目描述: 给定一个整数数组,找出两个不重叠的子数组A和B,使两个子数组和的差的绝对值|SUM(A) - SUM(B) ...

  8. Java ES 实现or查询

    es mapping里有三个字段: A:Integer B:Integer C:TEXT 现在想实现一个查询,来检索  (  (A =1 and B=2)  or (c like "test ...

  9. Bean Validator

    Bean Validator 关于Jakarta EE 2018年03月, Oracle 决定把 JavaEE 移交给开源组织 Eclipse 基金会,并且不再使用Java EE这个名称. 因此jav ...

  10. npm版本兼容导致的npm ERR! ERESOLVE unable to resolve dependency tree

    当团队项目中,团队成员的npm包管理工具版本不一致时执行npm install报错: npm -v查看版本信息:7.x与6.x之间的兼容问题 解决方案: 一:升级或降级npm版本,保持一致npm in ...