QJM 协议

优质
小牛编辑
127浏览
2023-12-01

QJM协议简介

QJM是QuorumJournalManager的简介,是Hadoop V2中的namenode的默认HA方案。qjm方案简单,只有两个组件:journal node和libqjm,qjm方案并不负责选主,选主交由外部实现,例如基于zookeeper实现。libqjm负责journal数据的读写,其中包括journal在异常情况下的一致性恢复;journalnode负责log数据的存储。

img

QJM的Recovery算法是一个basic paxos实现,用于协商确认最新的in progress log segment的内容(实际操作是确认log segment中的txid范围),因为已经进行了选主,写入的edit log可以按照Multi-Paxos进行优化,跳过basic paxos中的Prepare阶段直接进入Accept阶段,即向2n+1个节点写入n+1个节点返回成功即成功,来降低写入数据内容协议一致的延迟,这样其延迟为:median(Latency @JNs)。

上面的描述很简单,但是下面有很多非常有意思的问题:

  1. 当一个JN收到了edit,其他节点没有收到,这个时候namenode挂掉了怎么处理?
  2. 当两个namenode都声明自己是active,出现“脑裂”怎么处理?
  3. 多个节点在写入过程中挂掉,如何从不一致状态中恢复?
  4. Namenode在写入或者是finialized的过程中挂掉,如何从不一致状态中恢复?
  5. 如果standy namenode在failover过程中挂掉,如何从不一致状态中恢复?

QJM分布式协议是基于paxos的实现,能够解决上述问题,并确保数据在节点间的一致。具体来讲,使用Multi-Paxos进行commit每批edit log,使用Paxos进行standy namenode进行failover时的recovery。

http://blog.cloudera.com/blog/2012/10/quorum-based-journaling-in-cdh4-1/

整体QJM的流程:

  1. Fencing prior writers
  2. Recovering in-progress logs
  3. Start a new log segment
  4. Write edits
  5. Finalize log segment
  6. Go to step 3

Fencing

Fencing是分布式系统中解决“脑裂”问题的解药,新的active namenode能保证老的active namenode不会再修改系统元信息。QJM中Fencing的关键在于epoch number,具有如下属性:

  • 当一个writer变为active,需要赋值一个epoch number
  • 每个epoch number都是唯一的,两个writer不会有同一个epoch number
  • Epoch number定义writer的序,epoch number越大writer越新

Epoch number的实现流程如下:

  • 在开始写入edit log之前,QJM先生成一个epoch number
  • QJM向全部journalNode发送newEpoch请求,并带上生成的epoch number,只有多数JournalNode应答成功才算成功
  • JournalNode收到newEpoch,将其持久化到本地的lastPromisedEpoch中
  • 任何对edit log修改的RPC都需要带上epoch number
  • JournalNode在处理非newEpoch请求时,会检查请求中的epoch number和本地的lastPromisedEpoch。如果请求中的epoch小,直接拒绝;如果请求中的epoch大,更新本地的lastPromisedEpoch,。这样即使一个JN挂掉再起来过程中有新的writer,也能更新lastPromisedEpoch。【没有master进行节点加入并设置最新的epoch number】

上面的这些策略可以保证:一旦QJM收到newEpoch(N)的多数成功应答,那么任何epoch小于N的writer都不会成功写入多数节点。这样就能够解决两个namenode的脑裂问题。

上面的lastPromisedEpoch主要用来进行fencing,并影响AcceptedEpoch(每一轮的promisedEpoch肯定会递增,Prepare的时候取当时的promisedEpoch作为AcceptEpoch)。JournalNode上除了lastPromisedEpoch还需要一个lastWriterEpoch,用于recovery的时候比较unfinialized的edits数据版本。

上面流程中有一个问题没有解释清楚,就是QJM如何设置初始epoch number,这里面就有一个生成epoch的流程:

  1. QJM发送getJournalState()给全部JournalNode,journalNode返回本地的lastPromisedEpoch。
  2. QJM收到多数应答的时候,选取最大值并加1作为proposedEpoch.
  3. QJM发送newEpoch(proposedEpoch)给全部JournalNode,JournalNode与本地的lastPromisedEpoch进行比较,如果proposed大,就更新本地的lastPromisedEpoch,并返回成功,否则返回失败。
  4. QJM如果收到多数应答,则设置epoch number为proposedEpoch;否则抛出异常并终止启动。

Recovery

不变量

在讨论recovery之前应该先讨论有些系统依赖的不变量:

  • 一旦log被finialized,就不是unfinialized了
  • 如果一个segment以N开始,那么一定在多数节点上包含以N-1结尾的finialized的segment
  • 如果一个finialized segment以N结束,那么一定在多数节点上包含以N结尾的finialized的segment

这些系统依赖的不变量会影响后面recovery source选取的策略。

Recovery算法

当一个新的writer启动之后,上一个writer可能还留下部分log segment处于in progress状态。新writer在写入新的edit log之前,需要对in progress的log segment进行recovery,并进行finialize。因此recovery的工作就是要保证各个JN对in progress的logsegment达成一致,并进行finialize。Recovery算法是一个Paxos过程,利用少数服从多数、后者认同前者的原则,保证in progress的数据一致性。

具体Recovery的算法如下:

  • Determining which segment to recover:

每个JN在响应newEpoch的时候都会返回最新log segment的transaction id。

  • PrepareRecovery RPC:

QJM向每个JN发送PrepareRecovery请求获取最后一个log segment的状态,包括长度和是否finialized。如果JN上journal id状态为accepted,那么就返回上次accept的writer的epoch。

这个请求和应答对应到paxos的Prepare (Phase 1a) 和 Promise (Phase 1b)

  • AcceptRecovery RPC:

QJM收到PrepareRecovery的应答之后,新的writer选择一个JN作为source进行同步,其必须包含之前已经committed的transactions。AcceptRecovery RPC中包含segment的状态和source的URL。

AcceptRecovery对应到Paxos中的Phase 2a,通常叫做Accept。

当JN收到AcceptRecovery的时候,进行如下操作:

  1. Log Synchronization:如果本地没有或者长度不同,就从source中下载对应的log segment到本地。
  2. Persist recovery metadata:JN将segment的状态(segment的起止id和状态)和当前writer的epoch持久化到disk上,这样同样segment id的PrepareRecovery请求的时候,将这些metadata作为应答。

  3. Finalize segment:

这个时候多数JournalNodes已经有相同的log segment了,并且都持久化了recovery metadata,即使后面又有PrepareRecovery最终都会得到相同的结果。最后只需要简单的调用FinializeLogSegment即可。

Recovery Source选取

从上面的Recovery算法可以看出,关系到数据安全性最核心的就是接收到PrepareRecovery应答之后,QJM进行Recovery Source的选取。选取规则如下:

  1. 如果一个JN没有对应的log segment,不能作为source
  2. 如果一个JN有对应的finialized log segment,说明上一轮Recovery已经走到Finalize segment阶段或者是不需要Recovery。这个JN应该作为source
  3. 如果有多个JN有对应的in progress log segment,需要按照如下比较:

a) 对于每个JN,以PrepareRecovery应答中的lastWriterEpoch和AcceptEpoch的最大值作为maxSeenEpoch

b) 如果有一个JN的maxSeenEpoch大于其他JN,那么选择这个JN作为source

c) 如果maxSeenEpoch相同,选择transaction id更大(拥有更多的edit log)的JN作为source

【讨论】为什么这里选取transaction id最大的而不是像AFS那样选取长度最长的n+1个副本中的最小值?因为hdfs中提供了logSync和log两种接口,log是一种异步的接口,可能会出现已经回复了用户,但是没有写到后端的情况,这个时候尽量恢复更多的edit log能降低log丢失的风险。

Write Edits

当一批edits开始写入的时候,QJM会同时向全部JN节点发送请求,JN收到journal请求会会进行如下操作:

  1. 检查请求中的epoch number是否与lastPromisedEpoch一致
  2. 检查请求中edits的txid是否跟本地txid是否有空洞或乱序
  3. 将journal写入本地磁盘并进行sync
  4. 返回成功

QJM会等待多数JN的应答,只有多数JN节点应答成功才返回成功。如果某个JN挂掉或返回失败,将其标记为“out of sync”,当前log segment的后续journal请求不再向其发送。

Read Edits

QJM中只有finialized的log segment才可读,因为QJM已经保证了finialized log segment在多数JN上已经对其内容达成一致。如果要读取in progress的log segment,虽然log segment前半部分数据可能已经达成一致,但是整体内容并没有达成一致,QJM并不知道一致点的位置,需要读取多数JN,根据epoch和txid的大小才能确定,数据是否已经在多数JN上达成一致,跟Recovery算法流程差不多,实现较复杂。

当前QJM在读取的时候,先向全部JN发送一个getEditLogManifest(),获取finialized的segment视图,JN暴露http接口来获取finialized的log segment数据,QJM建立RedundantEditLogInputStreams进行读取。

最后更新:

类似资料

  • Git 可以使用四种主要的协议来传输资料:本地协议(Local),HTTP 协议,SSH(Secure Shell)协议及 Git 协议。 在此,我们将会讨论那些协议及哪些情形应该使用(或避免使用)他们。 本地协议 最基本的就是 本地协议(Local protocol) ,其中的远程版本库就是硬盘内的另一个目录。 这常见于团队每一个成员都对一个共享的文件系统(例如一个挂载的 NFS)拥有访问权,或

  • 协议为方法、属性、以及其他特定的任务需求或功能定义蓝图。协议可被类、结构体、或枚举类型采纳以提供所需功能的具体实现。满足了协议中需求的任意类型都叫做遵循了该协议。 除了指定遵循类型必须实现的要求外,你可以扩展一个协议以实现其中的一些需求或实现一个符合类型的可以利用的附加功能。 协议的语法 定义协议的方式与类、结构体、枚举类型非常相似: protocol SomeProtocol { //

  • 本页包含内容: 协议的语法(Protocol Syntax) 对属性的规定(Property Requirements) 对方法的规定(Method Requirements) 对突变方法的规定(Mutating Method Requirements) 对构造器的规定(Initializer Requirements) 协议类型(Protocols as Types) 委托(代理)模式(Dele

  • 在周星驰的电影《唐伯虎点秋香》中,周星驰饰演的主角一进入华府,就被强制增加了一个代号9527。从此,华府的人开始称呼主角为9527,而不是他的姓名。 域名(domain name)是IP地址的代号。域名通常是由字符构成的。对于人类来说,字符构成的域名,比如www.yahoo.com,要比纯粹数字构成的IP地址(106.10.170.118)容易记忆。域名解析系统(DNS, domain name

  • 要想团队协作使用Git,就需要用到Git协议。 3.1.1. Git支持的协议 首先来看看数据交换需要使用的协议。 Git提供了丰富的协议支持,包括:SSH、GIT、HTTP、HTTPS、FTP、FTPS、RSYNC及前面已经看到的本地协议等。各种不同协议的URL写法如表15-1所示。 表 15-1:Git支持的协议一览表 协议名称 语法格式 说明 SSH协议(1) ssh://[user@]ex

  • 协议(Protocols)为方法,属性和其他需求功能提供了蓝图。 它为方法或属性骨架而不是实现。 通过定义类,函数和枚举,可以进一步完成方法和属性的实现。 协议的一致性满足了协议要求的方法或属性。 语法 协议也遵循与类,结构和枚举类似的语法 - 协议在类,结构或枚举类型名称之后声明。 单个和多个协议声明也是可以的。 如果定义了多个协议,则必须用逗号分隔。 当要为超类定义协议时,协议名称应使用逗号跟

开发工具

协同放置