一文搞定Journal Node原理
【概述】hdfs的HA机制,具体来说可以分为两部分,一部分是基于zkfc、zookeeper完成nn之间的选主;而另一部分则是nn之间的元数据共享与同步。从hdfs2.0版本开始,引入了H...
【概述】
hdfs的HA机制,具体来说可以分为两部分,一部分是基于zkfc、zookeeper完成nn之间的选主;而另一部分则是nn之间的元数据共享与同步。
从hdfs2.0版本开始,引入了HA using Quroum Journal Manager(QJM)方案。
QJM的思想最初来源于paxos协议。在具体实现中,Journal Node(JN)集群作为NN之间editlog的一致性存储系统,也是HDFS高可用方案中的核心组件。
通过JN,ANN可以尽可能及时的将元数据同步到SNN,ANN通过push的方式将editlog写入JN集群,而SNN通过pull的方式从JN中读取editlog,JN之间彼此不主动进行数据的交互。
由于paxos协议的关系,读写JN都要求至少大多数节点成功返回才认为本次请求是成功的。另外,2N+1台JN组成的集群,最多可以容忍N个JN节点异常。因此整体具有很强的HA能力。
本文就其中的一些细节展开进行说明。
【JN中的概念与持久化文件】
一些概念:
epoch
epoch是paxos协议中的一个概念,可以用于标识leader。
而具体实现中,epoch是一个单调递增的整数,用来标识每一次ANN的生命周期,每发生一次NN的主备切换,epoch就会加1。
ANN后续的每个请求中,都会携带epoch,同时JN会对请求进行校验,如果携带的epoch比本地存储的小,则抛出异常;而如果携带的epoch比本地存储的大,则更新本地的epoch。
这就意味着,不管怎样进行NN的切换,任意时刻都只能有1个NN成功写JN。
txid
每条写入的editlog都有一个对应的递增的事务ID。
segment
多个连续的事务(txid)组成一个segment,持久化到一个对应的editlog文件中。
这样,对应到具体实现中, 任何时间,JN上只会有一个segment处于正在写的状态(Inprogress),而其他的segment文件则都处于写完关闭的状态(Finalized)
JN中的持久化文件:
|-- current
| |-- committed-txid
| |-- edits_0000000000000000001-0000000000000000002
| |-- edits_inprogress_0000000000000000003
| |-- last-promised-epoch
| |-- last-writer-epoch
| |-- paxos
| |-- VERSION
|-- in_use.lock
last-promised-epoch
有两个地方会写入,一个是ANN发送新的epoch的rpc请求时,会进行更新;另一个是每次editlog的写请求时,会判断请求中的epoch是否比本地的epoch大,如果是则进行更新,否则会抛出异常。
last-writer-epoch
每写一个新的segment时,检查请求中的epoch是比上次写入的epoch大,如果大,则将当前请求中的epoch写入文件中。
committed-txid
处理每个写editlog的事务请求中,同步更新本地记录的事务ID。
edits_$starttxid-$endtxid
已经写完关闭的segment文件,该文件记录了从开始事务ID到结束事务ID的连续的editlog信息。
edits_inprogress_$lasttxid
当前正在写的segment文件,文件名中记录了开始事务ID
VERSION
记录集群的相关信息,包括命名空间ID,集群ID,创建时间等。
【正常读写流程】
1. 格式化
在集群初始化时,首先会进行格式化动作,具体是由NN触发进行的。
JN收到格式化的请求后,先删除editlog存储目录下的所有文件,然后将NN的相关信息写到该目录下的VERSION文件中。
VERSION文件只有在格式化时才会写入,而如果本地没有VERSION,则会认为没有格式化,所有的请求都无法正确处理。
2. 设置epoch
在HA模式下,格式化完成后,两个NN通过zkfc完成选举动作,成为Active的NN首先向JN集群发送getJournalState的RPC请求,JN收到请求后返回自己保存的已经通过的epoch(lastPromiseEpoch)
NN收到大多数JN返回的epoch后,选择其中最大的epoch,并加1作为当前新的epoch,然后向各个JN发送newEpoch的RPC请求(请求中会带上新的epoch)
每个JN收到新的epoch后,首先检查这个新的epoch是否比它本地保存的lastPromiseEpoch大。
如果大的话就把lastPromiseEpoch更新为新的epoch,并且向NN返回自己本地磁盘上最新的一个editlogSegment的起始事务ID,为后续的数据恢复做准备;
如果小于或等于就向NN返回错误,表示该JN接收了更新的提案。
NN收到大多数JN对newEpoch的成功响应之后,就会认为生成新的Epoch成功。
在此之后进行editlog的恢复动作(对于新部署的,或者正常结束的集群,不会触发进行),这里暂时不展开,后面会讲解
完成恢复之后,则可以进行editlog的写入。
3. 读写editlog
ANN向JN写editlog的流程其实比较简单,就是向所有JN发送一个RPC请求,当收到多数节点的成功响应后,认为本次editlog的写入是成功的。
从JN中读取editlog的工作,则是在SNN中发生的。
SNN启动后,内部会创建一个EditLogTail的线程。具体读逻辑包括下面四个步骤
1)向所有JN请求从指定事务之后的editlog文件集合
2)对所有返回的editlog文件清单,按起始事务ID进行排序,并去除重复的文件
3)依次向editlog文件对应的jn发送请求,通过Http下载editlog文件
4)对下载后的editlog文件,依次读取每一条editlog并进行动作重放,即更新内存中inode、block等元数据信息。
注意:SNN的EditlogTail会定时读取从JN读取editlog信息,读取editlog并进行对应的重放后,只是在内存中对元数据进行了更新,并没有实际持久化到本地盘中。
如下图所示:
并且从nn的日志中可以看出是有定时请求读取editlog信息的。
只有在定时触发checkpoint的时候,此时会再次读取editlog的信息(每次读取后在内存中更新了最新的事务ID,如果异常重启则是从本地文件中读取最后更新的事务ID),然后将元数据信息以fsimage的形式存储在本地,同时记录最后一次的事务ID。
【JN异常后的同步】
正常运行过程中,如果某个JN出现了异常,ANN内部会对该JN进行标记,后续不再给该JN发送editlog。直到触发进行滚动日志操作,此后如果异常的JN恢复正常,则继续向该JN写editlog。
也就是说,当JN出现异常后,ANN先标记该JN为异常,后续的editlog不写入。正在写入的editlog会有重试机制继续尝试;另外,当开始写新的segment文件时(滚动日志),如果该JN已经恢复正常,则接着写入,之前segment没有写入的editlog不会进行补写。
如下所示,JN异常时,从1465开始的editlog没有正确保存到segment文件中,以及之后的一段都因JN异常没有写入。
edits_0000000000000001478-0000000000000001479则是JN恢复正常之后写入的segment文件,中间缺失的editlog不会补偿写入。
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:03 edits_0000000000000001457-0000000000000001458
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:05 edits_0000000000000001459-0000000000000001460
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:07 edits_0000000000000001461-0000000000000001462
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:09 edits_0000000000000001463-0000000000000001464
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001478-0000000000000001479
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001480-0000000000000001481
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001482-0000000000000001483
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001484-0000000000000001485
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001486-0000000000000001487
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001488-0000000000000001489
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001490-0000000000000001491
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001492-0000000000000001493
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001494-0000000000000001495
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001496-0000000000000001497
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001498-0000000000000001499
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:40 edits_0000000000000001500-0000000000000001501
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:41 edits_0000000000000001502-0000000000000001503
-rw-r--r--. 1 hadoop hadoop 42 Oct 19 22:43 edits_0000000000000001504-0000000000000001505
-rw-r--r--. 1 hadoop hadoop 1048576 Oct 19 22:09 edits_inprogress_0000000000000001465
-rw-r--r--. 1 hadoop hadoop 1048576 Oct 19 22:43 edits_inprogress_0000000000000001506
【JN异常后的同步】
当ANN出现异常时,通过ZKFC以及ZK,SNN会成为新的ANN,由于原来的ANN异常时,JN集群中的不同节点,其写入的editlog可能不一致,因此新的ANN先要保证所有JN的数据一致后,才能进行写入,这个过程就是JN的数据恢复流程,具体包括如下几个步骤:
1. 向所有JN获取当前的epoch,从结果中选取最大的epoch。
2. 将最大的epoch加1后,作为新的epoch,然后写入所有JN中。
3. 从所有JN获取最新segment的起始事务ID(txid),该步骤实际上是上一步骤请求的响应结果中带回来的,这样就减少了一次rpc请求。
例如下面,两个JN,正在写的segment文件,起始的事务ID均为16,因此在newEpoch的请求中,都返回了16。
4. 从结果中选取最大的事务ID作为参数,然后向所有JN发送prepareRecovery的RPC请求。
5. JN收到请求后,根据自身情况,返回指定起始事务ID的segment信息,包括起始事务ID、结束事务ID、以及当前segment是否为正在写。
接着上图的流程,jn-1中,已经处理了事务ID为28的editlog请求,因此返回16-28。而jn2仅处理到16的事务ID(之前出现了异常,导致后续的editlog没有写进来),因此返回16-16。
6. ANN从请求结果中,选取最优的segment,并组装为URL,包括该segment所在的JN、命名空间信息、以及segment的信息,将该URL作为参数,向JN发送AcceptRecovery的RPC请求,要求进行数据的同步。
7. JN收到请求后,对URL进行合法性校验,然后向指定的JN发送HTTP请求,下载对应的segment。
ann收到请求响应后,选择需要恢复的segment,显然16-28是最新最全的数据,因此将该起始段的segment作为待恢复的数据,发送给所有jn。
jn1收到数据后,发现本地已经是最新的了,因此忽略处理。而jn2发现本地数据有落后,因此解析segment的url,向jn1发送数据同步的http请求,完成数据下载。
8. JN在发送完AcceptRecovery的RPC请求后,会继续向JN发送一条finalizedSegment的请求,要求关闭当前正在写的segment,然后打开新的segment进行写。
对照jn1、jn2的日志也能更好的理解这一流程。
jn1的日志如下图所示:
jn2的日志如下图所示:
【其他常见问题】
JN写editlog是试试刷新到本地磁盘的吗?
不是,JN将editlog写入本地磁盘和NN写editlog到本地是复用了同一份代码,定时触发刷盘。
SNN从JN读editlog是实时读取的吗?
不是,周期性进行读取,默认时间间隔为1分钟。
JN存储的editlog会自动删除吗?
不会,SNN触发执行checkpoint时才会进行删除,同时还会进行一定数量的事务的保存。
【巨人的肩膀和推荐阅读】
1. https://hexiaoqiao.github.io/blog/2018/03/30/the-analysis-of-basic-principle-of-hdfs-ha-using-qjm/
好了,本文就介绍到这里,原创不易,点赞,在看,分享是最好的支持, 谢谢~
更多推荐
所有评论(0)