HDFS NameNode 高并发数据读写架构及QJM选举深入研究

本套技术专栏是作者(秦凯新)平时工作的总结和升华,通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。期待加入IOT时代最具战斗力的团队。QQ邮箱地址:[email protected],如有任何学术交流,可随时联系。

1 Hadoop 2.x 系统架构演进

  • 2.x版本中,HDFS架构解决了单点故障问题,即引入双NameNode架构。
  • 同时借助共享存储系统来进行元数据的同步,共享存储系统类型一般有几类,如:Shared NAS+NFS、BookKeeper、BackupNode 和 Quorum Journal Manager(QJM),下图中用的是QJM作为共享存储组件,通过搭建奇数结点的JournalNode实现主备NameNode元数据操作信息同步。

  • QJM全称是Quorum Journal Manager, 由JournalNode(JN)组成,一般是奇数点结点组成。每个JournalNode对外有一个简易的RPC接口,以供NameNode读写EditLog到JN本地磁盘。当写EditLog时,NameNode会同时向所有JournalNode并行写文件。

2 高并发与QJM写过程息息相关(高并发)

  • EditLog同时写到本地和JournalNode两处地方。
  • 写本地由配置中参数dfs.namenode.name.dir控制。
  • 写JN由参数dfs.namenode.shared.edits.dir控制。
  • 在写EditLog时会由两个不同的输出流来控制日志的写过程,分别为:EditLogFileOutputStream(本地输出流)和QuorumOutputStream(JN输出流)。
  • NN写EditLog也不是直接写到磁盘中,为保证高吞吐,NameNode会分别为EditLogFileOutputStream和QuorumOutputStream定义两个同等大小的Buffer,大小大概是512KB,一个写Buffer(buffCurrent),一个同步Buffer(buffReady),这样可以一边写一边同步,所以EditLog是一个异步写过程,同时也是一个批量同步的过程,避免每写一笔就同步一次日志。
  • 双写双同步过程:hadoop定义了一个缓冲区交换的过程,即bufferCurrent和buffReady。在达到条件时会触发交换。一个负责EditLog写入缓冲区,另外一个缓冲区负责写入磁盘并同步到JournalNodes。
  • 如bufferCurrent在达到阈值同时bufferReady的数据又同步完时,bufferReady数据会清空,同时会将bufferCurrent指针指向bufferReady以满足继续写,另外会将bufferReady指针指向bufferCurrent以提供继续同步EditLog到JN。

  • NameNode使用多线程接收多个客户端发送过来的并发的请求,但是瓶颈却出现在后续的写edits log上。此时就会出现本地磁盘 + 网络传输给journalnodes,性能两大瓶颈:磁盘写 + 网络写!

  • 分段加锁机制 + 内存双缓冲机制

  • 多个客户端线程可以快速的获取锁,生成全局锁txid,然后快速的将edits log写入内存缓冲。每一次只会有一个线程获取全局锁,但是注意因为是写缓冲区,所以很快就会释放锁。

  • 缓冲区的交换发生在EditLog同步结束后,当来一个线程进行检查有没有谁在写磁盘和网络,如果没有了直接交换双缓冲的区域buffCurrent和区域buffReady,接着第二次释放锁。这个过程相当快速,内存里判断几个条件,耗时不了几微秒。

  • 同步EditLog到JN时,有一个专有线程可以批量的将一个缓冲中的多条edits log刷入磁盘和网络。

  • 因此只要没有线程在写磁盘和网络,就达到了缓冲区交换的目的。

  • 也就因此达到了每秒数千次的高并发请求。

      详情请参考石杉的架构笔记:https://juejin.im/post/5bec278c5188253e64332c76
    复制代码

3 QJM的隔离双写机制

  • 在ANN每次同步EditLog到JN时,先要保证不会有两个NN同时向JN同步日志。这个隔离是怎么做的。这里面涉及一个很重要的概念Epoch Numbers,很多分布式系统都会用到。Epoch有如下几个特性:

  • 当NN成为活动结点时,其会被赋予一个EpochNumber 每个EpochNumber是惟一的,不会有相同的EpochNumber出现 EpochNumber有严格顺序保证,每次NN切换后其EpochNumber都会自增1,后面生成的EpochNumber都会大于前面的EpochNumber

  • QJM是怎么保证上面特性的呢,主要有以下几点:

      第一步,在对EditLog作任何修改前,QuorumJournalManager(NameNode上)必须被赋予一个
      EpochNumber
      第二步, QJM把自己的EpochNumber通过newEpoch(N)的方式发送给所有JN结点
      第三步, 当JN收到newEpoch请求后,会把QJM的EpochNumber保存到一个lastPromisedEpoch
      变量中并持久化到本地磁盘
      第四步, ANN同步日志到JN的任何RPC请求(如logEdits(),startLogSegment()等),都必须包
      含ANN的EpochNumber
      第五步,JN在收到RPC请求后,会将之与lastPromisedEpoch对比,如果请求的EpochNumber小于
      lastPromisedEpoch,将会拒绝同步请求,反之,会接受同步请求并将请求的EpochNumber保存在
      lastPromisedEpoch
    复制代码
  • 这样就能保证主备NN发生切换时,就算同时向JN同步日志,也能保证日志不会写乱,因为发生切换后,原ANN的EpochNumber肯定是小于新ANN的EpochNumber,所以原ANN向JN的发起的所有同步请求都会拒绝,实现隔离功能,防止了脑裂。

4 主备NN切换机制

  • 要完成HA,除了元数据同步外,还得有一个完备的主备切换机制,Hadoop的主备选举依赖于ZooKeeper。
  • HealthMonitor: 监控NameNode健康状态,若状态异常会触发回调ZKFailoverController进行自动主备切换。
  • ActiveStandbyElector: 通知ZK执行主备选举,若ZK完成变更,会回调ZKFailoverController相应方法进行主备状态切换。
  • 防脑裂: ZK本身是强一致和高可用的,可以用它来保证同一时刻只有一个活动节点。
  • ZKFailoverController: 是HealthMontior和ActiveStandbyElector的母体,执行具体的切换操作。
  • 本套技术专栏是作者(秦凯新)平时工作的总结和升华,通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。期待加入IOT时代最具战斗力的团队。QQ邮箱地址:[email protected],如有任何学术交流,可随时联系。

5 hadoop的Chunk缓冲机制(文件上传)

  • Chunk缓冲机制:hadoop支持超大规模的文件上传和数据写入,那么在HDFS客户端源码中,内部实现了高效的chunk缓冲机制。上传数据时首先会被写入一个chunk缓冲数组,这个chunk是一个512字节0.5KB大小的数据片段,而最终的容器是一个缓冲数组,一般为9个chunk的大小,该数组可以容纳多个chunk大小的数据在里面缓冲。基于此机制就可以支持大数据量的文件上传。
  • 当chunk缓冲数组都写满了之后,就会把这个chunk缓冲数组根据chunk大小(512字节0.5KB)切割为多个chunk,一个chunk是一个数据片段,默认是512字节0.5KB。
  • 然后多个chunk会直接一次性写入另外一个内存缓冲Packet数据包内,Packet数据包可以容纳127个chunk,大小大致为64KB。
  • Packet数据包机制:通过这个Packet数据包机制的,可以在内存中容纳大量的数据,避免了频繁的网络传输。
  • 内存队列异步发送机制:当一个Packet被塞满了chunk之后,就会放进一个内存队列进行FIFO排队。
  • DataStreamer线程会不断的获取队列中的Packet数据包,通过网络传输直接连续发送Packet数据包给DataNode,当写满一个Block时,就会通知NameNode Block写入完毕。

6 hadoop文件契约机制(大规模HDFS文件写请求过期处理)

  • 多个客户端同时并发的写Hadoop HDFS上的同一个文件是不被允许的,因为HDFS上的文件是不允许并发写的。
  • 通过文件契约机制,可以保证同一时间只有一个客户端线程访问同一个文件。
  • 该客户端通过开启一个契约后台线程,不断的进行文件续约,如果某个契约很长时间没续约了,此时就自动过期掉这个契约,让其他的HDFS客户端来写。
  • 注意问题来了,一个nameNode内部可能有成千上万个客户端来同时修改hdfs上的成千上万个文件,这种情况下,如何保证契约后台线程去优雅的检查成千上万个文件的契约是否过期?
  • 在HDFS内部维护了一个TreeSet数据结构来根据最近一次续约时间对契约进行排序,因此往往最老的契约就排在了TreeSet的最前面,最近续约的契约就放在了TreeSet的最后面。每次检查契约是否过期的时候,不用再遍历成千上万的契约,直接判断TreeSet最前面契约还没过期,那么就不用继续检查了!
  • 如果一个契约过期了,那么就删掉最老契约,然后再检查第二旧契约,依次类推。
  • TreeSet排序 + 优先检查最旧契约的机制在支持大规模HDFS文件写请求上,有非常好的性能提升。

7 总结

一直想深入到Hadoop源码上,仔细的研究一番,奈何时间有限,大数据技术栈体系还没有完全形成,希望我的博客系列在完整成型后,深入的研究一波源码。辛苦成文,实属不易。各自珍惜。

另外参考了大神的作品,来完成我的个人学习笔记,勿怪,谢谢!附上地址:https://juejin.im/post/5bf80bd66fb9a049ee801ad9。
复制代码

本套技术专栏是作者(秦凯新)平时工作的总结和升华,通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。期待加入IOT时代最具战斗力的团队。QQ邮箱地址:[email protected],如有任何学术交流,可随时联系。

秦凯新 于深圳 201812020147

你可能感兴趣的:(HDFS NameNode 高并发数据读写架构及QJM选举深入研究)