第5章 分布式共识算法 #
在前面的章节中,我们讨论了如何通过复制来防止单点故障导致的某些数据丢失,如何通过分区来承载海量数据,如何通过分布式事务来确保跨节点操作的原子性。
然而,当我们试图将这些机制组合成一个真正能够全天候运行、自动容错的生产级系统时,会发现一个明显的缺口:如何在部分节点故障的情况下,依然让存活的节点对系统的状态达成一致?
传统的两阶段提交协议(2PC)虽然解决了"所有节点要么全部提交,要么全部回滚"的一致性问题,但它是阻塞式的——一旦协调者发生故障,整个集群可能陷入无限等待。这揭示了分布式系统中一个更深层次的挑战:我们需要一种机制,它不仅能达成一致,还必须具备容错性。换言之,即使集群中的少数节点(如半数以下)发生了崩溃或网络隔离,系统依然能够继续推进,对外提供服务。
这便是**共识算法(Consensus Algorithms)**的舞台。
从本质上讲,共识算法解决的是分布式计算中著名的"协商"问题:在一个不可靠的网络环境中,由一组并发的进程提议某个值,并最终就该值达成唯一的、不可逆的协定。
这一看似简单的目标,实则是分布式系统中最坚硬的内核。共识算法不仅是实现状态机复制(State Machine Replication)的理论基础,更是现代分布式数据库实现自动 Leader 选举(Leader Election)、原子广播(Atomic Broadcast)以及配置变更的核心动力。无论是 Paxos 的精妙理论,还是 Raft 的工程实践,它们都旨在将不确定的物理世界(丢包、延迟、宕机)抽象为一个逻辑上严格有序的整体。
在本章中,我们将深入探讨共识算法的原理与演进,理解它们是如何在"一致性"与"可用性"之间找到那条完美的平衡线。
5.1 共识算法简介 #
5.1.1 概述 #
在分布式系统中,**共识(Consensus)**问题占据着核心地位。在此之前,我们讨论了通过复制来保证数据的持久性,通过分区来扩展系统的容量。然而,当这些机制在面临部分节点故障或网络分区时,如何让整个系统依然能够像一个单一的、连贯的整体一样行动,便是共识算法所要解决的根本问题。
直观地说,共识就是指多个参与者(进程或节点)对某个特定的值(Value)达成一致看法的过程。这个"值"在不同的场景下可以代表不同的含义:它可能是一个布尔值(决定事务提交还是回滚),选出的主节点 ID(Leader Election),或者是一条具体的日志条目(Log Entry)。这个定义看似抽象,但其实涵盖了大量实际应用场景:
- 在主从复制中,客户端需要通过主节点才能写入数据,因此节点之间需要选举出一个领导者来协调任务分发、日志复制等工作。如果没有一个可靠的共识机制,可能会出现多个节点同时认为自己是领导者,导致**脑裂(split-brain)**现象,导致数据冲突。
- 在分布式数据库、分布式存储等系统中,数据通常会被复制到多个节点上以提高可用性和容错能力。然而,如何保证所有副本在写入时保持一致?这就需要共识算法来协调写入顺序和结果。
- 考虑银行转账问题。用户A要转账给用户B,这里涉及到两个操作:从用户A的账号上扣除款项,向用户B的账号增加款项。这两个操作必须合在一起成为一个"原子操作":要么都成功,要么都失败。表现在具体的操作上,系统的所有节点需要对这个原子操作形成一致:所有的节点都提交成功,或者都终止操作回滚数据,不能出现在某些节点上提交成功,而在另外的节点上中断的情况。
- 集群的配置发生变更,例如集群增加或者删除了节点时,这些配置信息同样需要在集群的节点中达成共识。
- 在 2.5 节中我们提到,系统的状态由事件按照顺序组成,所以如果一组事件如果能对一个分布式系统中的所有节点保持一样的顺序,那么这些事件在所有节点中就会得到同样的状态,而这里的"事件顺序"也是一种共识。
- 系统中的多个节点需要访问临界区资源时,需要决定访问的顺序。
以上的问题虽然各不相同,在不同的问题场景中需要达成的一致内容各不相同:可能是消息传递的顺序、一组进程是存活还是宕机、哪个节点是主节点、谁能访问共享资源、事务应该终止还是结束等等,不管这些细节如何。但是它们都有一个共同的特性:系统中的节点,需要就某些共享信息达成一致。
5.1.2 共识算法的难点 #
如果网络是完美可靠的,或者节点之间不存在延迟,达成共识将变得轻而易举。共识算法之所以复杂且困难,根源在于我们所处的分布式环境通常是异步(Asynchronous)且不可靠的。
在此环境下,共识算法面临以下核心挑战:
- 节点故障:节点可能因为硬件故障、软件错误或断电而停止工作。例如,一个数据库节点宕机,可能导致数据写入失败或不一致。共识算法需要确保即使部分节点宕机,系统仍能达成一致。
- 网络问题:分布式系统中的节点通过网络通信,可能面临延迟、分区(网络断开)或消息丢失。例如,两个数据中心之间的网络中断可能导致节点无法同步状态。共识算法需要通过超时、重试或投票机制来处理这些问题。
- 拜占庭故障:某些节点可能表现出恶意行为,比如发送错误的消息、伪造数据或故意不响应,这种情况在区块链等开放系统中尤其常见。共识算法需要设计机制来识别和隔离恶意节点。
- 数据一致性:在分布式数据库或文件系统中,多个副本需要保持一致。例如,用户在不同节点上更新数据时,系统必须确保所有副本最终反映相同的状态。共识算法通过定义如何选择"正确"的值来解决这一问题。
想象一群探险者在迷雾森林中寻找宝藏,每个人都有一份不完整的地图(分布式系统中的节点状态)。他们需要通过喊话(网络通信)来决定集合地点,但有人可能迷路(节点故障),有人可能故意指错方向(拜占庭故障),还有人可能听不清喊话(网络延迟)。共识算法就像一个魔法协议,确保所有诚实的探险者最终聚集在同一个地点,即使面对这些挑战。
5.1.3 共识和一致性 #
受翻译的影响,很多讨论共识算法的中文资料都称它们是"分布式一致性算法",虽然"一致性"(Consistency)和"共识"(Consensus)这两个词非常相似,但它们在分布式系统中有着明确的区别和紧密的联系。在本节介绍两者的区别和联系,为了规范和严谨,本书中严格区分使用"共识"和"一致性"。
一致性
一致性是指多个副本之间的数据是否一致,或者客户端访问系统时是否能看到一致的数据视图。一致性更多是从外部视角来看待系统的状态,强调的是读写操作的结果是否符合预期的行为模型。在 3.2 节中详细讨论了常见的一致性模型。
共识
共识则是一种协议或算法机制,用于在多个节点之间就某个值达成一致。它是实现一致性的一种手段,尤其是在多副本系统中,共识是实现强一致性的重要工具。
共识协议通常满足以下几个性质:
- 终止性(Termination):所有非故障的节点最终都会做出决定。
- 一致性(Agreement):所有非故障的节点对同一个值达成一致。
- 有效性(Validity):共识值必须是某个节点提出的合法值。
共识更偏重于内部逻辑的协调机制,是系统为了达到某种一致状态而采取的具体技术路径。