一. Facebook RAID 简介
Facebook RAID 本质上是一个针对目标 RAID 目录的 MR 任务,在 MR 任务中,完成对该目录下的常规文件的 RAID、对已 RAID 文件的修复等功能。
该项目基于 Hadoop 0.2 版本开发,它最近的 commit 也已经是6年以前,可以认为这个项目已经死了,同时由于那时候 Hadoop 的功能并不完善,导致将其合入当前的 Hadoop 版本时,存在比较多的风险,详见下面讨论。
二. Facebook RAID 中可能导致数据不一致的致命 bug
这个 bug 的根本原因是(为简单起见,考虑文件仅有一个条带的情形,多个条带类似,按照默认的10+4配置,小于等于10个 block 大小的文件都会切分为一个 stripe):按照 Facebook RAID 实现,如果一个文件在写入过程中,有 block missing 导致文件变为 corrupt,那么此时有两种做法:
- 正确的做法
绝对不能对正在写入的文件进行修复,因为此时的 parity 文件很可能是之前生成的历史 parity 文件,已不再适用。 - 错误的做法
立刻对该文件进行修复,由于此时使用的是历史 parity 文件,修复出来的结果很可能是错误的结果。
Facebook RAID 采取了错误的做法,目前已发现有两个流程可以必现此问题,分别如下。
1. 对一个 RAID 文件,append 打开,正在写入时,进行修复,必然导致数据不一致
- 典型流程如下:
- client 写入一个 source 文件.
- 一段时间以后(例如3个月),该 source 文件被 raid,生成了对应的 parity 文件,且 source 文件被降为1副本,至此一切正常。
- client 随后 append source 文件进行追加写,由于此时 source 文件已经变成了1副本,在1副本的情况下,block missing 的的概率增大。
- 此时有任意 block missing,导致文件变为 corrupt,block missing 有两种典型情况:
- block 文件内容发生变化,导致 checksum 异常,此时 DirectoryScanner、VolumeScanner、client 等都有可能上报该出错 block。
- DN 上的 block 文件直接丢失,此时 DN 的 DirectoryScanner 会发现这个情况,并在随后的 Full BlockReport 中上报该 block。
- client 继续 append 了一些数据。
- 随后 missing 的 block 被修复(RaidNode 中 BlockFixer 线程每隔1分钟检查一次出错的文件并进行修复)。这是关键,此时修复使用的是之前生成的、已经不再适用的 parity 文件。
- 这时候修复出来 block 一定是错误的、数据不一致的 block。
- 根本原因:
一定不能对正在写入的文件做修复,此时修复出来的结果极有可能是错误的。
2. 对一个常规文件,create 打开并正在写入时,先 RAID 后修复,必然导致数据不一致
- 典型流程如下:
- client 开始 create 并写入 source 文件,处在写入流程中。
- 该 source 文件被 raid,生成了对应的 parity 文件,这是关键,由于文件的内容仍在不断变化,因此此时生成的 parity 文件是一个错误的 parity 文件。
- raid 结束后,该 source 文件随即被降为1副本,这会导致文件现有的 block 统一开始降副本,且新的 block 统一分配为1副本,在1副本的情况下,block missing 的概率增大。
- 此时有任意 block missing,导致文件变为 corrupt,block missing 有两种典型情况:
- block 文件内容发生变化,导致 checksum 异常,此时 DirectoryScanner、VolumeScanner、client 等都有可能上报该出错 block。
- DN 上的 block 文件直接丢失,此时 DN 的 DirectoryScanner 会发现这个情况,并在随后的 Full BlockReport 中上报该 block。
- client 继续写入了一些数据。
- 随后 missing 的 block 被修复(RaidNode 中 BlockFixer 线程每隔1分钟检查一次出错的文件并进行修复)。
- 此时候修复出来 block 一定是错误的、数据不一致的 block。
- 根本原因:
一定不能对正在写入的文件做 RAID,此时生成的 parity 文件一定是错误的,后续再使用该错误的 parity 文件修复,也一定会得到错误的修复结果。
三. Facebook RAID 中可能导致 corrupt 文件永远不能修复的严重 bug
按照 Facebook 的正确做法,如果一个文件在写入过程中,出现 missing block 变为 corrupt 的话,那么这个 corrupt 文件将永远无法修复,原因:
- 现有的可能存在的历史 parity 文件已经过时,不可用。
- 由于源文件损坏,也无法生成新的 parity 文件。
该 bug 属于 Facebook RAID 架构问题,无法解决。
四. Facebook RAID 中存在其它风险
-
RaidNode 中 blockFixer 线程对 fsck 的使用存在风险
RaidNode 中,对 corrupt files 定期进行扫描并修复的线程是 blockFixer,默认情况下,它使用 fsck 拿到集群中所有的 corrupt block files,具体命令是fsck -list-corruptfileblocks -limit 200000
,但在 Hadoop 2.8.5 中已移除 -limit 选项,这会导致 fsck 立刻报错。 -
RaidNode 中 blockCopier 线程对 fsck 的使用存在错误
RaidNode 中,blockCopier 线程定期扫描由于 decommission 过程,而导致所有 replica 都位于 decommissioning DN 上的那些 block,并对这些 block 进行 copy,默认是使用 fsck 命令fsck -list-corruptfileblocks -list-decommissioningblocks -limit 200000
,但在 Hadoop 2.8.5 中已移除 -list-decommissioningblocks 选项,最终也会导致 fsck 立即报错。 -
RaidNode 修复 block 完成后,选择目标 DN 进行 sendBlock 的选择逻辑存在 bug
RaidNode 为修复后的 block 挑选即将 sendBlock 的目标 DN 时,没有排除目前已经出错的 DN,导致该 block 可能 send 给已出错的 DN,而当该 DN 上原始的 block 文件仍存在时(例如原始 block 文件并未丢失,只是数据 checksum 异常),将立刻导致 send 失败。 -
RaidNode 修复 block 完成后,将 block send 给 DN 的逻辑存在风险
Facebook RAID 基于 Hadoop 0.2版本开发,那时候的 Hadoop 并不完善,存在很多功能缺失。具体来说,RaidNode 重建出正确的 block 之后,将 block 发送给 DN 的流程,参考了 HDFS DN 之间 Transfer Block 的流程,这个流程有一个明显的特征,即:Sender 发送 block 时只管发送,没有任何 ACK 机制,也不能确定 Receiver 是否真正收到 block 并写入了磁盘。这种做法在 DN 之间互相 transfer block 时是没问题的,因为 NN 会跟踪所有的 PendingReplicationBlocks,如果超过时限(最大5分钟)没有收到 Receiver DN 的确认,就会重新安排这个 block 的复制任务,但在 RAID Node 中,并没有类似的跟踪逻辑,这地方存在较大风险。事实上,RAID Node 更应该参考的是 HDFS Client 在做 Write Error Recovery 时,在不同的 DN 间 transfer block 的逻辑,但如前所述,Hadoop 0.2 并不完善,没有这部分功能。
-
NameNode blockPlacementPolicy 存在风险
Facebook RAID 提供了配套的 BlockPlacementPolicyRaid,需要在集群的 NameNode 上配置,其作用是:- 对于正常的非 RAID 文件, fallback 为默认的 HDFS block placement policy。
- 对于 RAID 文件,需要特殊化处理:尽量避免将同一个 stripe 上的 block 分配在同一个 DN 上,防止单个 DN 坏掉后,导致整个 stripe 无法修复,这对于保证修复成功率非常重要。
但在 Hadoop 2.8.5 中,BlockPlacementPolicyRaid 已无法兼容 Hadoop 定义的 BlockPlacementPolicy Interface,Hadoop 2.8.5 的 BlockPlacementPolicy Interface 功能更为独立,已不再与 FSNameSystem 有任何关联,因此,在 BlockPlacementPolicyRaid 中已不可能再通过 FSNameSystem 进行任何操作(当前有两个地方使用了 FSNameSystem:判断 source file 是否存在,和判断 parity file 是否存在)。
-
DistributedRaidFileSystem 中 open 文件时存在重大风险
DistributedRaidFileSystem 中 open 一个文件时,如果文件的 block 个数小于3(代码中写死),则不使用 RAID 方式读打开,改为使用正常的 HDFS 方式读打开。但实际上,一个 block 个数小于3的文件,完全有可能是一个 RAID 文件(一个文件超过多少 block 才可以做 RAID 是一个可配的值,最小可以取0,即:所有大小的文件都做 RAID)。这个 bug 会导致 block 个数小3的 RAID 文件无法进行修复式读取。 -
RaidNode 节点配置项存在重大风险
HDFS 客户端使用 Facebook RAID 时,需将fs.hdfs.impl
配置为org.apache.hadoop.hdfs.DistributedRaidFileSystem
,如此才能在读取出错时进行透明修复,但是,如果在 RaidNode 节点本身配置了这个选项,将导致 RaidNode 进程无法启动,原因:RaidNode 代码中写死,底层文件系统必须严格限制为org.apache.hadoop.hdfs.DistributedFileSystem
,否则无法启动。
五. 对 Facebook RAID 的处理结论
综合上面的讨论,Facebook RAID 目前既存在已知的致命 bug、严重 bug、多个风险,也很可能存在目前未知的其他 bug 和风险。因此:
-
代码方面,除了上面已发现的致命 bug 之外,不做任何修改(除非影响到 hadooop 2.8.5 的兼容性),原因如前所述,该项目在6年前就已停止更新,目前已经死亡,继续 fix 的意义不大。
-
如上所述,Facebook RAID 存在架构层面的严重 bug,无法解决,因此,禁止新增任何新的 RAID 文件。
-
对目前仍存在 RAID 文件的 HDFS 集群,其 RaidNode 进程需要继续运行,保证对集群中现有 RAID 文件的周期性扫描和修复。
-
对现有的 RAID 文件,尽快 revert 为多副本存储,后续 Hadoop 3.x 上线后,如果还有新的 RAID 需求,需要切换到 HDFS EC。
六. 使用 Hadoop 2.8.5 HDFS client 读取已有 RAID 文件
Hadoop 2.8.5 中,已不再支持 CDH3,因此需要遵照原生 Facebook RAID 的用法,在 hdfs-site.xml 中做如下配置:
<property>
<name>fs.hdfs.impl</name>
<value>org.apache.hadoop.hdfs.DistributedRaidFileSystem</value>
</property>
注意两点
- 配置这个选项有隐患:目前 Hive、HBase 等代码中很多地方都会判断底层的文件系统是否 instanceof DistributedFileSystem,如果是的话会做一些特殊的处理,配置了这个选项后,会破坏这个判断,导致跳过可能存在的针对 HDFS 的特殊处理。
- 尽量不要配置这个选项,客户端如果出现读失败(这个概率极小),则等待 RaidNode 修复破损文件(或命令行手动修复)之后,再行读取。
七. 将 Facebook RAID 文件 revert 为多副本存储
- 以下所有命令都需要在集群的 RaidNode 节点上执行。
- fsck 执行如下命令,确定集群的 corrupt files 中没有 raid 文件:
bin/hdfs fsck -list-corruptfileblocks
- 如果有破损的 raid 文件,使用如下命令进行修复:
bin/hdfs raidshell -recoverBlocks $filePath1 $filePath2 ...
- 设置所有 RAID 目录的 replication 系数为3,命令:
bin/hdfs dfs -setrep 3 raid目录
- 等待 NN UnderReplicated blocks 个数降为0.
- 重复第3、4步,确保所有 RAID 文件都已 revert。
网友评论