返回 登录
0

固态硬盘并不是“固”不可破

阅读34138

图片描述

今天要说的这件事儿就像是半夜里的一场噩梦。我们公司的一台运行着检索API的服务器突然停止进行索引,原因未知。我们在Algolia所架设的系统可用性强、灵活性强,所以过去从来没有发生过什么故障。问题出现后,新触发的API调用重定向到了服务器集群中运转正常的机器上,没再出现故障,而此次事件唯一受到重大影响的则是一位被半夜惊醒的工程师。

为我们所有API的HTTP(S)通讯提供服务的NGINX daemon运行正常,可随时响应检索请求,但是索引进程却挂掉了。检索进程是由daemon中的supervise工具来监控的,在一个循环中出现进程崩溃还好理解,但是全盘崩溃就让人难以想象了。最后的结果是文件系统变成只读模式。没办法,就当是宇宙射线捣的鬼吧。我们将文件系统修复,借助一台运转良好的服务器重置文件系统中的文件,让一切都恢复如初。

第二天,另一台服务器的文件系统也变成只读了,两个小时后又一台,一个小时后又出现一台。这里面肯定有“鬼”。重置文件系统和其中的文件后,我们准备好好分析一下,因为这种事儿绝不是孤立事件。分析过程中,我们对与存储栈相关的软件进行故障检测,逐个排查最近发生的更改情况。

排查Bug的时间到了!

我们首先思考的问题是:难道是我们自己的软件出了问题?我们使用不安全的系统指令了吗?还是我们处理数据的方式不正确?会不会是我们把数据flush到硬盘之前没有正确的将文件读写到内存中?

  • 文件系统——ext4中有漏洞吗?有可能是我们不小心把数据写进了文件分配表的内存空间?
  • Mdraid——mdadm工具中有漏洞吗?我们是否进行了错误的配置?
  • 硬盘——硬盘出现问题了吗?
  • 固态硬盘(SSD)——SSD要坏了吗?还是说问题更严重,磁盘固件出了问题?

我们甚至开始打赌讨论问题可能出现的地方,并严格按照上面这个顺序从简单的小毛病到超级难解决的大毛病逐个提出可能的解决方案。

从头到尾过一遍我们软件栈的存储顺序让我们有机会给可能的漏洞埋下“陷阱”,如果问题一出现就能被我们抓个现行,这样我们就能够更好的定位出错的位置了。在排查了我们搜索引擎的每一个存储指令后,我们自信的得出结论:问题的根源并不是我们处理数据的方法。很遗憾,即便这样,我们还是没有真正找到问题。

一小时后,又一台服务器挂掉了。这次我们把出问题的机器从服务器集群中取了出来,开始一点一点排查问题。在修复文件系统前,我们注意到有些文件的片段丢失了,俗称“清零”,具体情况是:文件更改日期没有发生改变、文件大小没有发生改变,唯一有变化的是文件的部分片段被清零。而小文件则全部被删除。这种现象太诡异了,我们开始考虑是否是因为操作系统或文件系统在内存中隐藏了某些特定分区,而我们的应用没有进入这些分区的权限。我们之所以会这样认为,是因为如果不是这种情况的话,我们的应用是不可能绕过文件系统对文件进行更改的。我们的软件是用C++程序写的,考虑到Windows程序跟Linux程序有各种各样不兼容的情况发生,在排查问题的发生原因时我们总是会冒出一大堆奇怪的想法。所有的内存块都在我们的控制范围之外,所以现在我们又进入了一个死胡同。

那么问题会出在ext4文件系统中吗?遍历内核变更日志寻找ext4问题的过程实在不忍回顾。我们在每一个版本的内核中都找到了理论上可能对我们造成重大影响的已修复问题。我不得不承认,看变更日志前我睡得挺好的。

在经常崩溃的几台机器上我们部署了包括3.2、3.10、3.13和3.16在内的众多内核版本,我们想看看究竟哪个版本会出问题。结果,全都出问题了。我们又跑进了一条死胡同。也许是ext4出了问题,而这个问题可能从没有人发现过?可惜我们可没那么幸运,哪能这么简单就定位到问题所在之处。我们也不想就这样停留在目前的状况下。ext4中存在bug还是有可能的,而且可能性还很大。

如果问题不是出在mdadm上呢?然而,看完变更日志后,我们确信不能再沿着这条路走下去了。

我们的系统修复之路进入到非常严峻的阶段,这场午夜噩梦似乎没法立刻终结。我们花了将近两个星期的大把时间来尽快分离出有问题的机器,然后尽快将其恢复原样。我们做的其中一件事就是通过检测软件来查找索引文件中的空白内存块,即便有些内存块从来没有使用过,我们也要检测,我们的软件可以提前告知我们问题出在哪儿。

天天都会上演的系统崩溃

然而每天都有越来越多的服务器挂掉,所以我们尝试将系统重置的流程自动化,确保自动化后系统能够达到我们觉得满意的程度。每次尝试失败后,我们都会努力查看崩溃出现的不同原因,以期发现所有问题的“最小公分母”。这些问题都有共同的病症,但有一件事开始变得越来越清晰:我们发现所有问题都出在服务器的其中一块分区上。分区上所用的软件栈跟其他一样,但是分区的硬件有些许不同。问题主要是在固态硬盘上,但奇怪的是所有固态硬盘都来自同一家生产厂家。这个发现让我们非常担忧,于是我们联系了服务器供应商,询问他们的其他客户是否也遇到过类似的问题。但是,让技术人员相信你偶尔才遇到的问题是个“真问题”有点困难,因为在最新版的固件上我们遇到的问题无法复现。我们没能成功的从供应商那里寻求到解决办法,但至少我们已经取得了一次小胜利。

现在我们知道了什么样的软件和什么样的硬件搭配在一起能出现问题,于是我们换了一组不同的硬件然后装上一样的软件栈,尝试复现问题。接下来发生什么了呢?不出所料,崩溃再一次发生。现在我们非常确信问题不是出在软件栈上,而是出现在硬件上。但能绕过系统改变内存块中文件内容的祸因究竟是什么呢?仔细一番考量,这其中又有一堆解不开的结……

这之后,我们的日子开始变得“有条不紊”:不紧不慢冲完澡后吃早饭,然后开始修复崩溃的服务器;干完活儿吃午饭,然后开始修复崩溃的服务器;干完活儿吃晚饭,然后开始修复崩溃的服务器。直到有一天早上我慢慢悠悠洗澡时进行了一番漫长的思考,我不禁想起一个问题:出现问题的序列究竟有多大?我发现丢失的数据都是512字节的,而硬盘上每一个内存块的大小也是512字节。进一步分析发现,每一次都是一个内存块被清零。这是硬件的漏洞吗?还是说内存块是被某个东西清零的?那究竟是什么原因导致内存块被清零呢?是Trim!Trim指令可以控制固态硬盘清零空白内存块。但是有些内存块并没有被清零,其他类型的固态硬盘也没有受到严重影响。我们决定做个试验,将所有服务器的Trim指令禁用。这样就能水落石出了!

第二天,所有服务器全部正常,没有一台崩溃。两天后一切正常,一周后依然风平浪静。噩梦终于结束了!当然,是我们自己这么觉得噩梦结束了,其实不然。发现这个问题的一个月后,一台服务器自动重启,随之而来的是数据损坏,但仅仅损坏一些小文件(然而这些小文件里就有证书文件)。即便是关机方法不当也不至于引发这样的问题。

我们在内核源代码中捣鼓了一阵子与Trim指令相关的代码,找到了不兼容Trim指令的设备黑名单。这个黑名单设置了针对不同固态硬盘的具体行为,并根据型号名的regexp(正则表达式对象)来识别硬盘。我们正在使用的固态硬盘已经被明确的标明可以完全兼容Trim指令,但是我们手中受影响的一些固态硬盘的制造商却是受限的,并不是他们生产的所有固态硬盘都兼容。这些受影响的硬盘并不符合任何正则表达式,所以毫无疑问他们是无法兼容Trim指令的。

问题的全景

此时此刻,我们终于发现了问题的来龙去脉。问题出现时,系统发出Trim指令,清零空白的内存块,然而指令却被硬盘编译错误,控制器清零了那些本来不应该被清零的内存块。因此,我们的文件最终都被清零了512个字节,文件大小不到512字节的则被完全清零。我们很倒霉,运行异常的Trim指令被发送到了文件系统的“超级块”(Super-block,有关文件系统的大部分信息都保存在这个内存块中),并最终导致服务器崩溃。禁用Trim指令后,现有的大文件不再受到影响。但是,已经映射到内存的小文件如果在Trim指令禁用后从来没有更改过,就会拥有两种状态:内存中保存的内容是正确的,但是硬盘上保存的内容是受损的。进行文件错误检查时之所以没有发现问题,是因为被检查的文件并不是保存在硬盘上的文件,而被检查的则是内存中的映射。为了保证数据完整,我们重启了无数次服务器,但最终经历了数周的“捉鬼”运动后我们终于给这件事儿画上了句号。

真相大白后,我们将固态硬盘的问题告知了服务器供应商,他们又通知了服务器生产商。我们最新部署的系统切换到了不同的固态硬盘上,我们不建议任何人使用任何与Linux内核发生过不兼容事件的固态硬盘。同样需要注意的是,即便你并没有直接启用Trim指令,至少在Ubuntu 14.04后的版本中每周cron都会在所有分区中运行FSTRIM指令,硬盘突然异常几秒钟的情况只不过是小问题,不用大惊小怪。

文章太长不想看的话,直接看下方的数据吧

有一系列讨论指出问题与最新引入市场的“Queued TRIM”有关。这是不正确的。我们目前所使用硬盘上的Trim指令在发送前都需要先清空队列中的其他操作,而我们发现的问题则与Linux内核近期禁用这个功能的更新没有任何关系。

出现故障的固态硬盘型号:

  • SAMSUNG MZ7WD480HCGM-00003
  • SAMSUNG MZ7GE480HMHP-00003
  • SAMSUNG MZ7GE240HMGR-00003
  • Samsung SSD 840 PRO Series
  • recently blacklisted for 8-series blacklist
    Samsung SSD 850 PRO 512GB
  • recently blacklisted as 850 Pro and later in 8-series blacklist

运转正常的固态硬盘型号:

  • Intel S3500
  • Intel S3700
  • Intel S3710

英文原文:When Solid State Drives are not that solid

评论