聊聊HDFS删除Snapshot行为导致的NameNode crash

前言


关于HDFS的快照,使用过的同学对于这个功能还是持正面评价居多的吧。这个特性所能带给我们最大的好处就是防止用户误删数据导致数据丢失的问题了。从数据保护层面而言,HDFS Snapshot确实起到了十分关键的作用。但是话虽然是这么说,那么如果我们想确保集群内的每一分数据都能够得到Snapshot的保护,那么是否我们就可以在根目录下建个超级大的Snapshot呢?这样超级大的Snapshot会有什么隐患呢?事实上大Snapshot意味着后续代价更高的删除快照行为,甚至会拖垮NameNode导致NameNode crash,删除快照行为这个操作就会变得极为的重。这个其实已经是HDFS社区的一个known issue HDFS-11225: NameNode crashed because deleteSnapshot held FSNamesystem lock too long。不过对于这个issue,社区给出了一定的解决方案,本文笔者就来聊聊这个解决方案。

HDFS的Snapshot以及delete Snapshot行为


这里首先要介绍一下HDFS Snapshot行为主要做哪些事情,这样我们才能更好理解随后的删除Snapshot会做对应的哪些事情。

当用户在某个时刻点创建一个Snapshot之后,在这之后的每次文件目录的改动,在这个Snapshot内部变量中会维护一个diff列表,这里面信息包括以下几类:

  • 哪些文件被创建了,在此快照之后
  • 哪些文件被删除了,在此快照之后
  • 另外哪些文件被修改了

这里diff list的意思是,将Snapshot状态下的数据,经过diff列表的一系列操作之后,能够转化为当前的数据状态。当有多Snapshot存在时,这个diff list表示的就是快照间的状态差异。

凡是被修改了文件目录之后,HDFS Snapshot这边会对其原始INode数据进行一个拷贝,INode文件对应的实际block数据还是指向原来的那些block。简单来说,当用户误删除了当前目录的文件数据后,其实只是删除了INode,实际的block数据并不会被删掉。

因此我们可以看到,Snapshot的出现实际上延缓了数据删除的实际发生时间。如果Snapshot一直存在,随着时间的推移,它就会累积越来越多的Block数据。最终在系统清理Snapshot的时候,会出现一个大的量的数据删除。这就跟在HDFS里面删除了一个大目录等同的行为了,这个时候其实删Snapshot就是一个很重的行为了。一种极端的情况是,delete Snapshot操作执行时间过长,持有内部锁时间过长,还会导致NameNode crash。

因此我们建议说,使用Snapshot时,遵循以下使用原则为佳:

  • 创建的Snapshot存在时间不宜过长。
  • 不宜在过大的目录树下建Snapshot,可以转为多个小Snapshot。

删除快照行为除了本身涉及到大量block的删除,其次还有以下2点原因导致这个操作可能会变得异为繁重:

  • 目录树递归遍历式的删除块收集行为
  • 快照目录的diff计算比较

上述步骤2又会明显减慢Snapshot的删除行为,社区对此的优化正是基于上述的第二点,如何让NameNode去做更加快速的Snapshot diff比较。

基于SkipList的Snapshot diff预先合并


社区JIRA HDFS-11225采用的基本思路是用预先内存计算Snapshot diff的方式来加速这个过程,这样可以避免在删除Snapshot时需要执行Snapshot diff操作。

在Snapshot diff预先合并的实现中,社区实现了基于SkipList的解决方案,在内存中维护了多层级的快照链表关系,如下所示:

level 2: head ->  s1''------------------------------------>s10
level 1: head ->  s1'---->s3'------>s5'-------------------->s10
level 0: head ->  s1->s2->s3->s4->s5->s6->s7->s8->s9---->s10

每当有新快照创建,并且同时快照数量超过给定的阈值时,则进行下一层级的快照diff合并。例如在上面这个例子中,当s4创建出来的时候,我们会将s1,s2,s3之间的diff进行一个merge,直接多了一层从s1直接到s3的diff,就是level1行所展示的。同理,当s10创建出来的时候,我们还会有level3的Snapshot diff的计算。

在HDFS Snapshot中,当我们需要删除中间某个Snapshot的时候,它的内部diff(它和其下一个Snapshot间的diff)需要和它前一个Snapshot的diff(前一个快照和待删除快照间的diff)进行一个combine操作。举个例子,假设上述Snapshot列表内,我们删除了中间的s4快照,那么s4->s5的diff需要combine到s3->s4中的diff中去。这个很好理解,假设有个文件在s4到s5之间被删除了,s3还记着这个已经删除的INode,其实没有必要了。

假设我们还是使用原有单一链表组织方式,我们如果想对上面的s1和s10做个diff,中间就要经历多达9个diff的combine操作,在SkipList的模式下,它就能够马上从level3中查到diff结果。这其实就是利用空间换时间的一个典型例子了。

至于SkipList实现的HDFS Snapshot快速diff的代码实现可阅读HDFS-11225上的代码实现,这里就不展开进一步分析了。

引用


[1].https://issues.apache.org/jira/browse/HDFS-11225

猜你喜欢

转载自blog.csdn.net/Androidlushangderen/article/details/105256532