标题:Quorum Queues Internals - A Deep Dive
原文:https://www.cloudamqp.com/blog/quorum-queues-internals-a-deep-dive.html
时间:2019-04-03
在本文中,我们将更详细地了解Raft分布式共识算法,该算法应用于即将发布的RabbitMQ 3.8版本的仲裁队列功能。
仲裁队列提供了一种新的高可用性,它解决了现有镜像队列功能的一些不足。它还引入了一些新问题,正如我们在这篇文章中描述的。
每个仲裁队列都是一个复制队列;它有一个领导者和多个跟随者。这些领导者和追随者的常用术语是复制。复制因子为5的仲裁队列将由五个副本组成:一个领导者和四个追随者。每个副本将托管在不同的节点(broker)上。为了实现高可用性和耐用性,我们需要多个副本。根据仲裁队列的复制系数,我们可能会丢失一个或多个代理,而不会丢失消息并继续保持队列的可用性。
Introduction to Raft
Raft是一种分布式共识算法,这意味着它是一种分布式算法,其中多个节点同意某个状态。在Raft中,状态是命令的有序序列。这些命令在RabbitMQ中表示一个队列上与客户端交互的处理序列,例如写、读、确认等。
Raft中的节点或服务器,在仲裁队列中由队列副本表示。每个仲裁队列都是一个单独的Raft集群,因此如果我们有100个仲裁队列,那么就有100个Raft集群。运行数百或数千个Raft集群会造成communication flood,这将是资源密集型的,因此有一种称为Multi-Raft的Raft变体,单独Raft集群的通信会被组合并合并到更少的调用中。出于这个原因,RabbitMQ团队决定采用Multi-Raft。
即使使用了Multi-Raft,标准Raft协议的逻辑设计仍然是正确的,这就是我们接下来要讨论的内容。
有了Raft,所有的读写都要经过一个领导者,而领导者的工作就是将写的内容复制给追随者。当客户端尝试读/写跟随者时,它会被告知领导者是谁,并被告知将所有写操作发送到该节点。只有在仲裁节点确认已将数据写入磁盘后,leader才会确认对客户端的写入。仲裁只是大多数节点。一个由三个节点组成的集群的仲裁人数为两个,一个由五个节点组成的集群的仲裁人数为三个。每个节点将所有命令存储在日志结构中,其中每个命令在该日志中都有一个索引。共识算法的任务是确保所有节点在其日志中的同一索引中具有相同的命令。
Raft由三部分组成:
- leader election
- log replication
- cluster membership changes
Leader Election
领导人选举是对领导人达成一致意见的过程。Raft不允许同时有两个领导。
每个节点将处于以下三种状态之一:
- Follower:在这种状态下,节点不发出请求,而是被动地等待来自领导者或候选人的请求;
- Candidate:用于领导者选举,当跟随者检测到失去领导者时,他们会转换到候选状态,并开始向所有其他节点发送
RequestVote
请求; - Leader:负责与客户端交互并将日志复制到跟随者。
当一个节点启动时,它处于Follower状态,并等待来自领导者的心跳。如果等待超时,它将转换为Candidate状态,并向集群的所有成员发送RequestVote
消息。如果其他节点也在等待领导者心跳时超时,那么它们也可能同时发送RequestVote
请求。如果候选人没有获得多数选票,则节点将转换回Follower状态。只有当节点获得多数票时,它才会成为领导者。发送RequestVote
请求的第一个节点通常将成为领导者。
一旦节点成为领导者,它就会向所有跟随者发送周期性心跳信号。如果领导者失败或被关闭,那么追随者将等待心跳超时(Election timeout period),他们将再次开始发送RequestVote
请求,其中一个将成为新的领导者。
Term(也称为epoch或fencing token)的是一个单调计数器(逻辑时钟),用于检测过期节点,这些节点可能已断开连接或停机一段时间,并且具有过时的信息。每次跟随者转换为候选人时,它都会增加其term,并将该term包含在其RequestVote
消息中。一旦一位领导人当选,term直到下次选举才改变。当前term作为安全机制包含在所有通信消息中。
要赢得选举,有三条规则:
- 大多数节点应投赞成票;
- 当候选人发送
RequestVote
消息时,它会包含它认为是当前term+1的内容,接收节点检查term是否大于其自身维护的term,这可以防止过时节点(之前可能已断开连接或停机一段时间)成为领导者; - 候选人在
RequestVote
消息中包含其日志中的最后一个日志条目索引。如果索引落后于某个节点自己提交的最后一个日志项索引,则节点将拒绝该消息。这可以确保落后的节点不能成为领导者,因为这会导致数据丢失。
Log Replication
日志复制由领导者向其追随者发送AppendEntries
消息。跟随者可以向领导者指示他们最后提交的索引是什么,领导者可以从该点发出带有下一个命令的AppendEntries
消息。这意味着领导者可以将日志条目复制到一个拥有较新数据的跟随者,也可以复制到另一个刚刚加入且根本没有数据的跟随者。AppendEntries
消息充当领导者心跳信号,即使没有新数据,也会定期发送。
Network Partitions
当发生网络分区时,镜像队列有可能进入脑裂模式(除非使用暂停少数派分区的策略)。在脑裂模式中,位于分区另一侧的节点不再看到领导者,会选举新的领导者。现在集群有两个负责读写的领导者,存在两个领导者接受读写请求可以继续维持可用性,代价是队列可能会丢失消息。
请注意,RabbitMQ中的“读取”实际上也是“写入”,因为所有读取都是破坏性的。它们隐式地包含一个删除消息的命令。
Raft可防止脑裂,原因如下:
- 领导人选举的法定投票要求
- 所有写操作的仲裁确认要求
如果一个leader位于分区的少数侧,那么它无法确认任何新的写入,因为它无法将这些写入复制到多数侧。多数派的节点可以选举新的领导人,服务可以继续。此时将有两位领导人,但只有多数派的领导人才能发挥作用。网络分区恢复后,原始leader将发送一条AppendEntries
消息,该消息的term现在低于其他节点的term,它将立即成为跟随节点。
如果一个领导者位于多数派分区,那么它可以继续接受写操作。少数派的追随者将停止接收领导者心跳信号AppendEntries
,并将成为候选人。但这些候选人无法赢得领导人选举,因为他们无法获得多数票。他们将继续发送RequestVote
消息,直到网络分区结束,此时他们将看到已经有一个领导者,并且他们将再次恢复为追随者。
Raft协议还有很多内容,还有许多细微差别我们在这里没有介绍,因此如果你想全面了解Raft,请查看Raft网站和论文。
如前所述,由于需要大量的broker间通信,RabbitMQ没有完全按照Raft所描述的那样去实现。RabbitMQ团队已经实现了一个基于Multi-Raft的变体,其中多个Raft集群的通信被捆绑到单个共享通信协议中。但原始Raft协议的重要逻辑属性仍然成立。
Consistency and Availability
仲裁队列仅在大多数(仲裁)副本可用时正常工作。在三代理群集上,复制因子为3的仲裁队列只能承受单个代理的失效。如果两个代理宕机,则队列对客户端不可用。同样,如果网络分区将副本分成2:1,那么只有多数派分区可以继续工作。
同样,只有当仲裁保持可用时,才能保证消息的持久性(一致性)。如果所有副本队列中的数据永久丢失,则仲裁队列将无法恢复。
Summary
仲裁队列将是3.8版本的一部分特性。仲裁队列并不适用于所有场景,因此请仔细查看它们的功能和限制。您可以在此处阅读有关仲裁队列的更多信息:
- https://www.cloudamqp.com/blog/rabbitmq-quorum-queues.html
- https://www.rabbitmq.com/quorum-queues.html