分布式理论与一致性协议(CAP/BASE/Raft)

本文内容基于个人对特定技术的理解和实践,或为对相关技术内容的整理与分享。请读者批判性阅读,如有疏漏或不准确之处,恳请斧正。

CAP

Consistency 一致性:要求每个节点的数据是强一致的,只能返回最新数据,或者无法访问。

Availability 可用性:每个客户端都能在一定时间收到响应,但是数据不一定是最新的。

Partition Tolerance 分区容错性:发生网络故障后,集群被划分为多个分区,需要确保整个集群还能对外服务。

首先P分布式系统的前提,必须保持网络分区后,系统还能对外服务(服务的质量由A,C决定)。

如果分布式系统在网络分区时无法对外服务,即可用性,就失去了应用分布式的意义。

当发生网络分区后,

  1. 如果要保证一致性CP,那么分区中的的少数派会拒绝服务,因为他们不能保证数据是最新的。
  2. 如果要保证可用性AP,那么就不能保证所有的网络分区数据都是最新的。

BASE理论

BASE 由 CAP 理论演化而来的,主要是为了解决传统数据库的ACID 在面对大规模的分布式场景的局限性而提出的:如果无法做到强一致性,就根据业务的特点,采用适当的方式来达到最终一致性。其三要素分别为:

  • BA: Basically Available (基本可用)

    指系统故障时,允许损失一部分可用性,系统仍然能够对外提供服务

  • S: Soft State 软状态

    允许系统存在中间状态(与数据库完全保持一致相区分),并且不会影响系统的运行。

  • E: Eventually Consistent: 最终一致性

    系统无法一直保证强一致性,而是经过一段时间的同步后,在所有节点都能够达到一致的状态。

BASE 理论是一种AP 理论的延伸,即牺牲掉一部分一致性C, 用来换取最好的可用性 A。

Raft共识协议

典型应用:K8s ETCD, Kafka 集群元信息同步,TIKV

Raft算法是一种分布式一致性算法,将时间划分为任期,用于识别过期消息,保证系统状态的一致。

  1. Leader 选举

    Leader下线/(集群刚刚启动),其他的Follower在选举超时时间内没有收到心跳,就会发起新一轮的选举 称为Candidate,向其他节点发送Request Vote RPC 请求,当收到消息后,Follower 将自身term + 1Candidate 一致,并返回OK。超过半数后,Candidate 称为Leader ,并通知其他节点。

  2. 日志同步

    日志在Raft 是一种抽象的概念,代表需要在各个节点中同步的数据

    Leader 在保存日志时, 需要通过半数节点同意才会完全保存,Leader 在收到半数通过前宕机,则会发生选举,选举时会保证拥有最新数据的节点成为Leader,如果一个日志非最新的Follower发送Request Vote RPC , 会被其他更新日志的节点拒绝。

    当最新数据的Candidate 选举为Leader后,会立即与其他Follower进行数据同步,确保Leader 数据是最新的,旧Leader上线后也会被强制同步数据。

  3. 网络抖动问题:

    如果节点因为网络问题频繁掉线,则他会频繁的触发选举,并且term 还一直增加,导致Leader 一直在选举,集群无法服务。ETCD 算法,通过一次预投票解决该问题,预投票中term 不会增加,收到消息的节点会检查与Leader的链接,如果Leader 确实挂了,则回复OK,预投票节点正式发起投票 candidate term + 1;

Raft算法将时间划分为任意长度的任期(Term), 用于识别过期的消息,每一个任期都始于一轮新的选举。

选举Leader

初始状态

Follower等待超过选举时间后,仍然没有收到Leader的心跳,就会发起选举

  • 转换角色为Candidate
  • term + 1
  • 给自己投一票
  • 向其他节点发送 Request Vote RPC 请求

如果集群中只有则一个Candidate, 其他的Follower会:term + 1, 响应赞同票

此时该Candidate 转换角色为Leader 并发送请求给其他的节点。

Leader下线

此时会重复初始状态,最先超时的Follower选举为新的Leader。

分裂投票问题:如果有多个Follower同时选举,并且票数一致,此时无法决策Leader节点。因此Raft 为每个节点随机设置超时时间,避免冲突;即使真的冲突,不论是Follower还是Candidate都会在超时时间后,新增任期,发起选举,从而打破冲突。

日志复制

每个节点的日志进度根据三个指针表示:每一个Follower保存自己的commitIndex, 而Leader维护所有Follower的NextIndex(下一个要发送的索引) ,matchIndex(已经收到的索引)

Raft Leader 保存日志后不会响应提交,而是发送AppendEntries 告知其他节点,等待超过半数节点回复ACK后,才会更新 commitIndex。

提交完第一个日志后,Leader会提交下一条日志并且要求其他节点也提交(发送最新的commitIndex)。

Leader下线

首先进行选举,然后向其他节点发送一致性检查:

  • 如果节点反馈一致,则无视
  • 如果不一致,Leader则将Follower对应的 Next index 回退一位,继续匹配,直到next index 与 match index一致,然后开始传输恢复日志

Leader上线

上一个任期的Leader,有可能还未将日志传给当前任期的Leader, 也就是包含过期的日志,当前Leader 仍然需要进行一致性检查。强制覆盖旧Leader的日志。

数据安全性

如果Leader下线后,增加了一台新的空节点,并且该空节点先发起选举,此时其他节点不会投票给该新节点。这是由于新节点再发送Request Vote RPC (term, index) 时,index 如果小于某个节点,则该节点并不会赞成,最终应该是index 最大的节点才能成为新的Leader。

其中两个参数具有优先级,如果一台机器的Term更大,则称为Leader, 只有Term 较小或者是相同时才比较Index。

超过半数节点的好处:

  • Leader完整性:Leader 宕机之后,假设有5节点,至少还有两台机器拥有最新日志,由于数据强一致性的支持,下一次如果选举成功,则至少需要3个节点在线,其中必然存在一台最新日志的机器,赢得选举。
  • 状态机安全性:如果一条日志提交成功,由于Leader完整性,则日后必然能传递到其他节点

选举震荡问题

如果网络存在波动,始终有一个节点连接不到其他节点,则会频发的触发选举,Term不断增大,当他上线后,总是能成功选举为Leader,如果网络波动后再次下线又将导致重新选举。

K8s中使用ETCD共识算法,避免节点Term无效增加,首先发起预投票,其他Follower会检查Leader心跳从而投票,当预投票成功,说明Leader真的下线,此时增加Term 继续投票;如果失败,说明是该节点自身问题,继续充当Follower。

联合共识 Joint Consensus

集群在运行中可能会主动或者被动的上线或者下线节点,这将会导致重新选主的问题,如果集群突然增加了2个节点,可能会进行重新选主,例如集群节点1、2、3 扩容到1、2、3、4、5, 本来3是主,扩容后3、4、5推举5为主节点,此时1、2还没收到更新后的配置,就会导致集群中出现2个主(3、5,脑裂现象)。Raft 提供了一种联合共识的中间状态,当配置更新后,主节点需要满足在旧配置集合(1、2、3)与新配置集合(1、2、3、4、5)中均取得大多数ACK 才成功。

联合共识对于复杂的变更(多个节点)的实现比较复杂。较为容易实现的方式单节点变更 是每次只控制变更一个节点,其实现原理是:这样一定存在一个在重叠的节点, 例如 3节点扩充到4节点,那么 4节点需要3个大多数节点,其中至少包括2个旧集合的元素,根据每个节点只能投一票的原则,选主时自然会包含旧集合的大多数与新集合的大多数,所以单节点变更天然实现了联合共识。

Gossip 共识协议

典型应用:Redis Cluster

通过传染机制,快速的在集群中同步消息,实现方式比较简单,缺点是只能保证最终一致性。

周期执行流程:

  1. 节点随机挑选几个邻居,发送自身状态信息
  2. 邻居收到消息后,根据时间戳等字段更新自己的信息
  3. 在下一轮中将更新后的信息再发送给其他邻居

Gossip 的优点是不需要选举,并且不会随着节点的增多而影响性能,并且具有较高的容错,离线几个节点依然能正常工作。缺点就是同步需要时间,并且消息存在冗余。

Raft更适合强一致性的场景,如金融交易、元数据管理。而Gossip协议适用于大规模节点的支持最终一致性的消息管理。

posted @ 2025-12-02 00:51  听风讲故事~  阅读(0)  评论(0)    收藏  举报