Consual 一致性协议
Consul使用一致性协议来提供一致性(如CAP定义的)。一致性协议基于“Raft: In search of an Understandable Consensus Algorithm”。Raft的直观展示,见The Secret Lives of Data。
提示:本页覆盖了Consul内部的所有技术细节。有效的操作和使用Consul并不需要这些细节。这些细节是为了那些不想查阅源代码但又希望学习的人准备的。
Raft协议概述
Raft是一个基于Paxos的一致性算法。与Paxos相比,Raft被设计得有更少的状态,更简单和更好理解。
当讨论Raft时,有一些关键的术语需要知道:
- Log——Raft系统的基本工作单元是日志记录。一致性的问题可以被分解到复制日志中。日志是一个有序的记录序列。如果所有成员同意记录和他们的顺序,我们认为日志是一致的。
- FSM——有限状态机。有限状态机是互相转移的有限的状态的集合。当有新的日志,FSM允许在状态之间转换。相同日志序列的应用必须产生相同的状态,这意味着行为必须是确定性的。
- Peer set——所有参与日志复制的成员组成了Peer set。对于Consul而言,所有的server节点都在本地数据中心的Peer set中。
- Quorum(法定人数)——法定人数是Peer set中的大多数成员。对于一个大小为n的集合,法定人数必须至少是(n/2)+1个成员。例如,如果在Peer set中有5个成员,我们需要3个节点来组成一个法定人数。如果由于任何原因导致法定人数的节点不可用,则集群是不可用的,并且不会有新的日志提交。
- Committed Entry——当一条记录被持久化的存储在法定人数的节点中,该记录被认为是已提交的。一旦一条记录被提交他就可以被应用。
- Leader——在任何给定时间,Peer set选举一个节点成为leader。leader负责处理新提交的记录,复制到follow上和管理它们。
Raft是一个复杂的协议,不会在这里详细介绍(对于那些希望更全面的了解的,完整的规范在这篇文章中)。然而,我们试图提供一个更高层次的描述,这有助于建立一个心理模型。
Raft节点总是处于三个状态中的一个:follower,candidate和leader。所有的节点最初都是处于follower。在这个状态节点可以接受来自leader的日志和进行投票。如果一段时间没有收到任何记录,节点会自我升级为candidate。在candidate状态,节点从其它节点处获取选票,如果candidate获取法定的选票数,则它晋升为leader。leader必须接收新的记录并且复制到其它所有的follower上。另外,如果过时的查询时不可接受的,所有的查询也必须在leader上执行。
一旦一个集群有一个leader,它可以接受新的日志记录。client可以请求leader追加新的日志(从Raft的角度看,日志是一个不透明的二进制blob)。然后leader将记录写入持久化的介质中并且试图复制到法定人数的follower。一旦日志记录被认为是提交的,它能被应用到有限状态机上。有限状态机是应用指定的。在Consul中,我们使用BoltDB来维护集群状态。
显然,允许复制的日志以无限的方式增长这显然是不可取的。Raft提供一种快照当前状态和压缩日志的机制。因为FSM是抽象的,重播旧的日志来恢复FSM的状态必然导致相同的状态。这使得Raft可以在某个时间点计算FSM的状态,然后删除所有达到该状态的日志。这是自动执行的,无需用户干预并且防止无限制的使用磁盘,同时也减少了重放日志的时间。使用BoltDB的一个优点是,它允许Consul继续接收新的事务即使旧状态正在被快照,不会产生任何的问题。
只要有法定人数的节点可用,Consensus是容错的。如果法定人数的节点不可用,那么不可能处理日志或者成员关系。例如,假设这里有两个节点:A和B。法定人数也是2,意味着两个节点都必须同意提交日志记录。如果A或者B失败了,则不可能达到法定人数。这意味着集群无法添加或者删除或者提交任何日志记录。这个结果是不可用的,在这个时候需要手工干预来删除A或者B,然后以引导模式重启。
3个节点Raft集群可以容忍一个节点的故障,5个节点的集群可以容忍两个节点故障。推荐的配置是每个数据中心运行3或5个Consul Server。这可以最大限度的提高可用性,而不会有太大的性能牺牲。下面的部署表总结了潜在的集群大小和每个集群的故障容忍。
在性能方面,Raft比得上Paxos。假设都存在稳定的leader,提交一个日志记录需要往返集群半数节点。因此性能受限于磁盘IO和网络延迟。尽管Consul不是设计为高吞吐的写系统,但是它能否处理每秒成百上千的事务依赖于网络和硬件配置。
Raft in Consul
只有Consul server节点加入Raft。所有的Client节点转发请求到Server。这样设计的一部分原因是,随着更多的节点加入集群,法定人数的节点也会增加。这会引入性能问题,因为你可能需要等待数百机器而不是少数的来同意一条记录。
当开始时,一个Consul Server被设置成“bootstrap”模式。这个模式允许它选举自己为leader。一旦一个leader被选举,其它机器能够保持一致性和安全性的加入peer set。最终,一旦第一部分机器已经加入,bootstrap模式可以被禁用。更多细节看这个文档。
由于所有的server都加入作为peer set的一部分,它们都知道当前的leader。当一个RPC请求到达一个非leader的server时,请求被转发到leader。如果一个RPC是一个查询类型,意味着它是只读,leader生成基于FSM当前状态的结果。如果RPC是一个事务类型,意味着它修改状态,leader生成一个新的日志记录并且将其用于Raft。一旦一个日志记录被提交且应用于FSM,则事务完成。
由于Raft的复制性质,性能对网络延迟是敏感的。由于这个原因,每个数据中心选择一个独立的leader并维护一个不相交的peer set。数据按照数据中心分区,所以每个leader只负责它数据中心的数据。当一个请求被一个远程数据中心接到,请求被转发到正确的leader。这种设计在不牺牲一致性的情况下有较低的事务延迟和更高的可用性。
Consistency Modes
虽然所有的写入都通过Raft的日志复制,读取更灵活。为了支持可能需要的各种权衡,Consul支持3中不同的读一致性模式。
三种模式分别是:
- default——Raft使用leader租赁过程,该过程提供了一个时间窗口,在该窗口内leader保持稳定的角色。然而,如果一个leader与其他节点分区,当旧的leader仍然持有租赁时一个新的leader会被选举出来。这意味着有两个leader节点。这里没有脑裂的风险,因为旧的leader不能提交新日志。然而,如果旧的leader任然提供任何读取服务,读取的值可能是旧的。默认的一致性模式只依赖于leader的租赁期,可能暴露给client旧值。做这样的权衡是因为读取时快速的,一般是强一致的,并且只有旧值是很难触发的。读取旧值的时间窗口也是有界的,因为leader会由于分区而下台。
- consistent——这种模式是强一致的,没有任何隐忧。它要求leader验证法定人数的节点,证明它任然是leader。这会引入一个额外的往返所有节点的过程。这种权衡总是一致性的读取,但是会增加延迟因为有额外的往返时间。
- stale——这种模式允许在任何服务器上读取,不管它是不是leader。这意味着可能读取到任意的陈旧值,但是通常与leader差距50毫秒。这种权衡是快速和可扩展的读取,但是可能有陈旧值。这种不用leader模式的读取意味着一个不可用的集群也能够响应。
Deployment Table
下表展示了不同集群大小的法定人数和故障容忍。推荐的部署是3或5个server。单台服务是非常不可取的,因为在故障的情况下数据丢失是不可避免的。