分布式一致性协议

背景

在常见的分布式系统中,总会发生诸如机器宕机或网络异常(包括消息的延迟、丢失、重复、乱序,还有网络分区)等情况。

一致性算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性

CAP理论

CAP 理论告诉我们,一个分布式系统不可能同时满足一致性(C:Consistency),可用性(A: Availability)和分区容错性(P:Partition tolerance)这三个基本需求,最多只能同时满足其中的2个。

选项

描述

C(Consistency)

分布式环境中,一致性是指数据在多个副本之间是否能够保持一致的特性。

A (Availability)

可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果

P (Partition tolerance)

分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障

2PC

Two-Phase Commit,事务的提交过程分成了两个阶段来进行处理。

2PC阶段一

1.事务询问

协调者向所有的参与者询问,是否准备好了执行事务,并开始等待各参与者的响应。

1.执行事务

各参与者节点执行事务操作,并将 Undo 和 Redo 信息记入事务日志中

1.各参与者向协调者反馈事务询问的响应

如果参与者成功执行了事务操作,那么就反馈给协调者 Yes 响应,表示事务可以执行;如果参与者没有成功执行事务,就返回 No 给协调者,表示事务不可以执行。

2PC 阶段二

在阶段二中,会根据阶段一的投票结果执行 2 种操作:执行事务提交,中断事务。

执行事务提交步骤如下:

•发送提交请求:协调者向所有参与者发出 commit 请求。

•事务提交:参与者收到 commit 请求后,会正式执行事务提交操作,并在完成提交之后释放整个事务执行期间占用的事务资源。

•反馈事务提交结果:参与者在完成事务提交之后,向协调者发送 Ack 信息。

•协调者接收到所有参与者反馈的 Ack 信息后,完成事务。

中断事务步骤如下:

•发送回滚请求:协调者向所有参与者发出 Rollback 请求。

•事务回滚:参与者接收到 Rollback 请求后,会利用其在阶段一种记录的 Undo 信息来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。

•反馈事务回滚结果:参与者在完成事务回滚之后,想协调者发送 Ack 信息。

•中断事务:协调者接收到所有参与者反馈的 Ack 信息后,完成事务中断。

问题

1、同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。

2、单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。

3PC

三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。与两阶段提交不同的是,三阶段提交有两个改动点。

•引入超时机制。同时在协调者和参与者中都引入超时机制。

•在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。

也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

2PC 与3PC 区别

1、引入超时机制。同时在协调者和参与者中都引入超时机制。

2、三阶段在2PC的第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。

3 pc比t2pc多了—个can commit阶段,减少了不必要的资源浪费。因为2pC在第一阶段会占用资源,而3pc在这

个阶段不占用资源,只是校验一下sql,如果不能执行,就直接返回,减少了资源占用。

引入超时机制。同时在协调者和参与者中都引1入超时机制。

2pc:只有协调者有超时机制,超时后,发送回滚指令。

3pc:协调者和参与者都有超时机制。

协调者超时:can commit, pre commit中,如果收不到参与者的反馈,则协调者向参与者 发送中断指令。

参与者超时:pre commit 阶段,参与者进行中断;do commit 阶段,参与者进行提交。

缺点

数据不一致问题仍然是存在的,比如第三阶段协调者发出了 abort 请求,然后有些参与者没有收到 abort,那么就会自动 commit,造成数据不一致。

Raft协议

raft算法是对Paxos算法的简化设计和优化,毕竟世界上只有一种共识算法,那就是Paxos。但是该算法以复杂难以理解和难以实现闻名,Raft算法的提出者也说了他们花了很长时间来理解Paxos,觉得过于复杂所以才研究出了Raft算法。主要的是将复杂问题分治。

  • Leader Election
  • Log Replicated
  • Safety
  • Membership Management
  • Log Compaction

在Raft算法中,作者将一致性问题看成系统中进程间(在不同机器上)一个状态机同步问题,为保证每个机器上的进程具有一致性(即状态改变的过程一样,以此保证数据的一致),作者使用了Raft这个共识算法来保证在每个进程初始状态相同的情况下,每个进程的下一个操作(状态改变操作)相同,这样就能保证所有进程的状态机的改变过程是一样的,即系统中的进程最终会达成(数据)一致状态。而日志(log)就是raft共识算法对对每个操作(提案)的一种建模。日志在集群中的复制过程,就是共识形成的过程。

基于状态机实现的日志复制状态机(Log Replicated State Machine)则是分布式共识算法 一致性实现的基础。

模拟这个复制状态机集群 处理客户端请求的过程 可以分为五步:

  1. 客户端将请求 x<–z 发送个leader上的共识模块
  2. leader的共识模块将客户端请求转发给follower上的共识模块,并保证收到大多数(quroum,大于等于n/2 + 1,n表示集群节点个数)的回应。
  3. 每个server的复制状态机将各自共识模块介些的log entry追加到自己本地的log列表中
  4. 每个server的复制状态机并行提交log entry中的日志条目到状态机中 执行日志中的指令,比如将x设置为z。这一步可以看作是commit阶段,需要持久化。
  5. 完成了commit 之后,leader向客户大返回成功。

当然这个复制状态机并不能代表完整的raft的日志复制的过程,一些日志复制的细节上会有差异,但是整个日志复制到完成提交的过程是 raft Log Replicated的基础。

Leader Election

  • follower ,所有角色开始时的状态,等待接受leader心跳RPCs,如果收不到则会变成Candidate
  • Candidate,候选人。是变成Leader的上一个角色,候选人会向其他所有节点发送RequestVote RPCs,如果收到集群大多数的回复,则会将自己角色变更为Leader,并发送AppendEntries RPCs。
  • Leader raft能够保证每一个集群仅有一个leader。负责和客户端进行通信,并将客户端请求转发给集群其他成员。

Election Timeout 选举超时时间。即Cadidate 向集群其他节点发送vote请求时,如果在Election Timeout时间内没有收到大多数的回复,则会重新发送vote rpc。 一般这个超时时间是在150-300ms的随机时间。

Term:

简单来说Term 可以用一个数字来表示,主要提现的是raft集群Leader的存活周期。即当前Term为1,维持了一段时间,这段时间集群的leader没有发生变化。而当Term变成2,表示一定发生了Leader的变更(leader所在服务器跪了),但不一定表示Leader一定被选举出来了。

正常选举

由于未收到 Leader 的心跳,所有节点都进行倒计时,由于每个节点的倒计时时长不一样,先倒计时完成的节点变成 Candidate,并开启一个新的 Term 1。

Candidate 首先会投自己一票,并向其他节点发送投票请求,由于在这个 Term 内其他节点都未投票,所以会投票给请求的 Candidate。

Candidate 节点收到相应后就变成了 Leader 节点。

 

在变成 Leader 节点后,会周期性的发送心跳 Heartbeat 给 Follower 节点,Follower 节点在收到 Heartbeat 会重置自身的倒计时时间。这个 Term 会一直持续到某个节点收不到 Heartbeat 变成 Candidate 才会结束。

Leader 节点故障

当 Leader 节点故障时候,Follower 节点不再收到 Heartbeat ,自然也无法重置自身的超时时间。某个节点倒计时结束后,变成 Candidate 节点,Term 也变成2,同时会向其他2个节点发送投票请求。虽然只能收到一个节点的返回,由于自己也会投自己一票,所以依然能形成“多数派”,也可以正常变成 Leader 节点,领导整个集群。

平票处理

当某个集群有如图四个节点时候,如果有两个 Follower 倒计时同时结束(这是时候 Term 是一样的),都变为 Candidate 并发起投票。由于在一个 Term 内每个节点只可以投一票,而另外两个节点又分别投票给两个节点,这样两个 Candidate 节点都获得2票(包含自己投自己),就产生了“平票”情况。

这个时候就重新启动倒计时,重新开始一个新的 Term 。如果依然有两个节点同时变成 Candidate 并且产生平票,将重复上面的过程。直至某个 Term 内某个 Candidate 节点获得“大多数”投票,变成 Leader 节点。

在之前的描述过程中基本选举已有有一些描述了,这里通过角色变化图展示一下。

follower 没有收到heartbead 变成Candidate

Candiate 投票给自己,并发送投票请求给其他server ,收到大多数的相应则变成leader,否则等待超时再次变成Candidate 发送投票 或 接受其他的投票。

Leader 发送AppendEntries 复制日志到其他follower;维护心跳来保证自己被其他follower可见。心跳超时或发现更高的Term 就变成follower。

预选举

网络分区后,节点少的一个分区,由于接收不到leader的心跳,它们会自己开始选举,但由于人数过少不能成功选举出leader,所以会一直尝试竞选。它们的term可能会一直增加到一个危险的值。但分区结束时,通过心跳回应,原leader知道了集群中存在一群Term如此大的节点,它会修改自己的Term到已知的最大值,然后变为follower。此时集群中没有leader,需要重新选举。但是那几个原先被隔离的节点可能由于日志较为落后,而无法获得选举,新leader仍然会在原来的正常工作的那一部分节点中产生。

虽说最终结果没错,但是选举阶段会使得集群暂时无法正常工作,应该尽量避免这种扰乱集群正常工作的情况的发生,所以raft存在一种预选举的机制。真正的选举尽管失败还是会增加自己的Term,预选举失败后会将term恢复原样。只有预选举获得成功的candidate才能开始真正的选举。这样就可以避免不必要的term持续增加的情况发生。

Log Replicated

正常情况

Leader 节点接收到客户端请求,Leader 节点将变化写入自己的 Log,然后这个变化发送给 Follower 节点。

Leader 节点在收到 Follower 节点返回后会执行提交操作(需要集群内多数节点的回应),达成共识后真正的数据变化,并返回客户端操作结果。

在下次心跳时候 Leader 会告知 Follower 执行提交操作。

同样当客户端又发送了一个“ADD 2”请求,那么会继续一遍上述的过程

产生网络分区

“网络分区”是指某一个时间内,集群内的节点由于物理网络隔离,被分成了两分区,这两分区内的节点可以互通,而两个分区之间是访问不通的。

如下图所示,A、B节点被分到一个分区,C、D、E节点被分到另外一个分区,这两个分区之间无法连通。这时候C、D、E节点由于接收不到 Leader 节点的 Heartbeat,就会产生“Leader Election”,由于C、D、E所处的分区有集群的大多数节点,所以可以顺利的选举出一个新的 Leader , Term+1。

这时候出现两个客户端,其中一个客户端向集群发送请求,并连接到了原先的 Leader-B 上。Leader-B 虽然可以正常接收请求,也会将日志复制到其他节点。但由于无法获得“多数”节点的响应,所以日志无法提交,也无法给客户端反馈。

另外一个客户端也向集群发送写请求,并连接到新选举的 Leade-E 上。可以看到 Leader-E 由于可以获得“多数”节点的响应,所以可以正常的进行日志提交及客户端反馈。

当“网络分区”恢复的时候,集群中现在的两个 Leader 将都向集群内所有节点发送 Heartbeat,由于 Leader-E 的 Term+1后,比 Leader-B 的 Term 更新。根据约定,Leader-E 将成为整个集群的 Leader,A、B 节点成为 Follower,回滚未提交的日志,并同步新 Leader 的日志。

如上图,其中每一个角色都有自己的log entry列表,列表中的每一个方格代表一条entry。

方格中有一个数字1、2、3,一条entry x<–3、y<–1,数字代表的是Term,entry是能被状态机执行的commnad。

最上面有一排数字:1、2、3… 表示entry索引,是entry的全局唯一标识。

从这个日志列表中,我们能够看到通过index + term 能够唯一标识一条entry,且整个集群正常工作的成员的entry和index需要保持一致,上图中存在部分节点出现过异常导致现在的entry出现不一致的情况,这个时候leader会负责完成Log Replicated 来将follower的entry补全。

  • 提案阶段:是一个初始阶段,当leader收到来自客户端的一条请求后,会将请求打包成为一个entry,该entry便处于此阶段。它是不稳定的,也就是说集群没有办法保证此entry会被集群接受还是抛弃(网络、机器原因),需要raft共识算法发挥其作用来确定。
  • 达成共识阶段/可提交阶段:一旦某一entry被集群内大多数节点所持有,该entry就已经处于达成共识阶段,它已经能够确定将会被集群接受(提交),但具体何时写入状态机,外部客户端何时能够验证此命令已经生效,则是没有办法保证的(网络延迟、机器故障都会影响)这是一个基于上帝视角观测到的状态(代码实现无法判断此状态)。或者称之为“可提交阶段”,为了与“被提交阶段”对比明显,下面仍然称之为“达成共识阶段”。
  • 被提交阶段:可认为是显式的达成共识阶段。当leader意识到某一个entry已经进入了达成共识阶段,则leader将会将它标记为被提交状态。并将此信息广播给集群中的其他节点声明该entry已经被集群接受,可以将其应用进状态机了。需要注意的是,每个节点对于哪些entry是被提交的可能存在认知上的不同步(但绝不会冲突),我们这里认为当leader标记其已被提交,那么该entry就进入了被提交阶段。
  • 被应用状态:当集群中的任意节点意识到某一个entry已经被标记为已提交,而且自身也持有这个entry,就会将其应用进状态机,对于KV数据库可能是写、删除操作等,此时一个请求才真正被完成,可以被外部验证其已被执行。

Safety

  • 选举安全:在一次任期内最多只有一个领导者被选出
  • leader 只添加操作:领导者在其日志中只添加新条目,不覆盖删除条目
  • 日志匹配:如果两个log包含拥有相同索引和任期的条目,那么这两个log从之前到给定索引处的所有日志条目都是相同的
  • leader完整性:如果在给定的任期中提交了一个日志条目,那么该entry将出现在所有后续更高任期号的领导者的日志中
  • 状态机安全性: 如果服务器已将给定索引处的日志条目应用于其状态机,则其他服务器不能在该索引位置应用别的日志条目

Membership Management

成员变更

多数派

基于前面的介绍,我们能够意识到一个重要的概念贯穿着领导选举和日志复制两个部 分,那就是“多数派”的概念。

  • 选举要经过大多数人同意
  • 被大多数节点所持有的日志才能进入“达成共识阶段”

联合共识

那么对于如此重要的一个属性,raft如何保证整个集群任意时刻都能持有一个相同的”大多数“的具体数值呢?相当于说,集群成员能否时刻知晓正确的集群成员数量?

对于一个成员不变的集群,“大多数”的数量是固定不变的。 但是当集群成员数量发生改变,特别是增加成员时,对于多数派的认知,集群的不同成员之间可能会发生分歧。

脑裂

1 一个最简单的解决方法就是,每次只允许最多一个新节点加入集群。当原集群的多数派成员的接受此次变更后,才允许下一个新节点的加入。

2 联合共识解决一次加入多个节点的情况。新旧配置表。一个节点当选既要得到新多数派支持也要得到旧的多数派支持。

Log Compaction

长时间运作的集群,其内部的日志会不断增长。因此需要对其进行压缩,并持久化保存。对于新加入集群的节点,可以直接将压缩好的日志(称之为快照Snapshot)发送过去即可。

对于任何一个通过日志来实现一致性的协议来说, 在实际工程化时都会面临日志不断增长的问题, 一方面大量的日志占用了太多的存储空间, 增加了存储成本; 另一方面, 当日志特别长时, 新加入集群的结点将需要非常长的时间来重放日志, 为了解决问题, raft 采用了快照的方式, 实际上我们并不需要历史的所有时序数据, 而只需要保证当前时刻的状态机的状态是一致的即可, 因此历史的指令没有必要全部存储, raft 的快照可以近似用如下图示来说明

索引为 1~5 的日志项被创建为了一个快照, 快照中保存了状态机最后的状态, 同时也保存了最后一个日志项的索引和任期号, 显然, 应用这个快照和按序重放索引为 1~5 的日志项后, 状态机的状态是完全一致的, 当日志特别长时, 快照可大大降低存储空间, raft 协议规定生成快照的日志项必须是已提交过的, 集群中的各个结点自主生成已提交的日志项的快照, 领导人每隔一段时间也会将自己的快照广播发送给集群中的所有其它结点, 其它结点收到快照后会根据快照中说明的索引检查自己是否已含有此快照中的全部信息, 如果没有, 则删除自己全部的日志项, 然后应用这个快照, 若快照的最大索引小于自身的, 则结点简单地将快照对应的索引之前的所有日志项删除, 并应用这个快照, 而自身原有的在此索引之后的日志项仍然保留, 这样以来, 即便是新加入集群的结点, 也可以使用快照快速达到与领导人的同步。

除了后续的成员变更和日志合并之外。整个raft实现的共识算法 基本过程也就两个阶段:Leader Election, Log Replicated。

在这两个阶段基础上通过两种一致性 完成了异常情况下的集群数据一致性保障。

raft是一个强一致、高可用的共识算法。即使网络分区发生,只要由半数以上的节点处于同一个分区(假设60%),那么系统就能提供60%的服务,并且还能保证是强一致。

Raft

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值