一致性协议


Consul使用一个一致性协议来提供CAP中定义的一致性。这个一致性协议基于“Raft:一个可理解的一致性算法探寻”。对于Raft的可视化解释,可以看数据的秘密生活

高级主题!本文将介绍Consul内部的技术细节,对于实际操作和使用Consul,你不需要知道这些细节。这些细节记录在这里为那些希望了解它们的人,小伙伴们不必再去探索源代码。

Raft协议概览

Raft是一个基于Paxos的一致性算法。相比Paxos,Raft被设计为只有较少状态,以及一个更简单、更容易理解的算法。

当讨论Raft时有一些关键名词需要了解:

日志(Log) - Raft系统中关键的工作单元是日志记录,一致性的问题可以被分解为日志复制。日志是一种有序的记录,如果所有的成员在记录内容及其顺序上达成一致,我们认为日志是一致的。

FSM - 有限状态机。一个FSM是一个相互转换的有限个状态的集合。如果新的日志被应用,FSM被允许在状态间进行转换。相同日志序列的应用必产生相同的状态,这意味着行为必须是确定性的。

节点集 - 节点集是参与日志复制的所有成员的集合。对于Consul来说,所有的服务器节点都在本地数据中心的节点集中。

法定数目 - 法定数目是一个节点集中的大多数成员:对于一个大小为n的集合,法定数目需要至少(n/2)+1个成员。例如,如果在节点集中有5个成员,我们需要3个节点来构成一个法定数目。如果某种原因导致法定数目的节点不可用,集群将不可用,没有新的日志会被提交。

提交记录 - 当一条记录被法定数目的节点持久写入后,它被认为已提交。一旦一条记录被提交,它就可以被应用了。

Leader - 在某个给定的时间,节点集选举某个节点作为leader。leader负责接收新的日志记录,复制到跟随者,管理记录的提交。

Raft是一个复杂的协议,这里将不做详细介绍(对于那些渴望更全面了解的人,点击这里查看完整的协议)。然而,我们将提供一个高级的描述,对于构建一个心理认知模型可能是有用的。

Raft节点始终处于三个状态中的某一个:跟随者、候选者、领导者(leader)。所有的节点启动时是一个跟随者。在这个状态,节点可以从一个领导者接收日志记录,以及投票。如果一定时间内没有收到记录,节点自动升级到候选者状态。在候选者状态,节点请求来自对等节点的投票。如果一个候选者接收到法定数目的投票,它会被提升为领导者。领导者将接收新的日志记录,并且复制到所有的跟随者。此外,如果不允许陈旧读取,所有的查询必须在领导者执行。

一旦集群中选举出了leader,它就可以接受新的日志记录了。客户端可以请求leader追加一个新的日志记录(从Raft的角度来看,一条日志记录是一个不透明的二进制对象),然后leader将这些记录写入持久存储,并尝试复制到法定数目的跟随者。一旦日志记录被认为已经提交,它就可以被应用到一个有限状态机。有限状态机是专用的,在Consul中,我们使用BoltDB来维护集群状态。

很明显,允许复制日志无限增长是不合适的。Raft提供了一种机制:快照当前状态,并且压缩日志。考虑到有限状态机的概念,恢复的有限状态机的状态必须和旧日志重放保持相同的状态。这些自动执行,无需用户干预,并且可以防止磁盘无限使用,同时也最大限度减少日志重放花费的时间。使用BoltDB的一个优点是:它允许Consul继续接受新的事务,甚至当旧状态正在被快照时,防止某些可用性问题。

一致性容错的关键点是法定数目的节点可用。如果法定数目的节点不可用,则不可能处理日志记录或成员资格。例如,假定只有2个节点:A和B,法定数目也是2,意味着所有的节点必须达成提交同一条日志记录。如果A或B中任一个失败,则不可能达到法定数目,这意味着集群不能添加或移除一个节点或者提交任何日志记录。这个结果就是不可用。这种情况下,将需要手动干预,移除A或B,然后使用bootstrap模式重启剩下的节点。

在一个具有3个节点的Raft集群中可以容许一个节点出现故障,在具有5个节点的Raft集群中容许2个节点故障。推荐的配置是每个数据中心运行3或者5个Consul Server。这可以最大限度地提高可用性而不会太多的牺牲性能。下边的部署表总结了潜在的集群大小及对应的故障容错性。

在性能方面,Raft与Paxos差不多。假设leader是稳定的,提交一条日志记录需要集群中半数节点的往返处理。因此,性能主要受到磁盘I/O和网络延迟的约束。虽然Consul不是被设计为一个高吞吐量的写系统,它依赖的网络和硬件配置也应该能够处理每秒成百上千的事务命令。

Consul中的Raft

只有Consul Server节点参与Raft,并作为对等节点集的部分。所有的Client节点转发请求到Server节点。这样设计的部分原因是,随着更多的成员加入对等节点集,法定数目的大小也会随之增长。这会产生性能问题,因为相比少数机器,你可能需要等待上百个机器对日志记录达成一致。

在入门指南开始,一个单独的Consul Server被设置为“bootstrap”模式。这个模式允许它选择自己作为leader。一旦leader被选举出来,其它的Server可以在保持一致性和安全性的方式下添加到这个对等节点集中。最终,只要第一批Server节点被加入,bootstrap模式就可以被禁用了。看这里了解详细。

因为所有的Server都参与并作为对等节点集的部分,他们都知道当前的leader。当一个RPC请求到达某个非leader Server,这个请求会被转发到leader。如果这个RPC是一个查询,意味着它是只读的,leader基于有限状态机的当前状态产生这个结果。如果这个RPC是个事务类型,意味着它会修改状态,leader将产生一个新的日志记录,并使用Raft应用它。一旦日志记录被提交,且应用到有限状态机,则事务完成。

因为Raft复制的天性,性能对网络延迟比较敏感。基于这个原因,每个数据中心选举出一个独立的leader,并管理不相交的对等节点集。数据被数据中心分区隔离,所以每个leader只负责其所在数据中心的数据。当收到一个对远程数据中心的请求,这个请求将被转发到正确的leader。这个设计提供低延迟的事务处理和高可用性,而不用牺牲一致性。

一致性模式

虽然所有的写都需要通过Raft复制日志,但是读却更灵活。为了支持开发者可能需要的各种权衡,Consul对读提供3种不同的一致性模式。

这3种模式如下:

默认 - Raft使用leader租赁,在一个时间窗口内,leader假设的它的角色是稳定的。然而,如果leader被从剩余的节点隔离,一个新的leader将会被选举出来,同时旧的leader仍旧保持租约。这意味着有两个leader节点。因为旧的leader不能提交新的日志,这里没有裂脑的风险。然而,如果旧的leader提供任何读的服务,其结果可能是陈旧的。默认的一致性仅依赖与leader租赁,暴露给Client的可能是陈旧的值。我们做这种权衡,是因为读是快速的,通常强一致型的,并且只在一个很难触发的情形下是陈旧的。陈旧读的时间窗口也是有限的,因为leader由于隔离将会下台。

强一致 - 这种模式是没有附加条件的强一致性。它需要leader验证法定数目的对等节点来确定它仍旧是leader。这将附加产生到所有Server节点的往返处理。这种权衡要求总是一致性的读,但是由于额外的往返增加了延迟。

允许陈旧 - 这种模式允许任何一个Server提供读服务,无论它是不是leader。这意味着读可能是任意的陈旧,但都是leader 50毫秒内产生的。这种权衡考虑非常快速且具备良好伸缩性的读,但允许陈旧的值。这种模式允许在没有leader的情况下读,意味着集群不可用的时候,仍旧可以响应。

关于使用这几种模式的更多文档,请看HTTP API

部署表

下面这张表展示了各种集群大小情况下法定数目和故障容错情况。推荐的部署是3或者5个Server。单个Server的部署是非常不推荐的,因为在故障场景下将不可避免的丢失数据。

Servers Quorum Size Failure Tolerance
1 1 0
2 2 0
3 2 1
4 3 1
5 3 2
6 4 2
7 4 3