引自个人blog: http://jiangbo.me/blog/2012/12/21/hdfs-raid/
HDFS是构建在普通机器上的分布式文件系统,而这类系统需要解决的一个首要问题就是容错,允许部分节点失效。而为了解决数据的可靠性,HDFS采用了副本策略。默认会为所有的block存放三个副本(具体参见HDFS设计文档)。 副本机制能够有效解决部分节点失效导致数据丢失的问题,但对于大规模的HDFS集群,副本机制会带来大量的存储资源消耗。例如为了存储1PB的数据,默认需要保留3个副本,这意味着实际存储所有副本需要至少3PB的空间。存储空间浪费达到200%。减小浪费的方式主要是减少副本数,而当副本数降低到小于3时,数据丢失的风险会非常高。而HDFS RAID的出现主要是解决降低副本数之后,通过RAID机制中的Erasured Code来确保数据的可用性。
HDFS RAID的实现(Facebook的实现)主要是在现有的HDFS之上增加了一个包装contrib。之所以不再HDFS上直接修改,原设计者的解释是“HDFS的核心代码已经够复杂了,不想让它更复杂”。
HDFS RAID的使用场景主要有两个:raid数据管理和raid数据读取。
对于DRFS的管理,包括DFS中那些文件需要进行raid化,查询raid文件的状态等,主要通过HDFS-RAID提供的RaidShell工具来完成。本质上RaidShell作为一个client工具,通过RPC与集群中的RaidNode通信,完成各种管理操作。
使用HDFS RAID的client端需要配置fs.hdfs.impl为DistributedRaidFileSytem,DRFS包装了DFS的读(只是读)请求,当block读取时发生block丢失(抛出MissingBlockException)或损坏(CorruptionException)时,DRFS会捕获这两个异常,并向RaidNode发送RPC对失效的数据进行恢复。
RaidNode是HDFS-RAID中除NameNode和JobTracker之外的第三个master node,主要是接收client端的RPC请求和调度各守护线程完成数据的raid化和数据修复,parity文件删除等操作。
LocalRaidNode: 在RaidNode本地进行parity计算,parity文件的生成是一个计算密集型任务,而本地计算能力有限,因此该方式的扩展性有限。
DistributedRaidNode: 通过提交mapreduce job来进行parity计算
TriggerMonitor: 周期性检查raid-policy配置,根据最新的配置来进行对相应的数据raid化。raid化的调度周期主要收两个配置的影响,raid.config.reload.interval (重新加载raid-policy配置的周期,默认10s)和raid.policy.rescan.interval(重新扫描需要raid化的src的间隔,默认1小时)。简单讲,当新增了一个policy时,默认10s内该policy会被加载执行。而在一个已经raid化的目录中新增了一个文件时,该文件将在1个小时内被raid话。
BlockIntegrityMonitor: 负责通过DFS的fsck来对DRFS中已经raid化的数据进行检查,检查内容主要包括corrupt(损坏)和decomssion(丢失)的文件。一旦检测到这类文件的存在,BlocIntegrityMonitor会通过其维护的CorruptMonitor和DecomissionMonitor的两个线程来进行数据的修复。BlockIntegrityMonitor对应local和dist两种模式有两个实现,分别为LocalBlockIntegrityMonitor和DistBlockIntegrityMonitor。(可通过raid.blockfix.classname配置项设置,默认为dist)。区别主要在获取的corruptionMonitor和DecomissionMonitor的实现不同。
LocalBlockIntegrityMonitor: 提供了CorruptMonitor实现会循环通过fsck检查corrupt文件,通过BlockReconstructor.CorruptBlockReconstructor重建这些文件。但该实现不提供Decomissioning文件的监控处理。local模式下corrput文件的重建是在RaidNode上进行的,对大量数据的重建,会对RaidNode有较大的压力。
DistBlockIntegrityMonitor: Dist模式提供的CorruptionMonitor和DecomissionMonitor是通过DFSck获取corrupt和decomissed的文件列表,计算优先级后,通过向集群提交job来完成重建,Job的输入是一个包含所有文件path的sequence file,Mapper实现是通过Reconstructor来重建每个文件。
BlockFixer(CorruptionMonitor): BlockIntegrityMonitor构建的用于修复corrupt文件的worker线程。
BlockCopier(DecomissionMonitor): BlockIntegrityMonitor构建用于修复decomission文件的worker线程。
PlacementMonitor: PlacementMonitor主要是通过blockMover完成为DRFS中的根据placement策略提供在Datanode之间move block的工具线程。BlockMover通过一个ClusterInfo线程周期性(默认1min)获取集群中live节点的最新topo结构。对于parity block过于集中的节点,需要将其分散开。分散的过程主要是:为每个的block构建一个BlockMoveAction线程,该线程在所有datanode中除当前block所在的节点外随机选取一个datanode,并选取一个proxysource datanode,proxysource datanode是用于将block复制到datanode的源节点,选取规则是优先选取当前block副本所在dn中与目标datanode所属同一rack的节点,如果没有,则从副本列表中随机选取一个作为源节点。
PurgeThread: PurgeThread封装了PurgeMonitor,它会定期扫描Parity文件中是否有孤儿Parity文件(即拥有该Parity文件的source文件已经不存在了),如果有则需要将其删除,如果没有,会对Parity文件和对应的source文件进行placement检查。
HarThread: 为了减少RAID后Parity文件对Namenode的负担,HarThread封装了HarMonitor,它定期对超期的Parity文件进行归档处理(HAR),超期时间由raid.parity.har.threshold.days指定,默认是3天。
文件数据的raid化有两种场景,一种是通过raidShell之行 raidFile命令触发
hadoop raidshell -raidFile /path/to/file
另一种是TiggerMonitor线程周期行扫描policy,根据新的配置信息进行相应的raid化。
当前client端执行raidfile请求时,大致的处理流程如下:
triggerMonitor作为RaidNode上的守护线程,周期性从configManager中获取policy列表,对每个policy进行如下处理:
上述表明,hdfs raid中对文件的raid最终都是由RaidNode.doRaid()来完成,不通场景下的区别主要是raid过程的执行地点不同:
RaidNode.doRaid()的主要流程如下:
raid过程中最终的编码生成parity的工作有Encoder完成。编码过程主要如下:
对于Erasured Code的生成过程大至流程如下: 从源文件中block列表中选取一些(数量有stripe_length指定,默认是10)block,构成一个strip(条?)。通过ParallelStreamReader工具构建一个并行读取10个block的的数据,每个block每次读取1个buff的数据(buffer大小有raid.encoder.bufsize指定,默认是1m),一次读取构成一个二维byte数组byte[stripe_length][buff_size],这个二维数组做为Erasure Code的输入数据,进行编码生成erasued code。输出也是一个byte二维数组byte[parity_length][buffer_size]。
XOR算法中:parity_length为1, 即根据10位输入byte生成1位的奇偶校验码。
RS算法中: parity_length默认为4, 及根据10为输入生成4为的RS code,这四位分别写入4个™p文件中,在一个buffer全部编码完成后,将4个parity文件进行合并。生成一个™p文件。
raid数据的修复同样也有多个触发场景:
在client通过DRFS读取raid话的数据是,DRFS首先通过其内部封装的DFS去读block数据,当DFS读取时跑出CorruptionException或DecomissionException时,会被DRFS捕获,并对出错的block在client进行修复。主要流程如下:
RaidNode.unRaidCorruptionBlock()过程首先获取该block的parity文件信息,然后构建一个恢复文件的path路径(该路径位于hdfs.raid.local.recovery.location配置的目录下,默认是/tmp/raidrecovery,文件名为原文件名+”.”+随机long+”.recoveryd”),并通过Decoder.fixErasedBlock()来根据parity文件生成恢复文件。
注意:对于恢复文件所在的文件系统是可以通过fs.raid.recoveryfs.uselocal来配置的,默认是false,即使用DFS,恢复文件将在储与分布式系统中,当配置成true是,使用LocalFileSystem,将恢复文件存储在client端本地。
RaidNode上的BlockIntegrityMonitor线程会通过DFSck工具检查系统中corrupt或decomission的数据,通过BlockCopier和BlockFixer线程周期行对出错的数据进行修复。local模式下,修复过程在RaidNode上之行,Dist模式下修复过程通过提交Job的方式提交给集群完成。
Local模式 LocalBlockIntegrity线程的核心是周期调用doFix方法修复corrupt文件,主要流程如下:
Dist模式 DistBlockIntegrity中的有两个worker线程blockCopier和blockFixer,分别对应修复decomssion和corrput的文件。实际上两个线程的处理流程基本一致,大体如下:
计算获得的损坏文件的优先级:
corrput文件的优先级如下(R为文件副本数,C为该文件corrput的block数):
decomission优先级计算规则如下(D为decomission的block数):
将计算好优先级的文件列表按优先级排序,作为参数构建修复Job。
注意:对于修复Job还有一个参数限制,及每次job最多进行的task数,该值为固定值50,这意味着一个Job一次最多能修复的文件数是100个(raid.blockfix.filespertask*50)
通过raidshell执行 fixblock时, raidShell会通过BlockReconstructor来完成文件的修复。
BlockIntegrityMonitor和RaidShell对文件的修复最终都通过BlockReconstructor来完成。 BlockReconstructor修复文件过程主要分为三类:Har parity文件,parity文件和源数据文件。
Har parity文件
parity文件
parity文件的修复处理相对简单:
源数据文件
源文件的恢复与parity文件的修复相反,是一个decode过程:
Decoder的修复过程即一个parity文件的decode过程: