分布式一致性协议概览

两种一致性的定义

数据复制是导致数据一致性问题的唯一原因,需要复制的原因包括不限于:

  • 当数据库的一部分故障时,系统仍能正常工作;
  • 通过数据的多副本分散读请求,提高吞吐量;
强一致性

写请求提交以后立即改变集群的状态,任何时刻通过所有方式读取到的数据都是严格一致的。

弱一致性(最终一致性)

不保证写请求提交以后立即改变集群的状态,但是在一定的事件之后最终状态是一致的。

事务一致性指的是ACID,本文讨论范围为数据一致性。

CAP理论

Consistency(一致性):数据一致更新,所有数据变动都是同步的,如果不能一致,就会直接失败;

Availability(可用性):每个请求都能保证及时响应,但不保证返回最新数据;

Partition Tolerance(分区容错性):系统内部出现任何数据同步问题时,仍然能提供服务。

一个分布式系统最多只能满足两项指标。

只要有网络交互就一定可能发生节点间的延迟和数据丢失,所以分区容错是必须要保证的指标,一般情况下分布式系统分为两种:CP和AP。

CP:如果不能保证数据在所有节点的强一致,直接写入失败,读取时数据将会不存在。几乎所有的分布式算法,如两阶段提交、三阶段提交、Paxos、Raft都实现了CP,传统的分布式数据库HBase、Redis(存疑)、etcd、zk等都实现了CP。

AP:读请求返回的数据并不完全一致,可能在短时间内会出现脏数据,但系统总是能返回数据的。注册中心Eureka、Nacos等实现了AP。

Nacos在不同场景下使用了不同的协议,注册中心使用了AP协议(自研协议Distro),尽可能保障服务能对外提供,允许部分延迟,而配置中心使用了强一致的CP协议(Raft),避免重要配置丢失导致故障。

BASE 理论本质上是对 CAP 理论的延伸:

BA:Basically Available 基本可用,分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。
S:Soft State 软状态,允许系统存在中间状态,而该中间状态不会影响系统整体可用性。
E:Consistency 最终一致性,系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。

两阶段提交(2PC、XA)

流程

第一阶段 (prepare):协调者向所有参与者发起事务执行请求,所有参与者都将事务是否能执行的信息反馈发给协调者。
第二阶段 (commit/rollback):协调者根据所有参与者的反馈,通知所有参与者,步调一致地在所有分支上commit或者rollback。

问题

同步阻塞:在提交执行过程中,各个参与者在等待其他参与者响应的过程中,将无法执行其他操作。

单点故障:协调者出现故障时整个系统不可用。

数据不一致:在阶段二,协调者发送了commit请求后,发生了网络故障,导致只有部分参与者收到commit请求,并执行提交操作,其他参与者并未commit成功。

不确定性:参与者发生故障时,协调者只能通过超时等机制来中断事务。

三阶段提交

加入预询阶段避免非健康参与者直接执行事务导致阻塞时间过长;超时策略减少阻塞时间。

流程

预询阶段 (can_commit):协调者询问所有参与者是否能正常执行事务,参与者依据自身情况回复预估状态。

预提交阶段 (pre_commit):当所有参与者返回确定信息时,发送事务执行请求,如果有参与者返回否定信息或等待超时,发送abort通知参与者退出预备状态,中断事务。
提交阶段 (do_commit):协调者根据所有参与者的反馈,通知所有参与者,步调一致地在所有分支上commit或者rollback。如果参与者在第二阶段执行完且超时后还没收到commit通知,则自行commit。

问题

效率较低、数据不一致、单点故障

Paxos

Paxos和2PC所起的作用并不相同,Paxos用于保证同一个数据分片的多个副本之前的数据一致性,2PC用于保证属于多个数据分片上的操作的原子性,分布在不同服务器上的数据分片的操作要么全部成功,要么全部失败。

流程

Prepare 阶段:Proposer 选择一个提案编号 n,向所有的 Acceptor 广播 Prepare(n) 请求。

Promise 阶段:Acceptor 接收到 Prepare(n) 请求,此时有两种情况:

  • 如果 n 大于之前接受到的所有 Prepare 请求的编号,则返回 Promise() 响应,并承诺将不会接收编号小于 n 的提案。如果有提案被 Chosen (已经完全结束一轮选择)的话,Promise() 响应还应包含前一次提案编号和对应的值。
  • 否则(即 n 小于等于 Acceptor 之前收到的最大编号)忽略,但常常会回复一个拒绝响应。

所以,Acceptor 需要持久化存储 max_n、accepted_N 和 accepted_VALUE 。
Propose 阶段:当 Proposer 收到超过半数 Acceptor 的 Promise() 响应后,Proposer 向所有节点发起 Accept(n, value) 请求并带上提案编号和值。如果前面的 Promise 响应有返回 accepted_VALUE,那就使用这个值作为 value。如果没有返回 accepted_VALUE,那可以自由决定提案值 value。

Accept阶段:Acceptor 收到 Accept() 请求,在这期间如果 Acceptor 没有对比 n 更大的编号另行 Promise,则接受该提案。

详细图解参考 图解超难理解的 Paxos 算法(含伪代码)_分布式_多颗糖_InfoQ写作社区

活锁问题

当 Proposer 在第一轮 Prepare 发出请求,还没来得及后续的第二轮 Accept 请求,紧接着第二个 Proposer 在第一阶段也发出编号更大的请求。如果这样无穷无尽,Acceptor 始终停留在决定顺序号的过程上,那大家谁也成功不了。

解决活锁最简单的方式就是引入随机超时,这样可以让某个 Proposer 先进行提案,减少一直互相抢占的可能。

Multi-Paxos

只有一个Proposer/Leader,所有的写请求都在Leader上执行并同步到其他Acceptor上。

详细参考:

  1. Paxos 的变种(一):Multi-Paxos 是如何劝退大家去选择 Raft 的_分布式_多颗糖_InfoQ写作社区
  2. 理解分布式一致性:Paxos协议之Multi-Paxos-腾讯云开发者社区-腾讯云

Raft

概览

Raft是强一致性算法,优先追求数据一致,无法达到数据一致时停止服务。

使用Quorum机制,只要集群中超过半数节点正常运行就可以正常提供服务。

典型的中心化架构,包含一个Leader和多个Follower,由Leader节点处理所有写请求,并且将写请求广播到Follower节点。

三个部分共同组成Raft的核心设计:

  1. Leader选举。
  2. 日志复制:从Leader把日志复制到Follower。
  3. 数据安全:通过日志存储数据,已提交日志不能被覆盖、修改、抛弃。
节点的三种角色:

Leader

  • 响应客户端的写请求,将写请求同步写入到其他节点中。
  • 定时向Follower发送心跳,以维持Leader地位或同步数据。

Follower

  • 响应客户端的读请求,负责将写请求引入到Leader节点;
  • 响应Leader和Candidate的其他请求,如投票,数据同步等。

Candidate

  • 在Election Timeout时间内还没有收到Leader的心跳时,Follower会认为已经没有Leader了,会变成Candidate参与竞争Leader。
时间周期Term:

每一个Term都开始于选举Leader,在选出Leader之后进入漫长的数据同步阶段。当Leader因为故障下线,则开启一个新Term,并选出新的Leader节点(和Kafka的LeaderEpoch概念非常相似)。

选举

Follower变成Candidate:

如果某个Follower超过指定时间没有收到Leader心跳(达到选举超时时间),会认为Leader已下线,把自己转变成Candidate节点并发起选举流程。

Follower发起投票:

Follower把Term加1,然后把自己切换到候选者状态,先向自己投票,然后向其他所有节点发起RequestVote,如果收到集群中大多数节点的回应,那么当前Candidate切换到Leader状态。RequestVote包含以下信息:当前任期、节点ID、最后一条日志索引号、最后一条日志任期号。

Raft节点间通过RPC的方式交流,实现一个基本的Raft只需要两种RPC类型:RequestVotes和AppendEntries。RequestVotes用于候选者在选举阶段向其他节点发送投票请求,AppendEntries用于心跳和Leader节点向其他节点复制数据(日志)。

为Follower投票:

其他Follower节点只会遵循FIFO的原则给一个Candidate节点投票,在一个Term内只能投一次,它将投给第一个满足条件的RequestVote,并拒绝其他的候选者请求。

满足以下三个条件时才能投票:当请求的Term大于自己、请求的日志索引号和日志任期号大于自己、当Term和自己相等时但自己的votedFor为空(说明没投过票)。

选举成功:

超过半数节点为自己投票时,当选Leader节点。

节点在Candidate阶段可能会收到来自Leader节点的AppendEntries请求,此时节点会检查Leader节点的term,如果比自己的高,则回退到follower状态,否则直接拒绝AppendEntries请求并继续完成由自己开启的选举。

当大多数节点同时成为Candidate时,可能出现没有任何候选者获得大多数节点支持的情况,此时节点会等待选举超时,之后重新开启新term选举。为了确保选举不会一直死循环,超时时间在节点上随机生成。

为什么不为节点设置优先级,让性能好的节点永远当Leader?原因是优先级不好控制,超时太快或太慢都可能让系统陷入循环。

复制

复制原则:

Leader决定一条数据是否commit的依据是:大多数节点认为可以commit的时候,Leader就认为可以commit了。在Raft中,集群数据一致的基本规则是Leader强制要求Follower跟随其数据保持一致。

Leader会先把数据写入到自身的log中,并通过如前所述的AppendEntries远程调用发起RPC请求,尝试通知其他节点写入此数据。当所有节点都写入成功后,Leader会把状态更新为写入成功,并返回此结果给客户端。

如果Follower崩溃、失败或超时,或者数据包丢失导致RPC并未执行成功,Leader会重试AppendEntries(甚至在响应客户端之后也会继续重试)直到超过半数的Follower都成功写入数据。

为了保证安全性,Raft规定日志必须连续地提交,不允许出现空洞,如果给定位置的记录已经提交,那么所有前面的记录也都已经提交,这是Raft和Paxos的不同之处。

一致性检查:

通过Term和Index编号可以用来唯一确定一条数据,当Follower和Leader的数据不一致时,下一次AppendEntries的一致性检查会返回失败,之后的AppendEntries会尝试让Index递减以寻求找到Leader和Follower数据一致的Index位置,从而通过增删操作把Leader的最新数据同步到Follower中,最终达到:

  1. 在不同节点中,如果两个日志条目具有相同的索引和任期号,则它们的内容一定相同。
  2. 在不同节点中,如果两个日志条目具有相同的索引和任期号,则它们之前的所有日志一定相同。

日志覆盖:

类似Kafka主从同步的截断,请参考Kafka博文内容,搜索关键字为“截断”。

选举投票的限制
  1. 选举时节点不会投票给log序号(对比log中最后一条数据的Term和Index)小于自身的RequestVote请求。这一限制用来保证Leader节点最终保存所有已经commit的数据。

  2. 新上任的Leader不能直接抛弃前一任期未提交的日志,而是继续处理。但是对于前面任期的日志,即使被多数节点接收(没有commit),也不能提交该日志,这些没有commit的日志并不安全,只能等当前任期的日志commit之后才能提交前面任期的日志。

对于限制2,考虑如下图所示场景(来源于Raft论文):

a. S1为Leader节点,当前任期为2,最新日志为2-2(索引2上任期为2的日志),但还没将2-2复制到多数节点,也没有提交。

b. S1崩溃,S5当选新的Leader节点,任期为3,最新日志为2-3,还没有把2-3复制到其他节点。

c. S5崩溃,S1重新当选Leader节点,任期为4,然后将2-2复制到其他节点。根据上述限制2,在还没有提交3-4之前,不允许提交2-2。如果不遵循限制2,直接提交2-2,将会出现图d中的问题,2-2作为已提交的数据被错误的覆盖了。

d. 没有遵循限制2的场景:S1崩溃,S5再次当前Leader节点,任期为3(通过S2,S3,S4的投票当选),然后把2-3复制到其他节点并提交。此时如果图c中没有遵循限制2,直接提交2-2,该已提交数据将会丢失。如果遵循了限制2,没有提交2-2,那么此时2-2作为未提交数据,被覆盖是合理的。

e. 当S1的3-4被提交之后才提交前面任期的2-2,之后S5不可能会被选为Leader,因为已经超过半数节点的任期大于S5,就不可能已提交数据丢失的问题。

应用
产品类别用途
TiKV数据库底层存储引擎完全基于 Raft 实现数据一致性:数据分片为 Region,每个 Region 有多个副本,每个 Region 副本组通过 Raft 选举 Leader,Leader 负责处理读写请求,Follower 同步 Leader 日志。
etcd配置中心Kubernetes 集群的元数据存储(如 Pod 配置、服务发现)、分布式锁。
Kafka(KRaft)配置中心替代KafkaController对ZooKeeper依赖,用于集群控制平面的元数据管理:通过 Raft 选举 Controller Leader,负责集群的分区 Leader 选举、元数据同步,存储 Topic/Partition 配置、Broker 信息、Controller 节点等元数据;
Redis Cluster数据库用于 Redis 集群的主从副本一致性同步,替代传统的异步复制。通过 Raft 选举主节点,保证写操作的日志被多数副本同步后再返回,实现强一致性;适用于对数据一致性要求高的场景(如分布式锁、配置存储),传统缓存场景仍推荐异步复制。
Nacos配置中心支持两种一致性协议:Distro(默认,高可用)和 Raft(强一致性)

参考

  1. Raft动画
  2. 分布式一致性算法-Paxos、Raft、ZAB、Gossip
  3. 分布式一致性算法,你确定不了解一下
  4. In Search of an Understandable Consensus Algorithm
  5. 图解超难理解的 Paxos 算法(含伪代码)_分布式_多颗糖_InfoQ写作社区
  6. Paxos 的变种(一):Multi-Paxos 是如何劝退大家去选择 Raft 的_分布式_多颗糖_InfoQ写作社区
  7. 理解分布式一致性:Paxos协议之Multi-Paxos-腾讯云开发者社区-腾讯云
  8. Raft算法QA
  9. Git参考:
    1. https://github.com/datatechnology/jraft
    2. https://github.com/MicroRaft/MicroRaft
    3. https://github.com/wenweihu86/raft-java
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值