分布式系统是一个硬件或软件系统分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。在一个分布式系统中,一组独立的计算机展现给用户的是一个统一的整体,就好像是一个系统似的。系统拥有多种通用的物理和逻辑资源,可以动态的分配任务,分散的物理和逻辑资源通过计算机网络实现信息交换。
分布式服务的特点
- 可扩展
- 不存在单点故障问题
- 服务无状态
一、CAP理论
CAP 定理是分布式系统设计中最基础,也是最为关键的理论。它指出,分布式数据存储不可能同时
满足以下三个条件。
- 一致性(Consistency):在分布式系统环境下,数据在多台机器上有多个副本。当对数据执行更新操作时,数据更新操作完成后,所有节点在同一时间的数据完全一致,客户端读取的数据永远是更新后的最新数据,要么获得一个错误。
- 可用性(Availability) : 服务一直可用,即对于客户端每次读或写请求都能在有限时间内得到正确的响应,但不保证获取的数据为最新数据。 “有限时间内”是指对于客户端的每一个操作请求,系统必能够在指定的时间内返回对应的处理结果,如果超过了这个时间范围,系统将被认为是不可用的。
- 分区容错性(Partition tolerance) : 在网络分区出现故障时保证系统不受影响,仍让可以对外提供一致性和可用性服务。网络分区出现故障通常指的是节点之间的网络故障,但是节点内部网络是完好的,这种情况导致的问题就是节点之间无法进行数据复制。
也就是说,CAP 定理表明,在存在网络分区的情况下,一致性和可用性必须二选一。而在没有发生网络故障时,即分布式系统正常运行时,一致性和可用性是可以同时被满足的。这里需要注意的是,CAP 定理中的一致性与 ACID 数据库事务中的一致性截然不同。还有就是CAP定理是分布式系统中数据库相关的理论。掌握 CAP 定理,尤其是能够正确理解 C、A、P 的含义,对于分布式系统架构来说非常重要。因为对于分布式系统来说,网络故障在所难免,如何在出现网络故障的时候,维持系统按照正常的行为逻辑运行就显得尤为重要。你可以结合实际的业务场景和具体需求,来进行权衡。
- CA:CA (consistency + availability),这样的系统关注一致性和可用性,它需要非常严格的全体一致的协议,比如“两阶段提交”(2PC)。CA 系统不能容忍网络错误或节点错误,一旦出现这样的问题,整个系统就会拒绝写请求,因为它并不知道对面的那个结点是否挂掉了,还是只是网络问题。唯一安全的做法就是把自己变成只读的。
- CP:(consistency + partition tolerance),这样的系统关注一致性和分区容忍性。它关注的是系统里大多数人的一致性协议,比如:Paxos 算法(Quorum 类的算法)。这样的系统只需要保证大多数结点数据一致,而少数的结点会在没有同步到最新版本的数据时变成不可用的状态。这样能够提供一部分的可用性。
- AP:(availability + partition tolerance),这样的系统关心可用性和分区容忍性。因此,这样的系统不能达成一致性,需要给出数据冲突,给出数据冲突就需要维护数据版本。Dynamo 就是这样的系统。
CAP理论的证明
由于分布式系统中多个节点分布在不同的机器(当然也可以是单台机器上的多个节点),节点之间通过网络进行通信,由于网络不完全可靠,所以在分布式系统中我们必须要满足分区容错性。若要舍弃分区容错性,也就是只有一个分区,何谈分布式系统,所以下面的讨论总是围绕分区容错性来讨论。当网络分区出现故障的时候,我们可以通过一定策略来达到一致性或可用性的要求。
一个简单的分布式系统如下:系统中有两个节点对外提供服务,Server1和Server2,Server1和Server2各自维护和访问自己的数据库DB1和DB2,DB1和DB2数据通过复制技术保证数据之间的同步。
在理想情况下,即同时满足CAP的情况,DB1和DB2的数据是完全一致的,Server1与Server2可以同时对外提供服务,用户不管是请求server1还是请求server2,都会得到立即响应,并且获取的数据是完全一致且为最新数据。
但是现实中不可能出现这么理想的情况,当DB1和DB2之间网络发声故障时,此时有用户向Server1发送数据更新请求,DB1数据更新后无法将最新数据同步到DB2,此时DB2中存储的任然是旧数据;这个时候,有用户向Server2发送读数据请求,由于数据还没有同步,应用程序无法将最新的数据返回给用户,这个时候有两种选择:
- 牺牲数据一致性,保证服务可用性。Server返回DB2中的旧数据给用户
- 牺牲服务可用性,保证数据一致性。阻塞服务请求,直到故障恢复,DB1与DB2数据同步完成之后再恢复提供服务。
从上面分析可以看出,分布式系统不可能同时满足CAP。在实际应用的过程中,由于不能同时满足CAP,我们必须舍弃其中之一,由于P是所有分布式系统中不许满足的,所以最后需要在C和A之间做个取舍。
在大多数的分布式数据库中(如redis、Hbase等),往往是优先保证CP,因为无论是分布式系统还是想zooKeeper这种分布式协调组件,数据一致性往往是他们最基本的要求。
对于需要保证高可用性的系统,将舍弃数据一致性而保证服务的高可用性。
二、FLP不可能理论
FLP 不可能性的名称起源于它的三位作者:Fischer、Lynch 和 Paterson。它是关于理论上能做出的功能最强的共识算法会受到怎样的限制的讨论。
现实生活中的系统往往都是异步系统。因为系统中各个节点之间的延时,是否宕机等等都是不确定的。那么,在最小化异步模型系统中,是否存在一个可以解决一致性问题的确定性共识算法?
共识问题有几个变种,它们在“强度”方面有所不同。
通常,一个更“强”问题的解决方案同时也能解决比该问题更“弱”的问题。
共识问题的一个较强的形式如下,给出一个处理者的集合,其中每一个处理者都有一个初始值:
- 所有无错误的进程(处理过程)最终都将决定一个值;
- 所有会做决定的无错误进程决定的都将是同一个值;
- 最终被决定的值必须被至少一个进程提出过。
这三个特性分别被称为“终止”、“一致同意”和“有效性”。任何一个具备这三点特性的算法都被认为是解决了共识问题。
FLP 不可能性则讨论了异步模型下的情况,主要结论有两条在:
- 异步模型下不存在一个完全正确的共识算法。不仅上述较“强”形式的共识算法不可能实现,FLP还证明了比它弱一些的、只需要有一些无错误的进程做决定就足够的共识算法也是不可能实现的。
- 在异步模型下存在一个部分正确的共识算法,前提是所有无错误的进程都总能做出一个决定,此外没有进程会在它的执行过程中死亡,并且初始情况下超过半数进程都是存活状态。
FLP 的结论是,在异步模型中,仅一个处理者可能崩溃的情况下,就已经没有分布式算法能解决共识问题。这是该问题的理论上界。其背后的原因在于,异步模型下对于一个处理者完成工作然后再回复消息所需的时间并没有上界。因此,无法判断出一个处理者到底是崩溃了,还是在用较长的时间来回复,或者是网络有很大的延迟。
三、ACID理论
ACID原则指的是:Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性),用了四种特性的缩写。
ACID也是一种比较出名的描述一致性的原则,通常出现在分布式数据库领域。具体来说,ACID原则描述了分布式数据库需要满足的一致性需求,同时允许付出可用性的代价。
ACID特征如下:
- Atomicity:每次操作是原子的,要么成功,要么不执行;
- Consistency:数据库的状态是一致的,无中间状态;
- Isolation:各种操作彼此之间互相不影响;
- Durability:状态的改变是持久的,不会失效
四、BASE理论
BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)
BASE是指 基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency)。
它的核心思想是既然无法做到强一致性(CAP 的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性。
BA - Basically Available 基本可用
分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
这里的关键词是“部分”和“核心”,实际实践上,哪些是核心需要根据具体业务来权衡。
例如登录功能相对注册功能更加核心,注册不了最多影响流失一部分用户,如果用户已经注册但无法登录,那就意味着用户无法使用系统,造成的影响范围更大。
S - Soft State 软状态
允许系统存在中间状态,而该中间状态不会影响系统整体可用性。这里的中间状态就是 CAP 理论中的数据不一致。
E - Eventual Consistency 最终一致性
系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
这里的关键词是“一定时间” 和 “最终”,“一定时间”和数据的特性是强关联的,不同业务不同数据能够容忍的不一致时间是不同的。
例如支付类业务是要求秒级别内达到一致,因为用户时时关注;用户发的最新微博,可以容忍 30 分钟内达到一致的状态,因为用户短时间看不到明星发的微博是无感知的。
而“最终”的含义就是不管多长时间,最终还是要达到一致性的状态。
BASE模型是传统ACID模型的反面,不同于ACID,BASE强调牺牲强一致性,从而获得可用性,数据允许在一段时间内的不一致,只要保证最终一致就可以了
五、比较
ACID和BASE的区别与联系
- ACID是传统数据库常用的设计理念,追求强一致性模型。
- BASE支持的是大型分布式系统,牺牲掉对一致性的约束(但实现最终一致性),来换取一定的可用性。
- ACID和BASE代表了两种截然相反的设计哲学。
在英文中,ACID和BASE分别是“酸”和“碱”,看似对立,实则是分别对CAP三特性的不同取舍。在分布式系统设计的场景中,系统组件对一致性要求是不同的,因此ACID和BASE又会结合使用。
BASE和CAP之间的关系
BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充: CAP 理论是忽略延时的,而实际应用中延时是无法避免的。
这一点就意味着完美的 CP 场景是不存在的,即使是几毫秒的数据复制延迟,在这几毫秒时间间隔内,系统是不符合 CP 要求的。
因此 CAP 中的 CP 方案,实际上也是实现了最终一致性,只是“一定时间”是指几毫秒而已。
AP 方案中牺牲一致性只是指发生分区故障期间,而不是永远放弃一致性。
这一点其实就是 BASE 理论延伸的地方,分区期间牺牲一致性,但分区故障恢复后,系统应该达到最终一致性。
六、分布式一致性协议
在谷歌的 Transaction Across DataCenter 视频中,我们可以看到下面这样的图,这个是 CAP 理论在具体工程中的体现。
Paxos 协议
Paxos 协议,是莱斯利·兰伯特(Lesile Lamport)于 1990 年提出来的一种基于消息传递且具有高度容错特性的一致性算法。
Paxos 协议是强一致性、高可用的去中心化分布式协议。它是目前公认的解决分布式共识问题最有效的算法之一。一般描述它,都会包含两个词:分布式容错、分布式共识算法。
- 分布式容错:指在分布式环境下,能够容忍一部分节点宕机,还能向外提供稳定的服务。
- 分布式共识算法:是指在分布式环境下,各个节点能对某个值达成共识,即所有节点都认同某个值。
Google 的很多大型分布式系统都采用了 Paxos 算法来解决分布式一致性问题,如 Chubby、Megastore 以及 Spanner 等。开源的 ZooKeeper 以及 MySQL 5.7 推出的用来取代传统的主从复制的 MySQL Group Replication 等纷纷采用 Paxos 算法解决分布式一致性问题。阿里云PolarDB、PolarDB-X都有基于Paxos做数据同步复制。
Paxos 协议的流程较为复杂,但其基本思想却不难理解,类似于人类社会的投票过程。Paxos 协议中,有一组完全对等的参与节点,这组节点各自就某一事件做出决议,如果某个决议获得了超过半数节点的同意则生效。Paxos 协议中只要有超过一半的节点正常,就可以工作,能很好对抗宕机、网络分化等异常情况。
- 推荐一篇比较容易读的—— Neat Algorithms - Paxos,这篇文章中还有一些小动画帮助你读懂。
- 还有一篇可以帮你理解的文章是 Paxos by Examples
Raft 协议
Raft 算法和 Paxos 的性能和功能是一样的,但是它和 Paxos 算法的结构不一样,这使 Raft 算法更容易理解并且更容易实现。那么 Raft 是怎样做到的呢?
Raft 把这个一致性的算法分解成了几个部分,一个是领导选举(Leader Selection),一个是日志复制(Log Replication),一个是安全性(Safety),还有一个是成员变化(Membership Changes)。对于一般人来说,Raft 协议比 Paxos 的学习曲线更低,也更平滑。
Raft 协议中有一个状态机,每个结点会有三个状态,分别是 Leader、Candidate 和 Follower。
Follower 只响应其他服务器的请求,如果没有收到任何信息,它就会成为一个 Candidate,并开始进行选举。收到大多数人同意选票的人会成为新的 Leader。
一旦选举出了一个 Leader,它就开始负责服务客户端的请求。每个客户端的请求都包含一个要被复制状态机执行的指令。Leader 首先要把这个指令追加到 log 中形成一个新的 entry,然后通过AppendEntries RPC 并行地把该 entry 发给其他服务器(server)。如果其他服务器没发现问题,复制成功后会给 Leader 一个表示成功的 ACK。
Leader 收到大多数 ACK 后应用该日志,返回客户端执行结果。如果 Follower 崩溃 (crash)或者丢包,Leader 会不断重试 AppendEntries RPC。
这里推荐几个不错的 Raft 算法的动画演示:
Raft - The Secret Lives of Data
Raft Distributed Consensus Algorithm Visualization
另外这里提下,TiDB的TiKV就是基于Raft实现
Gossip 协议
Gossip 算法也是 Cassandra、Redis Cluster 使用的数据复制协议。这个协议就像八卦和谣言传播一样,可以“一传十、十传百”传播开来。但是这个协议看似简单,细节上却非常麻烦,当节点数大于32时,很可能就会出现网络风暴,阿里云的Redis Cluster有将Gossip优化成Raft
Quorum NRW模型
Quorom 机制,是一种分布式系统中常用的,用来保证数据冗余和最终一致性的投票算法,在有冗余数据的分布式存储系统当中,冗余数据对象会在不同的机器之间存放多份拷贝。
分布式系统中的每一份数据拷贝对象都被赋予一票。每一个操作必须要获得最小的读票数(Vr)或者最小的写票数(Vw)才能读或者写。如果一个系统有V票(意味着一个数据对象有V份冗余拷贝),那么这最小读写票必须满足:
- Vr + VW > V
- Vw > V/2
第一条规则保证了一个数据不会被同时读写。当一个写操作请求过来的时候,它必须要获得Vw个冗余拷贝的许可。而剩下的数量是V-Vw 不够Vr,因此不能再有读请求过来了。同理,当读请求已经获得了Vr个冗余拷贝的许可时,写请求就无法获得许可了。
第二条规则保证了数据的串行化修改。一份数据的冗余拷贝不可能同时被两个写请求修改。
Amazon Aurora 作为云原生数据库的先行者,并没有采用“唯一正确的一致性协议” Paxos,而是采用了 Quorum 模型,即要求“写副本数+读副本数 > 总节点数”。
Aurora 这么做的底气在于:整个数据库中只有一条有序的 redo-log,亚马逊在论文中提出了 the log is the database (日志即数据) 的思想:只要日志一致,数据一定也是一致的。如果将数据库看作一个状态机,在一致的状态机上应用相同的日志流,最后得到的状态当然也是一致的,在下个小节的Paxos/Raft 算法中也用到了类似的思想。Aurora 仅仅是用 Quorum 来推进各副本上日志应用的位点,而数据库内容本身是由同一条 log 得到的,最多有的新旧之分,而不可能数据不一致。
如果有不止一条 redo-log,这一套做法就行不通了。我们通常认为 Aurora 解决了高可用问题,但并没有很好的解决扩展性问题,数据库的吞吐量(尤其是写入吞吐量)仍然受单个节点限制。
PBFT协议
Practical Byzantine Fault Tolerance,拜占庭将军问题(The Byzantine Generals Problem),它其实是借拜占庭将军的故事展现了分布式共识问题,这个在区块链中非常著名。
PBFT算法的运作步骤为:
- 取一个副本作为主节点,其他的副本作为备份;
- 用户端向主节点发送使用服务操作的请求;
- 主节点通过广播将请求发送给其他副本;
- 所有副本执行请求并将结果发回用户端;
- 用户端需要等待F+1个不同副本节点发回相同的结果,作为整个操作的最终结果。
PBFT对每个副本节点提出了两个限定条件:
- 所有节点必须是确定性的。也就是说,在给定状态和参数相同的情况下,操作执行的结果必须相同;
- 所有节点必须从相同的状态开始执行。
PBFT算法包含三个重要阶段,分别是预准备(pre-prepare)、准备(prepare)和确认(commit),
- pre-prepare阶段和prepare阶段用来把在同一个view里发送的请求给确定下序列,就是排好序,让各个replicas节点都认可这个序列,照序执行。
- prepare阶段和commit阶段用来确保那些已经达到commit状态的请求即使在发生view change后在新的view里依然保持原有的序列不变
PBFT算法存在的问题:
- 计算效率依赖于参与协议的节点数量,不适用于节点数量过大的区块链系统,扩展性差。
- 系统节点是固定的,无法应对公有链的开放环境,只适用于联盟链或私有链环境。
- PBFT算法要求总节点数n>=3f+1(其中,f代表作恶节点数)。系统的失效节点数量不得超过全网节点的1/3,容错率相对较低。
Zab协议
Zab协议是为分布式协调服务Zookeeper专门设计的一种 支持崩溃恢复 的 原子广播协议,是Zookeeper保证数据一致性的核心算法。Zab借鉴了Paxos算法,但又不像Paxos那样,是一种通用的分布式一致性算法。它是特别为Zookeeper设计的支持崩溃恢复的原子广播协议。
在Zookeeper中主要依赖Zab协议来实现数据一致性,基于该协议,zk实现了一种主备模型(即Leader和Follower模型)的系统架构来保证集群中各个副本之间数据的一致性。这里的主备系统架构模型,就是指只有一台客户端(Leader)负责处理外部的写事务请求,然后Leader客户端将数据同步到其他Follower节点。
Zookeeper 客户端会随机的链接到 zookeeper 集群中的一个节点,如果是读请求,就直接从当前节点中读取数据;如果是写请求,那么节点就会向 Leader 提交事务,Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交