【美团一面】聊聊分布式一致性共识算法

454 阅读12分钟

笔者在面试美团部门的时候,被问到如何保证分布式系统的强一致性,当时回答的不是很好,所以就撸了一把该方面的知识体系。 思考:Zookeeper 是如何保证数据一致性的?这也是困扰分布式系统框架的一个难题。

拜占庭将军问题

拜占庭将军问题是一个协议问题,拜占庭帝国军队的将军们必须全体一致的决定是否攻击某一支敌军。问题是这些将军在地理上是分隔开来的,并且将军中存在叛徒。叛徒可以任意行动以达到以下目标:欺骗某些将军采取进攻行动;促成一个不是所有将军都同意的决定,如当将军们不希望进攻时促成进攻 行动;或者迷惑某些将军,使他们无法做出决定。如果叛徒达到了这些目的之一,则任何攻击行动的结果都是注定要失败的,只有完全达成一致的努力才能获得胜利。

拜占庭将军问题其实就是如何让一个国家处于不同地理位置的将军所接受的命令一致。(视频会议解决?)

Paxos 算法

什么是 Paxos 算法?

Paxos算法是一种基于消息传递且具有高度容错特性的一致性算法。

Paxos 的理论模型都有么?

Paxos算法解决的问题?

如何快速正确的在一个分布式系统中对某个数据值达成一致,并且保证不论发生任何异常, 都不会破坏整个系统的一致性。

Basic Paxos 算法

Basic Paxos算法角色有什么?

在一个Paxos系统中,首先将所有节点划分为Proposer(提议者),Acceptor(接收者),和 Learner(学习者)。(注意:每个节点都可以身兼数职)。

Client:系统外部角色,请求发起者。像民众。

Proposer:提议一个值,用于投票表决。向议员替民众提出议案。提议者可以理解为一个反向代理,用于转发请求。

Acceptor(Voter):对每个提议的值进行投票,并存储接受的值。

Learner:被告知投票的结果,接受达成共识的值,存储保存,不参与投票的过程。像记录员。

Basic Paxos算法的基本流程是什么?

每一个提义都有一个全局唯一自增Id。

一阶段准备;

客户端向提议者发起一个提议,并发送一个提议询问请求同步发送给投票者,投票者进行投票,将结果返回给提议者,如果投票超过半数同意该提议询问请求,则提议通过,进行二阶段提交;

二阶段提交;

提议者发送将提议的值同步发送给投票者,投票者进行投票,将结果返回给提议者,如果超过半数同意该提议的值,则更新该值。

流程总结

一阶段

proposer提出一个提案,编号为N,此N大于这个 proposer之前提出的提案编号。请求 Accpetore的 quorum接受。

如果N大于此 acceptor 之前接受的任何提案编号则接受,否则拒绝。

二阶段

如果达到了多数派, proposer会发出 accept请求,此请求包含提案编号N,以及提案内容。

如果此 acceptor在此期间没有收到任何编号大于N的提案则接受此提案内容,否则忽略。

Basic Paxos算法流程可能出现的问题

①Basic Paxos 部分节点失败,但达到半数同意

由于Basic Paxos 部分节点失败,但达到半数同意,所以最终提议还是会通过。

② Basic Paxos Proposer 失败

如果提议者集群某一台宕机了,客户会进行请求重试,选择可用的提议者节点,此时提议编号自增加 1,但提议内容不变,最终提议通过。

③Basic Paxos 活锁问题

客户端在一个提案的二阶段执行之前,又发起了一个提案,如此并发的循环提案,就会导致活锁问题,活锁问题本质就是客户端的所有提案一直被拒绝。

假设客户端提交了一个提案 A,提案编号为 1, 执行一阶段通过,开始执行二阶段;

此时客户端又发起了一个提案 B,提案编号为 2,执行一阶段通过,开始执行二阶段;

提案 A 开始执行二阶段,由于提案 B 的编号大于提案 A 的编号,提案 A 会被拒绝,提案 A 的二阶段执行结束;

此时客户端又发起了一个提案 C,提案编号为 3,执行一阶段通过,开始执行二阶段;

提案 B 开始执行二阶段,由于提案 C 的编号大于提案 B 的编号,提案 B 会被拒绝,提案 B 的二阶段执行结束;

如此往复,导致客户端所有的提案都被拒绝。

④Basic Paxos 性能较差

由于 Basic Paxos 的每阶段执行都要通过 RPC 调用,所以性能会比较差。

Multi Paoxs 算法

Multi Paoxs 是什么?

Multi Paoxs 是对 Basic Paxos 的流程优化。将 Baic Paxos 的二阶段,改成了三阶段。

Paxos 算法的缺点是什么?

① 理论过于抽象,难以应用到生产实践中;

② 性能会比较差;因为Basic Paxos 的每阶段执行都要通过 RPC 调用。

③ 并发调用时,会出现活锁问题。即客户端在一个提案的二阶段执行之前,又发起了一个提案,如此并发的循环提案,就会导致活锁问题,活锁问题本质就是客户端的所有提案一直被拒绝。

Raft 算法

Raft 算法是什么?

Raft 算法是一种将学术界的 Multi Paxco 理论进行工业化的强一致性算法。

Raft 算法的子问题有哪些?

  • 领导者选举(Leader election)
  • 日志同步/复制(Log replication)
  • 故障恢复( Safety)
  • 日志压缩(Log compaction)
  • 成员变更(Membership change)

Raft 算法的三个角色是什么?

领导者(Leader):接受客户端请求,并向Follower同步请求日志,当日志同步到大多数节点上后告诉Follower提交日志。任意一个term最多一个真正的leader,并且在网络分区的情况下不会存在多个leader,因为即时出现了网络分区,总会存在节点数较少的区域无法

跟随者(Follwer):接受并持久化Leader同步的日志,在Leader告之日志可以提交之后,提交日志。

参选者(Candidate):Leader选举过程中的临时角色。

Raft算法角色状态转换关系是什么?

Follower只响应其他服务器的请求。如果Follower超时没有收到Leader的消息,它会成为一个Candidate并且开始一次Leader选举。收到大多数服务器投票的Candidate会成为新的Leader。Leader在宕机之前会一直保持Leader的状态。

Raft 算法流程是什么?

Raft 的流程有 Leader 选举,日志复制,数据恢复。

Leader 选举

什么时候会进行选举?

服务器集群第一次启动时,会进行选举;

Leader 节点宕机后,Follower在选举超时时间内没有收到Leader的heartbeat,会重新进行选举。

Raft 使用心跳(heartbeat)触发Leader选举。当服务器启动时,初始化为Follower。Leader向所有Followers周期性发送heartbeat。如果Follower在选举超时时间内没有收到Leader的heartbeat,就会等待一段随机的时间后发起一次Leader选举。

如何进行选举?

Follower将其当前term加一然后转换为Candidate。它首先给自己投票并且给集群中的其他服务器发送 RequestVote RPC (RPC细节参见八、Raft算法总结)。

  • 赢得了超过半数的选票,成功选举为Leader;
  • 收到了Leader的消息,表示有其它服务器已经抢先当选了Leader;
  • 没有服务器赢得多数的选票,Leader选举失败,等待选举时间超时后发起下一次选举。
  • 拥有最新的已提交的log的Follower才有资格成为Leader,这是为了保证数据最新,防止发生数据丢失。

选举出Leader后,Leader通过定期向所有Followers发送心跳信息维持其统治。若Follower一段时间未收到Leader的心跳则认为Leader可能已经挂了,再次发起Leader选举过程。

Raft 选举会不会出现脑裂情况?

不会。脑裂情况是集群出现网络分区后,分区与分区的节点无法进行通信,分区内的节点可以进行通信。由此可能会选举出多个 Leader 节点。

但是 Raft 选举的规则遵循过半数原则,出现网络分区后,如果部署的是奇数台节点,只会有一个节点数较多的区域选举出 Leader 节点。

日志同步

如何进行日志同步?

Leader选出后,就开始接收客户端的请求。Leader把请求作为日志条目(Log entries)加入到它的日志中,然后并行的向其他服务器发起 AppendEntries RPC (RPC细节参见八、Raft算法总结)复制日志条目。当这条日志被复制到大多数服务器(超过半数)上,Leader将这条日志应用到它的状态机并向客户端返回执行结果。

日志同步过程中 follower 宕机了怎么办?

某些Followers可能没有成功的复制日志,Leader会无限的重试 AppendEntries RPC直到所有的Followers最终存储了所有的日志条目。

日志结构是什么?

日志由有序编号(log index)的日志条目组成。每个日志条目包含它被创建时的任期号(term),和用于状态机执行的命令。如果一个日志条目被复制到半数以上的服务器上,就被认为可以提交(commit)了。

Leader 什么时候会与 Followers 的日志出现不同的情况?如何解决呢?

Leader/Follower 宕机一段时间之后重启,可能会与Followers 的日志不一致。

Leader 宕机之后,会选举出新的 Leader,重启后会变成 Follower。

Leader 会先找到与 Follower 日志相同的位置,然后将覆盖Follower 该位置之后的日志。

Raft 的流程模拟网站

Raft Consensus Algorithm

Raft与Multi-Paxos的异同

相同点

不同点

Raft 的应用场景有什么?

Nacos 实现 CP 时就是用的是 Raft 算法。

ZAB 算法

什么是ZAB算法?

Zab 算法的全称为Zookeeper Atomic Broadcast(Zookeeper 的原子广播协议)。

Zab 借鉴了Paxos算法,是特别为Zookeeper设计的支持崩溃恢复的原子广播协议。基于该协议,Zookeeper 设计为只有一台客户端(Leader)负责处理外部的写事务请求,然后 Leader 客户端将数据同步到其他Follower 节点。即Zookeeper 只有一个Leader 可以发起提案。

Zab 算法的流程有什么?

Zab 协议包括两种基本的模式:消息广播、崩溃恢复。

消息广播

客户端发起一个写操作请求。

Leader服务器将客户端的请求转化为事务Proposal 提案,同时为每个Proposal分配一个全局的ID,即zxid。

Leader服务器为每个Follower服务器分配一个单独的队列,然后将需要广播的Proposal依次放到队列中去,并且根据FIFO策略进行消息发送。

Follower接收到Proposal后,会首先将其以事务日志的方式写入本地磁盘中,写入成功后向Leader反馈一个Ack响应消息。

Leader接收到超过半数以上Follower的Ack响应消息后,即认为消息发送成功,可以发送commit消息。

Leader向所有Follower广播commit消息,同时自身也会完成事务提交。Follower 接收到commit消息后,会将上一条事务提交。

Zookeeper采用Zab协议的核心,就是只要有一台服务器提交了Proposal,就要确保所有的服务器最终都能正确提交Proposal。

崩溃恢复

异常假设

一旦Leader服务器出现崩溃或者由于网络原因导致Leader服务器失去了与过半Follower的联系,那么就会进入崩溃恢复模式。

假设两种服务器异常情况:

  • 假设一个事务在Leader提出之后,Leader挂了。
  • 一个事务在Leader上提交了,并且过半的Follower都响应Ack了,但是Leader在Commit消息发出之前挂了。

Zab协议崩溃恢复要求满足以下两个要求:

  • 确保已经被Leader提交的提案Proposal,必须最终被所有的Follower服务器提交。
  • 确保丢弃已经被Leader提出的,但是没有被提交的Proposal。
Leader选举

崩溃恢复主要包括两部分:Leader选举和数据恢复。

Leader选举:根据上述要求,Zab协议需要保证选举出来的Leader需要满足以下条件:

  • 新选举出来的Leader不能包含未提交的Proposal。即新Leader必须都是已经提交了Proposal的Follower服务器节点。
  • 新选举的Leader节点中含有最大的zxid。这样做的好处是可以避免Leader服务器检查Proposal的提交和丢弃工作。
数据恢复

完成Leader选举后,在正式开始工作之前(接收事务请求,然后提出新的Proposal),Leader服务器会首先确认事务日 志中的所有的Proposal是否已经被集群中过半的服务器Commit。

Leader服务器需要确保所有的Follower服务器能够接收到每一条事务的 Proposal,并且能将所有已经提交的事务Proposal 应用到内存数据中。等到Follower将所有尚未同步的事务Proposal都从Leader服务器上同步过,并且应用到内存数据中以后, Leader才会把该Follower加入到真正可用的Follower列表中。

Gossip 算法

本质是一种扩散感染算法,速度非常快,可以做到1传10,10传100。但是问题就是可能会有间谍伪造消息,导致出现问题。

一致性共识算法与分布式事务的关系是什么?

还在研究中。

参考文档

Raft算法详解

多图详解分布式Raft算法的一致性保证 - 掘金