HDFS集群随着使用时间的增长,难免会出现一些“性能退化”的节点,主要表现为磁盘读写变慢、网络传输变慢,我们统称这些节点为慢节点。当集群扩大到一定规模,比如上千个节点的集群,慢节点通常是不容易被发现的。大多数时候,慢节点都藏匿于众多健康节点中,只有在客户端频繁访问这些有问题的节点,发现读写变慢了,才会被感知到。
因此,要想维护HDFS集群读写性能稳定,慢节点问题一直是一个绕不开的话题。在Hadoop2.9之后,社区支持了从Namenode jmx上查看慢节点的功能,小米基于此框架,开发了一整套HDFS集群慢节点监控及自动处理流程,在实际生产环境中,具有不错的应用效果,可以保证慢节点能够被及时准确发现,并在NameNode端自动规避处理。
为了保证高可靠性,文件默认以三副本的形式保存在HDFS上,当client开始写入一个新文件的时候,首先会找出三个DataNode建立pipeline,随后数据以packet为最小单元,依次通过pipeline里的三个DN,完成三副本写入。
具体的写入流程是:首先DN0收到从client端传过来的packet,DN0立即将packet传至下游DN,随后对packet进行落盘操作,DN1的操作与DN0一致,DN2作为pipeline里的最后一个节点,在落盘数据之后,会向上游DN发送ack,DN1在收到ack后继续向上游转发,DN0在收到DN1的ack后向client端转发,client收到DN0的ack即完成一次完整的pipeline写入流程。
在写pipeline的过程中,如果出现了网络慢节点,步骤1的耗时会明显增加,如果出现的是磁盘慢节点,步骤2的耗时会明显增加,因此,HDFS的慢节点监控,主要是监控步骤1和步骤2的耗时。
监控一个DN网络传输变慢的原理是,记录集群里各个DN间的数据传输耗时,找出其中的异常值并作为慢节点上报给NN。正常情况下各节点间的传输速率基本一致,不会相差太多,假如出现了A传B耗时异常,A就往NN上报B是慢节点,NN端在汇总所有慢节点报告后,会判断是否存在慢节点。比如X节点是故障节点,那么一定会有很多节点往NN上报X是慢节点,最终在NN端出现类似的慢节点报告,表明X是慢节点。
为了计算DN往下游传数据的平均耗时,DN内部维护了一个
HashMap<String,Queue<SampleStat>>
,HashMap的key为下游DN的ip,value是一个存放SampleStat对象的队列。SampleStat对象用于记录往下游DN传输packet的数量与总耗时,DN默认每五分钟做一次snapshot,生成一个SampleStat,用于记录在这五分钟内,往下游DN传输数据的网络情况,并存入HashMap的Queue。
HashMap的Queue是一个固定大小的队列,队列长度36,如果队列满了,就会踢掉队首成员把新的SampleStat加入队尾。这意味着,我们只会监控最近3小时(36 X 5=180min)的网络传输情况,目的是为了保证监控数据具有时效性。
这样,我们就可以知道DN1往集群其他DN传数据的耗时情况,如果存在慢节点,数据从DN1传到它的耗时一定会比其他线路高很多。
这里还要考虑到几个场景,一个是如果在最近五分钟内,DN1没有往DN2传输数据,那SampleStat里的数据应该取什么?答案是直接复用上一个五分钟的数据,因为我们不能说在这五分钟没有数据传输,网络就是没有问题的,有很大的可能,它的网络状况和前一个五分钟是一致的,所以在没有数据传输的情况下,直接复用上一周期的数据是合理的。
更进一步的,如果超过3个小时DN1都没有往DN2传数据,HashMap对应的Queue将会出现36个一样的SampleStat,那么基于Queue生成的慢节点报告将不具备时效性。因此我们需要在DN做snapshot的时候,给SampleStat带上一个时间戳,保证我们只监控最近3个小时的数据。
还有一个问题是,pipeline里有三个DN,DN间有两段网络传输,这两段网络传输我们都需要关注吗?其实我们只需要关注最后一段,即DN1往DN2传数据的情况就可以。一个是为了精简统计量,另一个原因是,第一段网络传输(DN0到DN1)在其他pipeline里可能就是最后一段,所以从整体上看只要监控最后一段的传输延迟,可以保证集群所有DN都能被监控到。
DN在上报心跳的时候会判断是否需要生成SlowPeerReport,并将其作为心跳信息的一部分发送给NN。SlowPeerReport的生成周期与DN做snapshot生成SampleStat的周期一致,默认取五分钟。首先从Queue取出所有SampleStat做叠加,求算数平均值averageLatency,那么在最近三小时,DN1往DN2传数据的平均延迟就是averageLatency1_2。然后根据这些averageLatency,算出慢节点上报阈upperLimitLatency。当有节点的averageLatency大于upperLimitLatency,即认为该节点属于一个网络慢节点,且由DN1上报。最后生成对应的SlowPeerReport,通过心跳上报给NN。
对于upperLimitLatency的计算,我们先算出averageLatency的中位数median,并以此算出中位数绝对偏差MAD(Median absolute deviation)[1]。MAD是一个健壮的统计量,比标准差σ更能适应数据集中场景的异常值,也就是说少量的异常值不会影响最终结果。
MAD=median(∣Xi−median(X)∣)
通常认为median + 3 x MAD是异常值,出现的概率是千分之几,这一点类似于正态分布的3σ定律[2]。
因此慢节点上报阈值upperLimitLatency可以取median + 3 x MAD,但是我们也要考虑到集群的容忍度,设定一个固定的lowThreshold,取两者间的较大值为upperLimitLatency。
NN内部维护一个慢节点HashMap<String,List<ReportingNode>,用于汇总慢节点信息。它的key是慢节点IP,value是对应的上报节点。NN在收到心跳后,从慢节点报告中解析出慢节点slowNode及其对应上报节点reportingNode,更新至HashMap。reportingNode会记录心跳的上报时间,用于判断所上报的慢节点报告是否具有时效性,一般取DN上报慢节点周期间隔的3倍为慢节点报告有效期,即5*3=15min。
慢节点报警通过使用一个独立的探测程序Canary实现,Canary会定期查询NN jmx,触发getSlowPeersReport方法,遍历HashMap查询是否存在slowNode。
为了解决报警恢复的问题,我们通过修改DN心跳协议,在慢节点报告中增加当前五分钟传输延迟currentLatency。基于此,报警触发以及恢复的标准如下:
磁盘不同于网络,一个机器上可以部署多块磁盘,磁盘间的请求量不一定是均匀的,所以只能以盘为单位对机器进行监控。在均匀写入的情况下,坏盘通常只会表现出间歇性的卡顿,从平均值看它的写入速度并不慢。因此如果直接参照网络传输去算median和MAD,去找异常值,得到的结果很可能就是这块盘一会报正常一会报异常,探测准确率很低。
正常情况下对hdfs写入来说,各个盘的写入量是差不多的,如果进入写入高峰期,那就是整个节点所有盘的写入速度都明显变慢,没有异常值,这种情况是不会报磁盘慢的。但如果出现特殊情况,只有个别盘突然进入一个短暂高峰期,它的写延迟将明显上涨,直接判断这是一个慢磁盘显然是不合理的,因此慢节点探测需要规避掉这类场景。
为了提高探测准确率,我们决定采用计数的方式来判断这个磁盘是否真正出了问题。每5分钟做一次统计,如果在一个小时内,出现了6次以上的写入慢,可以认为这是一块有问题的磁盘,将生成SlowDiskReport报告给NN。
具体的实现方式是,每个盘都维护一个长度为12的Queue,每5分钟尝试更新一次Queue,如果盘的WriteIo平均值大于upperLimitLatency,就将此刻timestamp加入Queue,表示在这一周期出现了写入磁盘慢,如果Queue满了就把队首成员踢掉再入列。在生成SlowDiskReport的时候,如果队列是满的且队首成员的timestamp在一个小时以内,就意味着,在最近一个小时,磁盘写慢的时间超过了30分钟,该磁盘将会被加入SlowDiskReport上报给NN。
我们进一步扩展了慢节点监控的功能,在NN端维护一个HashMap,用来记录已经上报的慢节点。这样NN在选DN建立pipeline的时候,可以尽量规避掉这些存在问题的节点。NN周期性查询慢节点报告,当发现新的慢节点信息,就更新HashMap,它的key为节点ip,value是慢节点信息过期时间expire time,expire time=current+ interval,用来表明这个节点在expire time之前会标识为慢的状态。
NN在选DN的时候,如果选中了慢节点,就加入exclude列表,重新选一次,最终选到正常节点建立pipeline。但是这里也要考虑集群中正常节点数不够的情况,如果遍历完所有可用节点,依然选不到DN会抛NotEnoughReplicasException,此时将触发重新选择,这一轮的选择不会考虑慢节点场景,这样可以保证NN在选DN的时候尽量不会选到慢节点。
该功能目前已经上线小米规模最大的离线集群,探测的准确度在90%以上,已累计为集群找出上百块坏盘。NN自动处理慢节点极大节省了人工介入成本,SRE只需要每周进行一次故障节点处理,即可保证集群读写性能稳定。
[1]中位数绝对偏差MAD:https://en.wikipedia.org/wiki/Median_absolute_deviation
[2]正态分布:https://en.wikipedia.org/wiki/Normal_distribution