Raft与MongoDB复制集协议比较

一文搞懂raft算法一文中,从raft论文出发,详细介绍了raft的工做流程以及对特殊状况的处理。但算法、协议这种偏抽象的东西,仅仅看论文仍是比较难以掌握的,须要看看在工业界的具体实现。本文关注MongoDB是如何在复制集中使用raft协议的,对raft协议作了哪些扩展。html

阅读本文,须要对MongoDB复制集replication有必定认识,特别是replicat set protocol versiongit

带着问题学习分布式系统之中心化复制集一文中,介绍了中心化副本控制协议。在raft(mongodb pv1)中,也是经过先选举出leader(primary),而后经过leader(primary)管理整个复制集。github

在3.2以及以后的版本中,mongodb默认使用protocol version 1。从官方的一些资料、视频能够看到,这个是一个raft-like的协议。本文主要从leader-election和log replication这两个角度来对比mongodb rs pv1与raft,并试图分析差别的缘由。web

须要注意的是,本文全部对MongoDB复制集的分析都是基于MongoDb3.4算法

本文地址:http://www.javashuo.com/article/p-xmvvskpj-bb.htmlmongodb

leader election

首先对raft协议中leader election作几点总结:数据库

  1. 同一任期内最多只能投一票,先来先得
  2. 选举人必须比本身知道的更多(比较term,log index)
  3. 为了understandability,raft中节点之间没有ranking,公平参与投票

选举、投票资格

为了简化协议,使得raft更容易理解,raft中全部节点都能发起选举、参与投票。但在MongoDB中,有更为丰富的选举控制策略,咱们从Replica Set Configuration就能看出来,replica set中的节点能够配置如下属性网络

members: [
    {
      _id: <int>,
      host: <string>,
      arbiterOnly: <boolean>,
      buildIndexes: <boolean>,
      hidden: <boolean>,
      priority: <number>,
      tags: <document>,
      slaveDelay: <int>,
      votes: <number>
    },
    ...
  ],
  • arbiterOnly: Arbiter上没有用户数据,只能投票,不能发起选举,其做用在于用尽可能少的资源使得复制集中节点数目为奇数。
  • hidden:虽然有数据,但对客户端不可见,能够用来作备份等其余用途。hidden的priority必定是0,所以不能够发起选举,可是能够投票
  • priority:A number that indicates the relative eligibility of a member to become a primary. priority为0时是不能发起选举的。
  • votes:是否能够参与投票,mongodb复制集中最多能够有50个节点,但最多只有7个能够投票,其做用在于下降复杂度。

priority

这里再单独强调一下priority,mongodb中app

Changing the balance of priority in a replica set will trigger one or more elections. If a lower priority secondary is elected over a higher priority secondary, replica set members will continue to call elections until the highest priority available member becomes primary.异步

经过rs.reconfig()修改节点的优先级的时候,会触发从新选举。整个复制集会不断发起选举,直到最高优先级的节点成为primary。固然,在选举-投票的过程当中,仍是必须知足候选者数据足够新的约束。

priority颇有用,好比在multi datacenter deploy的状况下,咱们可能根据用户的分布状况来肯定primary在哪一个datacenter。

heartbeat

raft中,只有leader给follower发心跳信息(心跳是没有log-entry的Append Entries rpc),而后follower回复心跳消息。

Followers are passive: they issue no requests on their own but simply respond to requests from leaders and candidates.

在mongodb中,节点两两之间有心跳

Replica set members send heartbeats (pings) to each other every two seconds. If a heartbeat does not return within 10 seconds, the other members mark the delinquent member as inaccessible.

heartbeat

primary handover

在raft中,只有当leader收到来自term更高的节点的消息时,才会切换到follower状态。若是出现网络分割(network partition),那么这个过时的leader还会一直认为本身是leader

If a candidate or leader discovers
that its term is out of date, it immediately reverts to follower
state.

在mongodb中,primary在election timeout时间尚未收到来自majority 节点的消息时,会主动切换成secondary。这样能够避免过时的Primary(stale primary)继续对外提供服务,尤为是MongoDB容许writeConcern:1.

选举过程 - 预投票

raft中,在election timeout超时后,当即会发起选举,执行如下操做

  • 增长节点本地的 current term ,切换到candidate状态
  • 投本身一票
  • 并行给其余节点发送 RequestVote RPCs
  • 等待其余节点的回复
  • 若是获得majority投票,成为leader

mongodb增长了一个预投票的过程(dry-run),即在不增长新的term的状况下先问问其余节点,是否可能给本身投票,获得大多数节点的确定回复以后才会发起真正的选举过程。其做用在于尽量减小没必要要的主从切换,这部分后面还会提到。

log replication

复制集中,各个节点数据的一致性是必需要解决的问题。而对于客户端(应用)而言,复制集则须要承诺已提交的数据不能回滚。

同步or异步

带着问题学习分布式系统之中心化复制集一文中,介绍了复制集中数据的两种复制方式,并分析了各自的优缺点。简而言之,同步方式可靠性更高,但可用性更差,网络延时更大;异步模式则刚好相反。

raft协议则是这两种方式的折中,当log复制到了大多数的节点就能够向客户端返回了。大多数节点既保证了数据的可靠性:数据不会被回滚;又保证了有较高的可用性:只有有超过一半节点存活整个系统就能正常工做。

MongoDB经过Write Concern选项将选择权交给了用户,用户能够根据实际状况来选择将数据复制到了多少节点再向客户端返回。writeconcern有三个参数

  • w:写到多少节点便可向客户端返回
    • 1,默认值,即写primary便可返回,性能最高,延迟最低
    • majority,同raft,写到大多数节点才返回
    • tag set,写到指定的节点才返回,用于特殊场景
  • j:是否写到journal(保证持久化)
  • wtimeout:多长时间若是没有写到w个节点就向客户端返回错误

因为默认写到primary便可向客户端返回,那么不难想到,若是oplog还没有同步到secondary,primary挂掉,那么新选举出来的Primary可能没有最新的已经向客户端确认的数据,致使数据的回滚,后面会提到mongodb经过catchup来尽可能避免回滚。

data flow

带着问题学习分布式系统之中心化复制集中也给出了两种数据从primary到secondary的方式:主从模式,链式模式。其中,主从模式是priamry推送给全部的secondary,显然raft就是这种模式。

而在MongoDB中,能够经过参数settings.chainingAllowed控制使用主从模式,仍是链式模式。默认值为True,即默认状况下,mongodb中secondary能够从其余secondary同步数据,这样secondary能够选择一个离本身最近(心跳延时最小的)节点来复制oplog,在MongoDB中,称oplog的同步源为SyncSource。

push or pull

raft中,leader并行将数据push到follower。而在MongoDB中,primary将数据写到local.oplog.rs,secondary按期从其SyncSource(参考上一节,不必定是从priamry拉数据,也多是从其余secondary)读取oplog,并应用到本地。

深刻浅出MongoDB复制一文中给出了一个oplog拉取的流程

MongoDB选择了pull的策略,显然会加大在writeConcern: majority时的延迟,但对于默认的链式复制,pull是更合适的,由于secondary更清楚本身的SyncSource。

append vs apply

在诸多共识算法中,都是将command封装到有序、持久化的log当中,raft和MongoD也是如此。

对于raft,leader先将log先append到本地的log entries,而后等到收到majority节点的回复后再apply log到状态机,以下入所示:

可是在mongodb中,即便客户端要求writeconcern:majority,primary也是先apply,将变动做用到状态机,再写oplog。以后,secondary再从其SyncSource的local.oplog.rs collection 拉取oplog,本地apply,而后写oplog。

MongoDB先Apply再写oplog,以及异步复制的机制,会致使即便数据没法写到大多数节点(可能primary与其余节点间网络故障),即便向客户端返回写入失败,写到primary的数据也不会回滚。

catchup

catchup既与write concern有关,也跟leader election有关。

mongodb中,有这么一个参数settings.catchUpTimeoutMillis, 其做用是

Time limit in milliseconds for a newly elected primary to sync (catch up) with the other replica set members that may have more recent writes.
The newly elected primary ends the catchup period early once it is fully caught up with other members of the set. During the catchup period, the newly elected primary is unavailable for writes from clients.

也就是说,在primary选举出来以后,会有一段时间,让primary尝试去其余节点读取到更新的写操做(more recent)。直到追加到最新的oplog,或者超时,primary才进入工做状态(接收客户端写请求)

究其缘由,MongoDB容许用户自定义writeconcern,且默认只要求写到primary。所以选举的时候即便获得了大多数节点的投票,且primary的数据在这些大多数节点中是最新的,但原来的primary可能没有参与投票,那么就可能致使数据的回滚。catchup可以尽可能避免回滚的出现,若是没法在settings.catchUpTimeoutMillis时间内完成catchup,也会将回滚的内容写入一个rollback文件。

差别的思考

MongoDB做为一个分布式数据库系统,既要支持OLTP,又要支持OLAP,既要知足水平伸缩,又要保证高可用、高可靠,还要支持分布式事务(Mongodb 4.x)。所以为了尽可能知足不一样场景下的业务需求,MongoDB提供了大量的选项,供用户选择,更加灵活。对于复制集这一块而言,选项包括但不限于:

  • WriteConcern
  • ReadConcern
  • ReadPreference
  • settings.chainingAllowed
  • settings.catchUpTimeoutMillis

因此,做为MongoDB的用户,首先得清楚这些可选项的意义,而后根据本身的业务需求,合理配置。

从这些选项的默认值以及MongoDB的实现,我的以为,在CAP这个问题上,MongoDB应该是更倾向于A(availability,可用性)的。

而诸如链式复制,Leader Priority这些特性,在分布式系统的部署层面来讲都是颇有用的,好比multi datacenter,不少分布式存储系统也支持一样的特性。Raft协议虽说是为工业实现提供了很好的指导,但到具体的应用,仍是得有诸多的调整和完善。

dry-run or pre-vote

MongoDB中的预投票是对raft协议很好的改进,以前,我在看Raft论文的时候也想到了一些corner case,在论文中并无很清楚的阐述,但预投票能很好解决这些问题。

事实上,MongoDB中的预投票(dry-run)并非首创的,在raft协议的超长版解释Consensus: Bridging Theory and Practice中,raft协议的做者就建议实现pre-vote来增长系统的鲁棒性。而在Four modifications for the Raft consensus algorithm(PS,该文的做者就是MongoDB的开发者)详细阐述了Pre-vote的缘由以及实现方法。Pre-vote是为了防止一个隔离的follower不断发起选举 致使term值的激增,以及没必要要的主从切换。

如上图因此,系统由s1 s2 s3三个节点组成,其中s1是leader,另外两个节点是follower。

pre-vote考虑的是这样一种状况,(s2)与(s1 s3)之间出现了网络分割(network partition),那么按照raft算法,s2会不断的尝试发起选举,意味着不断的增长term。那么当网络自愈以后,s2将消息发送到s1 s3. 按照raft论文figure2 Rules for servers

If RPC request or response contains term T > currentTerm: set currentTerm = T, convert to follower

所以s1 会切换到follower, s1 s3 term修改成57,但s2的log 大几率是过旧的(out of date),所以s2没法得到选举,s1 s3会在election timeout后发起选举,其中一个成为term 58的leader。

pre-vote 避免了term inflation,但更重要的是,避免了一次没有必要的从新选举: s1必定会切换到follower,而后s1或者s3再次发起选举,在这个过程当中,因为没有leader,整个系统实际上是不可用的(至少不可写)。

references

一文搞懂raft算法
replication
MongoDB and Raft
MongoDB复制集技术内幕:工做原理及新版本改进方向
MongoDB 高可用复制集内部机制:Raft 协议
Consensus: Bridging Theory and Practice
Four modifications for the Raft consensus algorithm