在Hadoop集群整个生命周期里,因为调整参数、Patch、升级等多种场景须要频繁操做NameNode重启,不论采用何种架构,重启期间集群总体存在可用性和可靠性的风险,因此优化NameNode重启很是关键。html
本文基于Hadoop-2.x和HA with QJM社区架构和系统设计(如图1所示),经过梳理NameNode重启流程,并在此基础上,阐述对NameNode重启优化实践。node
在HDFS的整个运行期里,全部元数据均在NameNode的内存集中管理,可是因为内存易失特性,一旦出现进程退出、宕机等异常状况,全部元数据都会丢失,给整个系统的数据安全会形成不可恢复的灾难。为了更好的容错能力,NameNode会周期进行CheckPoint,将其中的一部分元数据(文件系统的目录树Namespace)刷到持久化设备上,即二进制文件FSImage,这样的话即便NameNode出现异常也能从持久化设备上恢复元数据,保证了数据的安全可靠。git
可是仅周期进行CheckPoint仍然没法保证全部数据的可靠,如前次CheckPoint以后写入的数据依然存在丢失的问题,因此将两次CheckPoint之间对Namespace写操做实时写入EditLog文件,经过这种方式能够保证HDFS元数据的绝对安全可靠。github
事实上,除Namespace外,NameNode还管理很是重要的元数据BlocksMap,描述数据块Block与DataNode节点之间的对应关系。NameNode并无对这部分元数据一样操做持久化,缘由是每一个DataNode已经持有属于本身管理的Block集合,将全部DataNode的Block集合汇总后便可构造出完整BlocksMap。apache
HA with QJM架构下,NameNode的整个重启过程当中始终以SBN(StandbyNameNode)角色完成。与前述流程对应,启动过程分如下几个阶段:缓存
默认状况下,NameNode会保存两个FSImage文件,与此对应,也会保存对应两次CheckPoint以后的全部EditLog文件。通常来讲,NameNode重启后,经过对FSImage文件名称判断,选择加载最新的FSImage文件及回放该CheckPoint以后生成的全部EditLog,完成后根据加载的EditLog中操做条目数及距上次CheckPoint时间间隔(后续详述)肯定是否须要执行CheckPoint,以后进入等待全部DataNode注册和元数据汇报阶段,当这部分数据收集完成后,NameNode的重启流程结束。安全
从线上NameNode历次重启时间数据看,各阶段耗时占比基本接近如图2所示。微信
通过优化,在元数据总量540M(目录树240M,数据块300M),超过4K规模的集群上重启NameNode总时间~35min,其中加载FSImage耗时~15min,秒级回放EditLog,数据块汇报耗时~20min,基本可以知足生产环境的需求。数据结构
如前述,FSImage文件记录了HDFS整个目录树Namespace相关的元数据。从Hadoop-2.4.0起,FSImage开始采用Google Protobuf编码格式描述(HDFS-5698),详细描述文件见fsimage.proto。根据描述文件和实现逻辑,FSImage文件格式如图3所示。架构
从fsimage.proto和FSImage文件存储格式容易看到,除了必要的文件头部校验(MAGIC)和尾部文件索引(FILESUMMARY)外,主要包含如下核心数据:
NameNode执行CheckPoint时,遵循Protobuf定义及上述文件格式描述,重启加载FSImage时,一样按照Protobuf定义的格式从文件流中读出相应数据构建整个目录树Namespace及其余元数据。将FSImage文件从持久化设备加载到内存并构建出目录树结构后,实际上并无彻底恢复元数据到最新状态,由于每次CheckPoint以后还可能存在大量HDFS写操做。
NameNode在响应客户端的写请求前,会首先更新内存相关元数据,而后再把这些操做记录在EditLog文件中,能够看到内存状态实际上要比EditLog数据更及时。
记录在EditLog之中的每一个操做又称为一个事务,对应一个整数形式的事务编号。在当前实现中多个事务组成一个Segment,生成独立的EditLog文件,其中文件名称标记了起止的事务编号,正在写入的EditLog文件仅标记起始事务编号。EditLog文件的格式很是简单,没再经过Google Protobuf描述,文件格式如图4所示。
一个完整的EditLog文件包括四个部份内容,分别是:
NameNode加载FSImage完成后,即开始对该FSImage文件以后(经过比较FSImage文件名称中包含的事务编号与EditLog文件名称的起始事务编号大小肯定)生成的全部EditLog严格按照事务编号从小到大逐个遵循上述的格式进行每个HDFS写操做事务回放。
NameNode加载完全部必需的EditLog文件数据后,内存中的目录树即恢复到了最新状态。
通过前面两个步骤,主要的元数据被构建,HDFS的整个目录树被完整创建,可是并无掌握数据块Block与DataNode之间的对应关系BlocksMap,甚至对DataNode的状况都不掌握,因此须要等待DataNode注册,并完成对从DataNode汇报上来的数据块汇总。待汇总的数据量达到预设比例(dfs.namenode.safemode.threshold-pct)后退出Safemode。
NameNode重启通过加载FSImage和回放EditLog后,全部DataNode无论进程是否发生太重启,都必须通过如下两个步骤:
对于节点规模较大和元数据量较大的集群,这个阶段的耗时会很是可观。主要有三点缘由:
以前咱们在NameNode内存全景一文中详细描述过Block在NameNode元数据中的关键做用及与Namespace/DataNode/BlocksMap的复杂关系,从中也能够看出,每一个新增Block须要维护多个关系,更况且重启过程当中全部Block都须要创建一样复杂关系,因此耗时相对较高。
根据前面对NameNode重启过程的简单梳理,在各个阶段能够适当的实施优化以加快NameNode重启过程。
Fix:2.7.0
Hadoop-2.7.0版本前,SBN(StandbyNameNode)在执行CheckPoint操做前会先得到全局读写锁fsLock,在此期间,BlockReport请求因为不能得到全局写锁会持续处于等待状态,直到CheckPoint完成后释放了fsLock锁后才能继续。NameNode重启的第三个阶段,一样存在这种状况。并且对于规模较大的集群,每次CheckPoint时间在分钟级别,对整个重启过程影响很是大。实际上,CheckPoint是对目录树的持久化操做,并不涉及BlocksMap数据结构,因此CheckPoint期间是可让BlockReport请求直接经过,这样能够节省期间BlockReport排队等待带来的时间开销,HDFS-7097正是将锁粒度放小解决了CheckPoint过程不能处理BlockReport类型RPC请求的问题。
与HDFS-7097相对,另外一种思路也值得借鉴,就是重启过程尽量避免出现CheckPoint。触发CheckPoint有两种状况:时间周期或HDFS写操做事务数,分别经过参数dfs.namenode.checkpoint.period和dfs.namenode.checkpoint.txns控制,默认值分别是3600s和1,000,000,即默认状况下一个小时或者写操做的事务数超过1,000,000触发一次CheckPoint。为了不在重启过程当中频繁执行CheckPoint,能够适当调大dfs.namenode.checkpoint.txns,建议值10,000,000 ~ 20,000,000,带来的影响是EditLog文件累计的个数会稍有增长。从实践经验上看,对一个有亿级别元数据量的NameNode,回放一个EditLog文件(默认1,000,000写操做事务)时间在秒级,可是执行一次CheckPoint时间一般在分钟级别,综合权衡减小CheckPoint次数和增长EditLog文件数收益比较明显。
Fix:2.8.0
ANN(ActiveNameNode)将HDFS写操做实时写入JN的EditLog文件,为同步数据,SBN默认间隔1min从JN拉取一次EditLog文件并进行回放,完成后执行全局Quota检查和计算,当Namespace规模变大后,全局计算和检查Quota会很是耗时,在此期间,整个SBN的Namenode进程会被Hang住,以致于包括DN心跳和BlockReport在内的全部RPC请求都不能及时处理。NameNode重启过程当中这个问题影响突出。
实际上,SBN在EditLog Tailer阶段计算和检查Quota彻底没有必要,HDFS-6763将这段处理逻辑后移到主从切换时进行,解决SBN进程间隔1min被Hang住的问题。
从优化效果上看,对一个拥有接近五亿元数据量,其中两亿数据块的NameNode,优化前数据块汇报阶段耗时~30min,其中触发超过20次因为计算和检查Quota致使进程Hang住~20s的状况,整个BlockReport阶段存在超过5min无效时间开销,优化后可到~25min。
Fix:2.7.1
NameNode加载完元数据后,全部DataNode尝试开始进行数据块汇报,若是汇报的数据块相关元数据尚未加载,先暂存消息队列,当NameNode完成加载相关元数据后,再处理该消息队列。对第一次块汇报的处理比较特别(NameNode重启后,全部DataNode的BlockReport都会被标记成首次数据块汇报),为提升处理速度,仅验证块是否损坏,以后判断块状态是否为FINALIZED,如果创建数据块与DataNode的映射关系,创建与目录树中文件的关联关系,其余信息一律暂不处理。对于非初次数据块汇报,处理逻辑要复杂不少,对报告的每一个数据块,不只检查是否损坏,是否为FINALIZED状态,还会检查是否无效,是否须要删除,是否为UC状态等等;验证经过后创建数据块与DataNode的映射关系,创建与目录树中文件的关联关系。
初次数据块汇报的处理逻辑独立出来,主要缘由有两方面:
启动过程当中,不提供正常读写服务,因此只要确保正常数据(整个Namespace和全部FINALIZED状态Blocks)无误,无效和冗余数据处理彻底能够延后到IBR(IncrementalBlockReport)或下次BR(BlockReport)。
这原本是很是合理和正常的设计逻辑,可是实现时NameNode在判断是否为首次数据块块汇报的逻辑一直存在问题,致使这段很是好的改进点逻辑实际上长期并未真正执行到,直到HDFS-7980在Hadoop-2.7.1修复该问题。HDFS-7980的优化效果很是明显,测试显示,对含80K Blocks的BlockReport RPC请求的处理时间从~500ms可优化到~100ms,从重启期整个BlockReport阶段看,在超过600M元数据,其中300M数据块的NameNode显示该阶段从~50min优化到~25min。
Fix:2.7.0
若NameNode重启前产生过大删除操做,当NameNode加载完FSImage并回放了全部EditLog构建起最新目录树结构后,在处理DataNode的BlockReport时,会发现有大量Block不属于任何文件,Hadoop-2.7.0版本前,对于这类状况的输出日志逻辑在全局锁内,因为存在大量IO操做的耗时,会严重拉长处理BlockReport的处理时间,影响NameNode重启时间。HDFS-7503的解决办法很是简单,把日志输出逻辑移出全局锁外。线上效果上看对同类场景优化比较明显,不过若是重启前不触发大的删除操做影响不大。
选择HA热备方案SBN(StandbyNameNode)仍是冷备方案SNN(SecondaryNameNode)架构,执行CheckPoint的逻辑几乎一致,如图6所示。若是SBN/SNN服务长时间未正常运行,CheckPoint不能按照预期执行,这样会积压大量EditLog。积压的EditLog文件越多,重启NameNode须要加载EditLog时间越长。因此尽量避免出现SNN/SBN长时间未正常服务的状态。
在一个有500M元数据的NameNode上测试加载一个200K次HDFS事务操做的EditLog文件耗时~5s,按照默认2min的EditLog滚动周期,若是一周时间SBN/SNN未能正常工做,则会累积~5K个EditLog文件,此后一旦发生NameNode重启,仅加载EditLog文件的时间就须要~7h,也就是整个集群存在超过7h不可用风险,因此切记要保证SBN/SNN不能长时间故障。
当集群中大量数据块的实际存储副本个数超过副本数时(跨机房架构下这种状况比较常见),NameNode重启后会迅速填充到PostponedMisreplicatedBlocks,直到相关数据块所在的全部DataNode汇报完成且退出Stale状态后才能被清理。若是PostponedMisreplicatedBlocks数据量较大,每次全遍历须要消耗大量时间,且整个过程也要持有全局锁,严重影响处理BlockReport的性能,HDFS-6425和HDFS-6772分别将可能在BlockReport逻辑内部遍历很是大的数据结构PostponedMisreplicatedBlocks优化到异步执行,并在NameNode重启后让DataNode快速退出blockContentsStale状态避免PostponedMisreplicatedBlocks过大入手优化重启效率。
NameNode处理BlockReport的效率低主要缘由仍是每次BlockReport所带的Block规模过大形成,因此能够经过调整Block数量阈值,将一次BlockReport分红多盘分别汇报,以提升NameNode对BlockReport的处理效率。可参考的参数为:dfs.blockreport.split.threshold,默认值1,000,000,即当DataNode本地的Block个数超过1,000,000时才会分盘进行汇报,建议将该参数适当调小,具体数值可结合NameNode的处理BlockReport时间及集群中全部DataNode管理的Block量分布肯定。
前面提到NameNode汇总DataNode上报的数据块量达到预设比例(dfs.namenode.safemode.threshold-pct)后就会退出Safemode,通常状况下,当NameNode退出Safemode后,咱们认为已经具有提供正常服务的条件。可是对规模较大的集群,按照这种默认策略及时执行主从切换后,容易出现短期丢块的问题。考虑在200M数据块的集群,默认配置项dfs.namenode.safemode.threshold-pct=0.999,也就是当NameNode收集到200M*0.999=199.8M数据块后便可退出Safemode,此时实际上还有200K数据块没有上报,若是强行执行主从切换,会出现大量的丢块问题,直到数据块汇报完成。应对的办法比较简单,尝试调大dfs.namenode.safemode.threshold-pct到1,这样只有全部数据块上报后才会退出Safemode。可是这种办法同样不能保证万无一失,若是启动过程当中有DataNode汇报完数据块后进程挂掉,一样存在短期丢失数据的问题,由于NameNode汇总上报数据块时并不检查副本数,因此更稳妥的解决办法是利用主从NameNode的JMX数据对比全部DataNode当前汇报数据块量的差别,当差别都较小后再执行主从切换能够保证不发生上述问题。
除了优化NameNode重启时间,实际运维中还会遇到须要滚动重启集群全部节点或者一次性重启整集群的状况,不恰当的重启方式也会严重影响服务的恢复时间,因此合理控制重启的节奏或选择合适的重启方式尤其关键,HDFS集群启动方式分析一文对集群重启方式进行了详细的阐述,这里就再也不展开。
通过屡次优化调整,从线上NameNode历次的重启时间监控指标上看,收益很是明显,图7截取了其中几回NameNode重启时元数据量及重启时间开销对比,图中直观显示在500M元数据量级下,重启时间从~4000s优化到~2000s。
这里罗列了一小部分实践过程当中能够有效优化重启NameNode时间或者重启全集群的点,其中包括了社区成熟Patch和相关参数优化,虽然实现逻辑都很小,可是实践收益很是明显。固然除了上述提到,NameNode重启还有不少能够优化的地方,好比优化FSImage格式,并行加载等等,社区也在持续关注和优化,部分讨论的思路也值得关注、借鉴和参考。
NameNode重启甚至全集群重启在整个Hadoop集群的生命周期内是比较频繁的运维操做,优化重启时间能够极大提高运维效率,避免可能存在的风险。本文经过分析NameNode启动流程,并结合实践过程简单罗列了几个供参考的有效优化点,借此但愿能给实践过程提供可优化的方向和思路。
小桥,美团点评技术工程部数据平台研发工程师。2012年北京航空航天大学毕业,2015年初加入美团点评,关注Hadoop生态存储方向,致力于为美团点评提供稳定、高效、易用的离线数据存储服务。
回答“思考题”、发现文章有错误、对内容有疑问,均可以来微信公众号(美团点评技术团队)后台给咱们留言。咱们每周会挑选出一位“优秀回答者”,赠送一份精美的小礼品。快来扫码关注咱们吧!