注:本文内容引用自张洋老师的微博https://weibo.com/thinksoft,他现在是云和恩墨分布式存储研发专家。
存储系统中如何处理掉电恢复
在微博上看到这样一个问题:“那些很成熟的存储引擎,都是怎么处理崩溃恢复问题的呢,比如写数据落盘到一半,进程崩了,该如何恢复呢?求资料和指点。假设某次更新需要写n个页面,比如修改了里面的父子、兄弟关系,如果落盘的时候只修改了其中的一部分关系,导致关系都乱了,这种情况怎么恢复呢?”
首先来讲,确实得用日志,至少日志是方法之一。这个问题我从实际项目上的处理方式来谈谈,就当讲了个故事。2007年刚毕业的时候,参与SAN文件系统的开发。整体架构就是后端是阵列,然后服务器和客户端都通过光纤接入。每个服务器和客户端就能看见阵列上虚拟出来的LUN,无论是服务器还是客户端都可以直接读写这些LUN,就像读写真正的磁盘一样。这就有一个问题,需要一个分布式文件系统来支持这样一个架构。当时有一款叫做StoreNext的SAN分布式文件系统软件。我们公司主要就是买这款软件,但是非常贵,每年花不少钱在上面。
所以公司就打算自己开发一个差不多的存储软件来替换StoreNext。我2007年加入的时候,已经开发了近一年,然后接着又到了2009年,才算真正的开始试用。很不幸,第一套试用软件出问题了,来来回回去了三拨人到客户那里,调查原因,写事故分析报告。最后还好,大部分数据是捞回来了,事情后面也算平息。这款软件到目前为止,在全国各地,甚至国外许多地方都还有部署。不过也快走到了生命的尽头,估计以维护为主。
扯远了,扯这么多实际上是为了说明,在开发这款软件的时候,我们遇到了文章开头提出的问题。这款SAN文件系统,从数据角度来讲是分布式的,各个客户端向服务器拿到文件的位置以后,就直接计算应该写磁盘什么位置。直接通过光纤就写数据了,性能相当好。但是元数据服务器并不能算是分布式文件系统。只有主备两个服务器,干活的只有一个服务器。当时存储方面的知识技术还不像现在这么普及,CEPH我都还没听说过。所以主备如何切换,如何保证只有一个主,我们用的也是自己摸索出来的小作坊方式。虽然数千套软件在客户那里运行多年都没大问题,实际上这些方法到底对不对,谁都不敢保证。
在元数据服务器上,就会涉及到操作B+树,操作磁盘空间分配,操作MFT(相当于Linux下的Inode)区域等。就不要说需要操作这么多地方,就单拿B+树来说,就会涉及到操作B+树的多个块,一个块8K,存放10个Entry。当时的做法就是开发了一个日志模块,一次操作,例如创建文件涉及到的全部数据修改操作,打包到一起,计算检验和,先写入日志区域。日志是以追加的方式依次写入,每一个操作会有一个opid,日志区域头信息中需要记录日志的头尾。如果掉电以后,再上电开机,需要先从头部往后扫描日志区域有哪些没有commit的。如果扫到一个错误的位置,例如不是日志OP,或者是校验和不正确的OP,那么就停止了。确认日志区域的情况,将还没有commit的日志OP加载到内存,日志区域就算加载完成。当然这个做法必须要底层存储能够保证,写入成功的一定是持久化了的。里面有诸多细节我也记不得了,大致就是这么个样子。
后来随着网络带宽的发展,SAN光纤存储架构不再具有性价比优势。例如CEPH就是基于廉价硬件构建出的一个分布式的,可靠的,功能全面的存储系统。所以我们后来也参考CEPH设计了自己的分布式文件系统,包含有类似CEPH的做法,俨然就是个阉割版本的小CEPH。日志系统的设计方式基本上就无缝的拿过来了,毕竟那是经过多年检验没出过问题的方式。这种日志设计方式再次经过检验,在这个新版分布式文件系统的测试和试用期间都没有出现过问题。不过这款文件系统就没那么幸运了,最后还是被市场和公司无情的淘汰了。其中原因这里不表。
后来我也就转岗做了音视频和深度学习相关的工作。不过最近我又回到了熟悉的存储领域,存储领域可以算得上是技术上比较稳定的领域了。即使我有3、4年没做存储相关工作了,但是重新上手,发现曾经的知识和经验貌似还能用。CEPH还是那个CEPH,日志还是那个日志。不过多多少少还是有一些变化的,CEPH设计了BlueStore,Raft开始与Paxos分庭抗礼,SSD大行其道。
本地存储不用日志怎么处理掉电恢复
存储系统中,如文件系统Ext4,NTFS都采用日志来保证数据写入硬盘的一致性。在之前的文章中,我举过一个例子,例如创建一个文件,需要修改好几处数据。诸如B+Tree的多个块,空间分配、Inode等等。那么要保证创建文件所产生的这些数据写入都能全部成功。可以将这些操作数据打包为一个日志,写入日志区域。待日志写入成功,再写到相应的位置上。掉电恢复的时候,重做这条日志即可,如果日志写入失败,那么丢弃不完整的日志即可。
使用日志可解决上述的数据完整问题,但是由于要先写一次日志,再写数据。性能上不可避免的会有损失。例如Ceph早期采用本地文件系统作为其本地存储的底层,遇到了不少的问题,如写放大。就是因为本地文件系统如Ext4,Xfs的日志双写问题。我们团队自研的分布式存储系统在开发过程中,不可避免的也遇到了这样的问题。
我们采用Raft作为分布式一致性协议,Raft本身先写日志,再应用日志,已经是双写。如果Raft底层再采用日志型的本地存储的话,那么写放大必然会很严重。所以我们采用了SDPK BlobStore作为本地存储底层。BlobStore的实现比较简单,而且是没有日志的,性能和读写裸设备一致。BlobStore提供创建一个个的Blob,相当于一个个文件或者对象。
我们本地存储基于BlobStore设计开发,例如Chunk、Raft日志等内部数据结构,都对应一个Blob。所以如果一个操作有多次写入,就需要考虑某一步掉电,是否能够保证数据正确,能够再次正常加载。需要解决的主要是以下两个问题:
1)由于BlobStore能够保证原子写入的单位是4K,那么超过4K写入掉电怎么处理?这个问题采用多(4)副本来解决问题,例如一个元数据操作是8K数据,那么为这个8K数据留出4*8=32K的空间,第一次写第一个8K,第二次写第二个8K,循环往复。每次写入时候,计算MD5,且版本号增加1。那么在加载数据的时候,选取版本号最大且MD5正确的那一份数据即可。实际上我们元数据大多设计为4K大小单位,尽量满足写入的时候是一个原子操作。
2)一个操作分多步写,有写数据的,有写元数据的,其中一步掉电怎么办?这种情况就稍微麻烦一些,需要梳理每一步写盘过程中,掉电会导致什么问题,出现的问题是否可以处理。例如如下两个步骤:1)删除一个Blob。2)将这个Blob的ID记录从元数据中删除。如果是按照这样的顺序的话,可能出现元数据中还记录了这个BlobId,而实际上这个Blob已经删除了。那么再去打开,就会找不到这个Blob。更坏的情况是,这个BlobId被其他地方重用了,那么这里打开成功,再写入数据,就会导致了数据误写入,破坏了数据。那么解决的办法就是把步骤颠倒一下,先删除元数据中的BlobId记录,再删除Blob。即使中间掉电,最坏的情况便是泄露了一个Blob。
3)怎么处理掉电导致的一些问题,例如泄露了Blob?那么需要开发fsck分析工具,在上电的时候,将这些泄露掉的Blob删除掉即可。
以上列出了无日志的本地存储系统可能出现的一些问题,以及一些处理方法,欢迎大家探讨交流,批评指正。
作者:张洋
原文:企业存储技术
推荐阅读
欢迎关注企业存储技术极术专栏