在当今信息时代,分布式系统已经成为了各行各业的核心基础设施。分布式系统的一个重要问题是如何在多个节点之间达成一致的共识,即使存在故障或恶意行为的情况下也能够保持系统的安全性和正确性。为了解决这个问题,分布式系统共识算法应运而生。本文将带您深入了解分布式系统共识算法的基本概念、原理和一些常见的算法实现。
2 分布式系统面临的挑战
2.1 一致性与故障容忍性
在分布式系统中,节点之间的通信可能会遇到延迟、丢包或者节点故障等问题。这些问题会导致节点之间的状态不一致,因此确保系统的一致性成为了一个挑战。同时,分布式系统需要具备故障容忍性,即使有节点发生故障,系统仍然能够正常运行。
2.2 去中心化和安全性
分布式系统通常是去中心化的,没有一个单一的控制节点。这意味着系统中的节点应该能够相互协作,以达成共识。然而,节点可能存在恶意行为,试图破坏系统的安全性。因此,分布式系统共识算法需要具备安全性,能够抵御各种攻击和欺骗行为。
3 分布式系统共识算法的理论知识
3.1 CAP理论
CAP理论是分布式系统理论中的一个基本原则,它由计算机科学家Eric Brewer在2000年提出。CAP是三个关键属性的首字母缩写,代表了在分布式系统中三个重要的目标之间的冲突。
CAP理论指出,在一个分布式系统中,不能同时满足这三个属性。只能在Consistency、Availability、Partition Tolerance中选择两个而舍弃另外一个。后文列举的共识算法均在某些前提下,实现了其中的两个。3.2 拜占庭将军问题
拜占庭将军问题(Byzantine Generals Problem)是一个经典的分布式计算问题,涉及到多个参与者之间的可靠信息传递和共识达成。
问题的背景设想是,在拜占庭帝国的军队中,多个将军(Generals)带领着各自的部队,必须就是否进攻敌军达成共识。将军们之间通过传递消息来达成共识,但是有些将军可能是叛徒,会故意发送错误的消息以破坏共识。拜占庭将军问题的关键在于如何在存在叛徒的情况下,让忠诚的将军们能够就是否进攻敌军达成一致的共识,即使有些将军发送了错误的消息。
解决这个问题的一种方法是使用拜占庭容错算法,例如拜占庭共识算法(Byzantine Fault Tolerance)。该算法基于多数投票的原则,在所有将军中,至少有三分之二的将军是忠诚的情况下,可以达成共识。将军们通过相互传递消息,并根据多数投票的结果来判断最终的共识。然而,如果将军人数较少,存在叛徒的数量较多时,拜占庭将军问题可能无法得到解决,因为叛徒们可以通过协调行动来干扰共识的达成。拜占庭将军问题在分布式系统和计算机网络领域有广泛的应用,例如在区块链技术中,不可篡改性是其中最大的特点,共识算法的设计和拜占庭容错性能的评估都与拜占庭将军问题密切相关。
3.3 一致性模型
一致性本身根据使用场景的不同,存在许多不同的含义,在数据库和系统层面,则拥有不同的模型。分布式系统共识算法通常基于一致性模型,即所有正确的节点最终达成一致的状态。一致性模型可以分为强一致性和弱一致性两种。
3.3.1 强一致性模型
强一致性模型的目标是保证系统中的所有节点都能看到一致的数据状态,即使在节点故障或网络分区等异常情况下也能保持数据的一致性。为了实现强一致性,系统需要在数据写入和读取的过程中进行额外的同步和协调操作。根据CAP理论,强一致模型需要牺牲可用性,以达到“使用者”的最佳体验。比如在分布式系统中,修改字典数据(db与缓存2份数据)的场景下,我们需要添加分布式读写锁来保证db与缓存数据的一致性,写读互斥。顺序一致性(Sequential Consistency)
所有的读操作都会返回一个全局的、按照一定顺序的结果。顺序一致性要求保持所有操作的全局顺序,即使在不同节点上并发执行的操作也需要按照某种顺序进行排序。比如zookeeper中的ZAB协议就在读操作上保证了顺序一致性,后文也会详细讲解这个协议。在强度上线性一致性大于顺序一致性,是分布式系统中“使用者”所追求的最强的一致性,所有的读操作都会返回一个在全局时间顺序下的、与实际执行顺序一致的结果。线性一致性要求保持所有操作的全局时间顺序,即使在不同节点上并发执行的操作也需要按照某种时间顺序进行排序。比如etcd的读写操作都通过了Raft算法,实现了线性一致性,任何一次在etcd中的读操作都能读到某个数据的最近一次写的数据,且全局时间顺序下实际操作结果一致。
3.3.2 弱一致性模型
弱一致性模型通常更适用于大规模分布式系统,因为它可以提供更好的性能和可伸缩性。在某些应用场景下,对于数据的实时一致性要求不高,而对于系统的可用性和性能有更高的追求,可以选择采用弱一致性模型。最终一致性(Eventual Consistency)
系统保证在没有新的更新操作后,最终所有节点的数据最终会达到一致的状态。但是在更新操作发生后的一段时间内,节点之间可能存在数据的不一致性。比如在电商下单业务当中,一般需要实现减库存、创建订单和交易单这三个步骤。假设交易单生成失败,就会出现库存扣除了、订单生成了、交易单没生成的情况,此时我们只需保证最终交易单成功生成就行,这就是最终一致性。系统保证具有因果关系的操作在所有节点上是有序的,但是对于并发操作之间的顺序不做保证。因果一致性允许在并发操作之间存在一定的并行性,以提高系统的性能和可扩展性。比如你在论坛系统上发表主题,论坛用户在这个主题下发表了评论,而你对这条评论进行了回复,你的回复得在评论之后,这是一个因果关系,而其他没有因果关系的数据,可以允许不一致。
3.4 共识与一致性的区别
- 一致性:含义比共识宽泛,在不同场景(基于事务的数据库、分布式系统等)下意义不同。在分布式系统场景下,一致性指的是多个副本对外呈现的状态。如之前提到的顺序一致性、线性一致性,描述了多节点对数据状态的共同维护能力,一致性解决的是数据副本之间的同步问题。
- 共识:特指在分布式系统中多个节点之间对某个事情达成一致看法的过程。需注意达成某种共识并不意味着就保障了一致性,共识解决的是节点之间的决策问题。
在分布式系统中,共识算法是实现一致性的关键手段之一。共识算法通过节点之间的通信和协作,确保在多个节点上达成一致的决策或结果,从而保持数据的一致性。
4 常见的分布式系统共识算法
4.1 Paxos
4.1.1 概述
Paxos算法是由分布式系统领域的著名科学家Leslie Lamport于1990年提出的。当时,分布式一致性是一个备受关注的问题,而Lamport正是在解决分布式一致性的挑战上作出了杰出贡献。Lamport提出Paxos算法的背景可以追溯到20世纪80年代。当时,Lamport与其他研究人员一起致力于研究分布式系统中的一致性问题。他们的目标是设计一种能够在存在故障和网络延迟的条件下,仍能保持系统数据一致性的算法。在这个背景下,Lamport开始思考一种解决分布式一致性问题的可行方案。他的目标是找到一种算法,能够使分布式系统中的节点在缺乏全局时钟和可靠通信的情况下,就共识达成一致。Lamport在1990年的论文"The Part-Time Parliament"中首次提出了Paxos算法。这个名称源于希腊文中的一个概念,帕克索斯(Paxos),指的是古希腊某些城邦中的议会,以实现民主决策。通过Paxos算法,Lamport提供了一种解决分布式一致性问题的理论框架。该算法基于一系列阶段和消息交换来达成共识,并通过引入多数派的概念来确保节点之间的一致性。Paxos算法的提出不仅在学术界引起了广泛的关注,而且在实际系统中也得到了广泛应用。它被广泛认为是解决分布式系统一致性问题的重要里程碑,并为后来的一致性算法研究奠定了基础。
4.1.2 基本概念
- 提案(Proposal):提案是对分布式系统中的某个值或操作的建议。在Paxos算法中,每个提案都有一个唯一的编号标识,并包含一个值。提案的编号通常是一个由节点生成的递增序列号,用于区分不同的提案。
接受者(Acceptor):接受者是参与Paxos算法的节点,负责接收和处理提案。每个接受者都可以接受来自提案者的提案,并决定是否接受该提案。
学习者(Learner):学习者是Paxos算法中的观察者,它们可以接收并学习被接受的提案。学习者的角色是为了保证系统中的所有节点都达成一致。
多数派(Majority):多数派是Paxos算法中的重要概念。它指的是在分布式系统中,必须有超过半数的节点同意一个提案,才能使该提案被接受和学习。多数派的存在确保了系统的一致性。
准备阶段(Prepare Phase):准备阶段是Paxos算法的第一阶段,用于提案的准备工作。在准备阶段,提案者向接受者发送准备请求,请求接受者为其生成一个承诺编号。
接受阶段(Accept Phase):接受阶段是Paxos算法的第二阶段,用于接受提案并达成共识。在接受阶段,提案者向接受者发送接受请求,请求接受者接受其提案。
4.1.3 准备阶段(Prepare Phase)
首先在准备阶段开始时,提案者选择一个唯一的提案编号,该编号可以是一个递增的序列号或其他全局唯一的标识符,每个提案者都会选择一个不同的提案编号。然后提案者向所有接受者发送准备请求,请求它们为其生成一个承诺。准备请求包含提案者选择的提案编号,接受者在收到准备请求后,会检查是否已经为其他提案者生成了承诺。如果已经给其他提案者发送了承诺,那么接受者会返回之前发送的承诺的编号和值给提案者。如果接受者还没有给其他提案者发送过承诺,那么它会生成一个承诺编号,该编号要么是之前已经接受的最高提案编号,要么是一个初始值。然后,接受者将承诺编号和之前接受的提案值(如果有)发送给提案者。一旦提案者收到多数派的响应,它会检查收到的承诺响应中是否包含之前接受的提案编号和值。如果有,那么提案者会将具有最高编号的提案作为自己的提案值。否则提案者继续等待承诺响应,直到收到多数派的承诺为止。这样,提案者就获得了足够数量的承诺来提交自己的提案。
4.1.4 接受阶段(Accept Phase)
首先在接受阶段开始时,提案者向多数派的接受者发送接受请求,接受请求包含提案者选择的提案编号和值(可能是之前收到的承诺中的最高提案值或者是自己的原始提案值)。
然后接受者首先检查接受请求中的提案编号,确保它大于等于之前已经接受的最高提案编号,如果提案编号小于之前的提案编号,接受者将拒绝接受该提案。如果提案编号通过了检查,接受者会接受该提案,并将自己的承诺更新为接受的提案编号和值。然后,接受者向提案者发送接受响应,表示已成功接受提案。最后提案者处理接受响应,提案者在收到多数派的接受响应后,提案者会检查接受响应中的提案编号是否与自己发送的提案编号相同。如果不同,说明其他提案者已经接受了更高编号的提案,提案者将放弃自己的提案。如果提案者收到多数派的接受响应,并且这些响应中的提案编号与自己的提案编号相同,那么共识就已经达成。提案者可以认为自己的提案已经被多数派接受,并可以通知学习者该提案的值。
4.1.5 Paxos算法时序图
图1 paxos算法时序图4.2.1 概述
Raft 算法是一种用于分布式系统中的共识算法,旨在确保不同节点之间的数据一致性。不同于Paxos算法直接从分布式一致性问题出发推导出来,Raft算法则是从多副本状态机的角度提出,用于管理多副本状态机的日志复制;同时,Raft算法使用了更强的假设来减少了需要考虑的状态,使之变的易于理解和实现。
Raft 算法由 Diego Ongaro 和 John Ousterhout 在2013年提出,其名称来源于一种水上漂浮工具——"筏"。这是因为该算法的设计灵感是模拟筏子在一个池塘中的协作,从而避免出现问题,例如节点的领导者过于集中或决策过程的复杂性。4.2.2 基本概念
角色划分:Raft将系统中的角色分为领导者(Leader)、跟从者(Follower)和候选人(Candidate)。
- Leader:领导者是 Raft 算法中的核心角色。每个时刻系统中只能有一个 Leader。Leader负责处理客户端的写请求,并将这些请求作为新的日志项添加到自己的日志中。然后,Leader通过发送心跳消息定期向Follower广播这些日志项,从而确保所有节点上的日志保持一致。
- Follower:跟随者是 Raft 算法中的从属角色。它们按照领导者的日志进行操作,并复制Leader的日志,以保持日志的一致性。Follower只能响应客户端的读请求,并将读请求转发给当前的Leader。在选举过程中,Follower参与投票,帮助选举出新的Leader。
- Candidate:Leader选举过程中的临时角色。
系统在任意时刻最多只有一个Leader,正常工作期间只有Leader和Followers。Follower只响应其他服务器的请求。如果Follower超时没有收到Leader的消息,它会成为一个Candidate并且开始一次Leader选举。收到大多数服务器投票的Candidate会成为新的Leader。Leader在宕机之前会一直保持Leader的状态。
' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
图2 raft中三种角色转换图
任期(term):Raft算法将时间分为一个个的任期(term),每一个term的开始都是Leader选举。在成功选举Leader之后,Leader会在整个term内管理整个集群。如果Leader选举失败,该term就会因为没有Leader而结束。
4.2.3 Leader选举
当节点启动时,它会成为一个候选人(Candidate)状态。在这个状态下,节点将尝试成为新的Leader。节点首先会增加自己的当前任期号,并向其他节点发送请求投票的消息。Candidate向其他节点发送请求投票的消息,该消息包含任期号和候选人的标识符。其他节点在收到请求后,会检查自己的状态,并根据一定的条件判断是否投票给该Candidate。节点会对Candidate的请求进行投票。投票的原则是,如果该节点没有给其他Candidate投过票,并且Candidate的任期号大于等于该节点的当前任期号,那么该节点会投票给Candidate。如果Candidate在选举过程中收到了多数节点的选票(包括自己的一票),那么它将成为新的Leader。在一轮选举中,获得多数票是必要的,否则选举将无法完成。在某些情况下,可能会出现多个Candidate同时竞争Leader地位的情况。如果在一轮选举中没有Candidate获得多数选票,那么没有Candidate会成为Leader。此时,节点会增加自己的任期号,并且进入新的选举轮次,重复上述选举过程。一旦有了新的Leader,它会开始发送心跳消息给其他节点,以保持其领导地位。如果其他节点在一定时间内没有收到Leader的心跳消息,它们将会启动新的选举过程,试图选举新的Leader。
4.2.4 日志同步
当客户端向Leader发送一个写操作请求时(例如添加一条记录或执行一项更改),Leader将该请求作为一条新的日志项添加到自己的日志中。这个日志项包含了要执行的操作和相应的任期号。一旦Leader将日志项添加到自己的日志中,它会通过发送心跳消息来通知其他Follower。这些心跳消息也包含Leader的最新日志项。当Follower收到Leader的心跳消息后,它会检查心跳消息中的日志项。如果Follower的日志与Leader的日志一致,说明Follower已经与Leader同步。否则,Follower将尝试追赶Leader的日志。Follower会比较自己的日志和Leader的日志,找到最后一个匹配的日志项。然后,Follower从匹配位置的下一个日志项开始,将Leader的缺失日志项逐一添加到自己的日志中。一旦Follower的日志与Leader的日志一致,它就会将这些新的日志项应用到自己的状态机中,执行其中的操作。通过这样的方式,Follower可以保持与Leader相同的状态。由于所有的节点都按照Leader的日志进行操作,因此系统中的数据保持一致。即使在节点失效或网络分区的情况下,一旦失效的节点恢复或网络问题解决,它会根据Leader的日志来恢复并与其他节点同步。为了保持日志的持续复制,Leader会定期发送心跳消息给Follower,即使没有新的写操作请求。这个心跳机制防止Follower认为Leader失效,从而触发新的选举过程。
图4 Raft日志示例图
在 Raft 算法中,如果 Leader 和 Followers 上的日志不一致,这意味着系统中出现了一些问题或异常情况,可能会导致数据的不一致性。这些问题可能源自以下几个方面:
在日志复制的过程中,Leader 将新的日志项发送给 Followers,但其中一个或多个 Followers 无法成功复制日志。这可能是由于网络故障、节点崩溃或其他通信问题导致的。
如果 Leader 节点崩溃或失去了领导地位,新的 Leader 将被选举出来。新的 Leader 可能拥有与之前的 Leader 不同的日志内容,从而导致 Followers 和新 Leader 上的日志不一致。
在 Leader 和 Followers 上的日志不一致时,可能是由于 Leader 提交的某些日志项未能正确复制到 Followers 上。这种情况下,Followers 上缺少了 Leader 上的部分日志,导致不一致。
Leader选举过程可能出现问题,例如多个Candidate之间的竞争过于激烈或选举过程中发生了网络分区等。这可能导致选出的新 Leader 拥有一个较早的日志状态,与其他节点的日志不一致。
' fill='%23FFFFFF'%3E%3Crect x='249' y='126' width='1' height='1'%3E%3C/rect%3E%3C/g%3E%3C/g%3E%3C/svg%3E)
图5 Leader和Followers上日志不一致示例图
4.2.5 安全性
Raft增加了如下两条限制以保证安全性:
之所以要这样,是因为可能会出现已提交的日志又被覆盖的情况:
图6 raft中已提交的日志被覆盖示例图
4.3 ZAB
4.3.1 概述
ZAB(ZooKeeper Atomic Broadcast)算法是一种用于实现分布式一致性的算法,它被广泛应用于ZooKeeper分布式协调服务。ZooKeeper是一个可靠且高性能的分布式协调系统,用于协调分布式应用程序的数据一致性和管理。
ZAB算法的设计目标是在分布式系统中提供强一致性、高可用性和高性能。它通过原子广播的方式实现数据的一致性,确保所有参与者在同一时间点上看到相同的数据状态。
4.3.2 基本概念
角色划分:Leader(领导者)负责接收客户端写请求并进行数据更新,广播提案并收集Follower节点的确认;Follower(追随者)复制Leader节点广播的提案,并发送确认消息给Leader节点;Observer(观察者)类似于Follower节点,但不参与投票和选举过程。
提案(Proposal):提案由Leader节点生成,并包含客户端的写操作。每个提案都有一个唯一的递增编号(ZXID),用于保证提案的顺序性。
广播提案(Proposal Broadcast):Leader节点将提案广播给所有Follower节点,并等待大多数(quorum)Follower节点的确认。
广播消息(Message Broadcast):Leader节点将已经得到大多数Follower节点确认的提案转化为消息,并广播给所有节点。
4.3.3 ZAB算法协议流程
ZAB(ZooKeeper Atomic Broadcast)算法的协议流程包括几个关键步骤,涵盖了提案广播、消息投票和数据恢复等过程。下面是ZAB算法的典型协议流程:
首先当一个节点启动时,它首先会检查是否存在已知的Leader节点。如果有,则尝试连接该Leader节点进行数据同步;如果没有,则自己发起Leader选举。当选Leader之后节点向其他节点发送提议(proposal)来宣布自己的Leader候选人身份。其他节点收到提议后,会投票给候选人并广播选票。候选人收到大多数选票后,成为新的Leader,并向其他节点发送宣布结果的消息。Leader节点接收到客户端的写请求后,生成一个提案,通过FIFO队列,将其广播给所有Follower节点。Follower节点接收到提案后,将其记录在本地,并返回确认消息给Leader节点。当Leader节点收到来自大多数Follower节点的确认消息后,进入消息投票阶段。Leader节点将已经获得大多数确认的提案转化为消息,并将其广播给所有节点。每个节点按照相同的顺序执行接收到的消息,确保数据的一致性。当一个新节点加入ZooKeeper集群时,它首先请求Leader节点发送最新的数据快照(snapshot)和已经确认的提案消息。Leader节点将历史数据快照和消息发送给新节点,使其能够追赶上当前的数据状态。
当Leader节点发生崩溃或无法与其他节点通信时,崩溃恢复机制会被触发。新的Leader节点将通过Leader选举流程选出,并根据已经确认的提案消息来恢复数据一致性。
图7 提案广播和消息投票阶段流程图
通过以上协议流程,ZAB算法实现了数据的一致性和高可用性。Leader节点负责广播提案和消息,Follower节点复制并确认提案,所有节点按照相同的顺序执行消息,从而保持数据的一致性。同时,算法具备Leader选举和崩溃恢复机制,以保证系统的可用性和稳定性。
4.4 工业应用
4.4.1 Paxos 算法的产品应用举例
Google Chubby:Chubby是Google的一个分布式锁管理服务,用于在Google的分布式系统中实现分布式锁。它使用了Paxos算法来保证锁服务的一致性和可靠性。
Amazon DynamoDB:DynamoDB是Amazon的一种全托管的分布式数据库服务,用于处理大规模的数据集。它使用了Paxos算法来实现数据的副本一致性和故障恢复。
Google Spanner:Google Spanner是一个全球分布式的关系型数据库服务,它使用了TrueTime API和Paxos算法来实现全局一致性和可伸缩性。
Microsoft Azure Cosmos DB:Cosmos DB是Microsoft Azure的全球分布式数据库服务,它提供了多个API支持,包括SQL、MongoDB、Cassandra等。Cosmos DB内部使用了Paxos算法来实现数据的分布式复制和一致性。
4.4.2 Raft 算法的产品应用举例
etcd:etcd是一个高可用的分布式键值存储系统,被广泛应用于Kubernetes等容器编排平台中。etcd使用Raft算法来保证数据的一致性和高可用性。
Consul:Consul是一个开源的服务网格和服务发现工具,用于帮助管理大规模的微服务架构。Consul内部使用了Raft算法来实现服务注册和健康检查等功能。
TiDB:TiDB是一个分布式SQL数据库,兼容MySQL协议,支持水平扩展和高可用性。TiDB使用了Raft算法来实现数据的一致性和分布式事务。
Netflix Hollow:Hollow是Netflix开源的Java库,用于在大规模分布式系统中实现快速增量数据传输。Hollow使用了Raft算法来实现数据版本管理和一致性。
- Nacos:Nacos是阿里巴巴开源的注册中心和配置中心,它的集群模式实现,因为所有的数据都是直接使用调用Nacos服务端直接创建,因此需要由Nacos保障数据在各个节点之间的强一致性,针对此类型的服务数据,选择了强一致性共识算法来保障数据的一致性。
4.4.3 ZAB 算法的产品应用
Apache ZooKeeper:ZooKeeper是一个开源的分布式协调服务,它提供了分布式锁、配置管理和命名服务等功能,ZooKeeper内部使用了ZAB共识协议来保证一致性。4.5 Paxos、Raft、ZAB优缺点
5 总结
本文介绍了分布式系统共识算法的重要性和挑战。在分布式系统中,一致性是关键目标,要求系统中的所有节点最终达成相同的状态。为了满足一致性,共识算法需要满足一系列条件,包括线性一致性、顺序一致性等等。
在解决分布式系统共识问题时,需要克服节点通信延迟、故障容忍性、去中心化和安全性等挑战。拜占庭将军问题、Paxos算法和Raft算法还有ZAB算法都是常见的分布式系统共识算法,它们各自具有不同的优势和适用性,同时也在工业上应用广泛。了解分布式系统共识算法对于设计和实现可靠的分布式系统至关重要。随着技术的不断发展,分布式系统共识算法将不断演进,为构建更加强大和安全的分布式系统提供支持。通过理解共识算法的基本原理和满足的条件,我们可以更好地把握分布式系统的核心问题,为解决实际应用中的共识挑战提供指导。
本文作者
杰拉尔,来自缦图互联网中心后端团队。
也许你还想看