You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

86 lines
9.6 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 49 | 数据完整性(上):硬件坏了怎么办?
2012年的时候我第一次在工作中遇到一个因为硬件的不可靠性引发的Bug。正是因为这个Bug让我开始逐步花很多的时间去复习回顾整个计算机系统里面的底层知识。
当时我正在MediaV带领一个20多人的团队负责公司的广告数据和机器学习算法。其中有一部分工作就是用Hadoop集群处理所有的数据和报表业务。当时我们的业务增长很快所以会频繁地往Hadoop集群里面添置机器。2012年的时候国内的云计算平台还不太成熟所以我们都是自己采购硬件放在托管的数据中心里面。
那个时候我们的Hadoop集群服务器在从100台服务器往1000台服务器走。我们觉得像Dell这样品牌厂商的服务器太贵了而且能够提供的硬件配置和我们的期望也有差异。于是运维的同学开始和OEM厂商合作自己定制服务器批量采购硬盘、内存。
那个时候大家都听过Google早期发展时为了降低成本买了很多二手的硬件来降低成本通过分布式的方式来保障系统的可靠性的办法。虽然我们还没有抠门到去买二手硬件不过当时我们选择购买了普通的机械硬盘而不是企业级的、用在数据中心的机械硬盘采购了普通的内存条而不是带ECC纠错的服务器内存条想着能省一点儿是一点儿。
## 单比特翻转:软件解决不了的硬件错误
忽然有一天,我们最大的、每小时执行一次的数据处理报表应用,完成时间变得比平时晚了不少。一开始,我们并没有太在意,毕竟当时数据量每天都在增长,慢一点就慢一点了。但是,接着糟糕的事情开始发生了。
一方面,我们发现,报表任务有时候在一个小时之内执行不完,接着,偶尔整个报表任务会执行失败。于是,我们不得不停下手头开发的工作,开始排查这个问题。
用过Hadoop的话你可能知道作为一个分布式的应用考虑到硬件的故障Hadoop本身会在特定节点计算出错的情况下重试整个计算过程。之前的报表跑得慢就是因为有些节点的计算任务失败过只是在重试之后又成功了。进一步分析我们发现程序的错误非常奇怪。有些数据计算的结果比如“34+23”结果应该是“57”但是却变成了一个美元符号“$”。
前前后后折腾了一周,我们发现,从日志上看,大部分出错的任务都在几个固定的硬件节点上。
另一方面我们发现问题出现在我们新的一批自己定制的硬件上架之后。于是和运维团队的同事沟通近期的硬件变更并且翻阅大量Hadoop社区的邮件组列表之后我们有了一个大胆的推测。
我们推测这个错误来自我们自己定制的硬件。定制的硬件没有使用ECC内存在大量的数据中内存中出现了**单比特翻转**Single-Bit Flip这个传说中的硬件错误。
那这个符号是怎么来的呢?是由于内存中的一个整数字符,遇到了一次单比特翻转转化而来的。 它的ASCII码二进制表示是0010 0100所以它完全可能来自 0011 0100 遇到一次在第4个比特的单比特翻转也就是从整数“4”变过来的。但是我们也只能**推测**是这个错误,而不能**确信**是这个错误。因为单比特翻转是一个随机现象,我们没法稳定复现这个问题。
![](https://static001.geekbang.org/resource/image/45/0f/45ad4eb91f48afd08c581148d5f6320f.jpeg)
**ECC内存**的全称是Error-Correcting Code memory中文名字叫作**纠错内存**。顾名思义,就是在内存里面出现错误的时候,能够自己纠正过来。
在和运维同学沟通之后我们把所有自己定制的服务器的内存替换成了ECC内存之后这个问题就消失了。这也使得我们基本确信问题的来源就是因为没有使用ECC内存。我们所有工程师的开发用机在2012年也换成了32G内存。是的换下来的内存没有别的去处都安装到了研发团队的开发机上。
## 奇偶校验和校验位:捕捉错误的好办法
其实,内存里面的单比特翻转或者错误,并不是一个特别罕见的现象。无论是因为内存的制造质量造成的漏电,还是外部的射线,都有一定的概率,会造成单比特错误。而内存层面的数据出错,软件工程师并不知道,而且这个出错很有可能是随机的。遇上随机出现难以重现的错误,大家肯定受不了。我们必须要有一个办法,避免这个问题。
其实在ECC内存发明之前工程师们已经开始通过**奇偶校验**的方式,来发现这些错误。
奇偶校验的思路很简单。我们把内存里面的N位比特当成是一组。常见的比如8位就是一个字节。然后用额外的一位去记录这8个比特里面有奇数个1还是偶数个1。如果是奇数个1那额外的一位就记录为1如果是偶数个1那额外的一位就记录成0。那额外的一位我们就称之为**校验码位**。
![](https://static001.geekbang.org/resource/image/e9/40/e94c642bdf41290d6a4e5eb2d6bb3c40.jpeg)
如果在这个字节里面,我们不幸发生了单比特翻转,那么数据位计算得到的校验码,就和实际校验位里面的数据不一样。我们的内存就知道出错了。
除此之外校验位有一个很大的优点就是计算非常快往往只需要遍历一遍需要校验的数据通过一个O(N)的时间复杂度的算法,就能把校验结果计算出来。
校验码的思路,在很多地方都会用到。
比方说我们下载一些软件的时候你会看到除了下载的包文件还会有对应的MD5这样的哈希值或者循环冗余编码CRC的校验文件。这样当我们把对应的软件下载下来之后我们可以计算一下对应软件的校验码和官方提供的校验码去做个比对看看是不是一样。
如果不一样,你就不能轻易去安装这个软件了。因为有可能,这个软件包是坏的。但是,还有一种更危险的情况,就是你下载的这个软件包,可能是被人植入了后门的。安装上了之后,你的计算机的安全性就没有保障了。
不过,使用奇偶校验,还是有两个比较大的缺陷。
第一个缺陷就是奇偶校验只能解决遇到单个位的错误或者说奇数个位的错误。如果出现2个位进行了翻转那么这个字节的校验位计算结果其实没有变我们的校验位自然也就不能发现这个错误。
第二个缺陷,是它只能发现错误,但是不能纠正错误。所以,即使在内存里面发现数据错误了,我们也只能中止程序,而不能让程序继续正常地运行下去。如果这个只是我们的个人电脑,做一些无关紧要的应用,这倒是无所谓了。
但是,你想一下,如果你在服务器上进行某个复杂的计算任务,这个计算已经跑了一周乃至一个月了,还有两三天就跑完了。这个时候,出现内存里面的错误,要再从头跑起,估计你内心是崩溃的。
所以我们需要一个比简单的校验码更好的解决方案一个能够发现更多位的错误并且能够把这些错误纠正过来的解决方案也就是工程师们发明的ECC内存所使用的解决方案。
我们不仅能捕捉到错误,还要能够纠正发生的错误。这个策略,我们通常叫作**纠错码**Error Correcting Code。它还有一个升级版本叫作**纠删码**Erasure Code不仅能够纠正错误还能够在错误不能纠正的时候直接把数据删除。无论是我们的ECC内存还是网络传输乃至硬盘的RAID其实都利用了纠错码和纠删码的相关技术。
想要看看我们怎么通过算法,怎么配置硬件,使得我们不仅能够发现单个位的错误,而能发现更多位的错误,你一定要记得跟上下一讲的内容。
## 总结延伸
好了,让我们一起来总结一下今天的内容。
我给你介绍了我自己亲身经历的一个硬件错误带来的Bug。由于没有采用ECC内存导致我们的数据处理中出现了大量的单比特数据翻转的错误。这些硬件带来的错误其实我们没有办法在软件层面解决。
如果对于硬件以及硬件本身的原理不够熟悉,恐怕这个问题的解决方案还是遥遥无期。如果你对计算机组成原理有所了解,并能够意识到,在硬件的存储层有着数据验证和纠错的需求,那你就能在有限的时间内定位到问题所在。
进一步地,我为你简单介绍了奇偶校验,也就是如何通过冗余的一位数据,发现在硬件层面出现的位错误。但是,奇偶校验以及其他的校验码,只能发现错误,没有办法纠正错误。所以,下一讲,我们一起来看看,怎么利用纠错码这样的方式,来解决问题。
## 推荐阅读
我推荐你去深入阅读一下Wikipedia里面关于[CRC](https://en.wikipedia.org/wiki/Cyclic_redundancy_check)的内容,了解一下,这样的校验码的详细算法。
## 课后思考
有人说奇偶校验只是循环冗余编码CRC的一种特殊情况。在读完推荐阅读里面的CRC算法的实现之后你能分析一下为什么奇偶校验只是CRC的一种特殊情况吗
欢迎把你阅读和分析的内容写在留言区,和大家一起分享。如果觉得有帮助,你也可以把今天的内容分享给你的朋友。