【RPC】分布式一致性与一致性协议

文章目录

分布式一致性

在CAP、ACID和BASE中都提到了一致性。但是对于一致性的整个定义还是非常模糊的,所以本文会详细介绍一致性的模型,以及目前比较流行的一致性协议。

数据一致性并不只有存在与不存在两种情况,就像可以用0%到100%之间的任意数值来代表可用性的程度一样,一致性也有一些分类。一致性模型按照强弱可以粗略地分为弱一致性模型、最终一致性模型和强一致性模型。

  • 弱一致性模型的特点是向系统更新或者写入一个数值后,后续的读操作不一定能够读到这个最新的数值。
  • 最终一致性模型的特点是向系统更新或者写入一个数值后,后续一段时间内的读操作可能读取不到这个最新的值,但在该时间段过后,一定能够读到最新的数值。最终一致性模型又可细分为:因果一致性、单调一致性和最终一致性
  • 强一致性模型的特点是向系统更新或写入一个数值后,无论何时都能够读到这个最新的数值。强一致性模型又可以细分为两类:线性一致性和顺序一致性

下面就分别介绍一下这几种一致性模型。

1. 线性一致性

线性一致性又称为原子一致性和强一致性。

Linearizability: A Correctness Condition for Concurrent Objects. Maurice P. Herlihy, Jeannette M. Wing. 1987.

如果需要达到线性一致性,则需要满足如下条件:

  • 1)任何一次读操作都可以读到某个数据的最新值。
  • 2)系统中所有的节点内执行的时间顺序都和系统级别时钟下看到的时间顺序一致。

第一点非常容易理解,第二点则约束了两个维度下时间的执行顺序都必须是一样的。这两个维度是指单个进程内的时钟顺序和整个系统的时钟顺序。下面通过一个例子来理解一下。
《【RPC】分布式一致性与一致性协议》

图中有三个进程P1、P2、P3,从P1进程来看,只是执行了一次写操作。从P2和P3进程来看。事件顺序都是先执行一次写操作,然后执行一次读操作。从整个系统的时钟顺序来看,先执行三次写操作,然后执行两次读操作,所以最近一次的值是3,读操作读到的值也就是3。

2. 顺序一致性

上面的例子中五个事件在时间上没有重叠,但是在实际的场景中,不同进程的事件执行的时间点一定会有重叠,如下面的例子:
《【RPC】分布式一致性与一致性协议》

这三个进程在执行三次写操作和三次读操作时的时间点有重叠,从系统级时钟的维度来看,整个事件的执行顺序应该是write1→write2→write3→read2→read3→read2(数字代表进程标识)。如果按照线性一致性的约束,则第一次read2读到的值应该是a=3,因为第一次read前一次的写操作时write3。但是图中的结果却是a=2,这是因为write3操作虽然实在read2操作之前开始执行的,但是write3操作结束的事件却是在第一次read2操作开始之后。从进程P2的视角看,事件执行顺序应该时write2→read2→write3→write1→read2,P2进程和系统级别时钟的视角看到的事件执行顺序是完全不一样的。而这种时间点重叠,并且不满足系统级别时钟的事件执行顺序的一致性成为顺序一致性。

顺序一致性的约束如下:

  • 1)任何一次读操作都可以读到某个数据的最新值,这一点和线性一致性是相同的。
  • 2)任何进程看到的事件顺序是合理的,达成一致即可,并不需要所有进程的时间顺序和系统级时钟下的事件顺序一致,它放宽了对一致性的要求,并不像线性一致性一样严格。

3. 因果一致性

线性一致性和顺序一致性有一个相同的约束,就是任何一次读操作都可以读到某个数据的最新值,这是强一致性的表现。而因果一致性属于最终一致,它的约束如下:

  • 1)对于具有因果关系的读/写事件,所有进程看到的事件顺序必须一致。
  • 2)对于没有因果关系的读/写事件,进程可以以不同的顺序看到这些并发的事件。

4. 单调一致性

单调一致性可以分为单调读一致性和单调写一致性。

单调读一致性指的是任何时刻一旦读到某个数据项在某次更新后的值,就不会再读到比这个值更旧的值。

单调写一致性指的是一个进程对某一个数据项执行的写操作必须在该进程对数据项执行任何后续写操作之前完成。

相较于因果一致性,单调一致性更聚焦于单个进程内的读/写操作顺序。

5. 最终一致性

因果一致性和单调一致性都属于最终一致性中的一种,只是在最终一致性的基础上增加了一致性的强度,因果一致性是在最终一致性的基础上增加了因果关系的约束,单调一致性是在最终一致性的基础上增加了单进程内的约束。而没有增加其他约束的最终一致性也就是字面上的最终一致性的意思,只有一个约束,就是向系统写入更新或者写入一个数值,后续一段时间内的读操作可能读取不到这个最新的值,但是在该时间段过后,一定能够读到这个最新的数值。

对比以上几个一致性模型的约束条件,可以发现它们之间也有一定的强弱之分,由弱到强,分别是最终一致性、单调一致性、因果一致性、顺序一致性、线性一致性。

一致性模型是理论总结,一致性协议则是一致性模型的具体表现形式。与之经常混淆的是共识算法。共识和一致性描述的内容并不相同,共识用来描述一组进程间的协作过程,并且确定下一个操作,而一致性描述的是进程间某一时刻的状态。共识和一致性相比,共识的概念更狭隘一些,因为达成共识就可以达到一致性。但是需要达到一致性,并不一定需要达成共识,而一致性有强弱之分,共识算法是一致性协议的一种具体实现手段。

一致性协议

1. Paxos算法

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

在分布式系统中,只能减少却无法避免进程挂掉、进程重启、通信消息丢失、延迟等情况,Paxos算法实现的就是在发生这些异常时,不会破坏分布式系统决议的一致性。一些进程可以提出各种请求,最终只有一个请求会被选中,只要有一个请求被选中,那么其他进程都能获得该请求带来的变化。

在Paxos算法中有三类角色,分别是

  • Acceptor(决策者):用于决策最终选用哪个决议。
  • Proposer(提议者):该角色的职责为向决策者提出提议。
  • Learner(最终决策执行者):该角色的职责是执行最终选定的提议。

从提议的提出到提议的选定,再到提议的执行,可以大致分为两个阶段,第一个阶段就是决策者选出一个最终的提议(其实是Proposer与Acceptor之间的交互),第二阶段就是最终决策执行者如何获取并执行该提议。

由于Paxos算法是一套偏向理论的算法,难以理解且实现,所以本文就不展开介绍细节了。

2. Raft算法

Raft是一种用来管理复制状态机的算法,复制状态机通常使用复制日志实现,Raft也不例外。每个服务器存储一个包含一系列命令的日志,其状态机按顺序执行日志中的命令。每个日志中的命令都相同且顺序也一样,因此只要处理相同的命令序列,就能得到相同的状态和相同的输出序列。这也是Raft实现一致性的核心思想。

Raft算法有三种角色:

  • Leader(领袖):该角色的职责是接受和处理一切请求,并同步数据给Follower。
  • Follower(群众):该角色的职责是转发请求给Leader,接受Leader的同步数据,以及参与投票。
  • Candidate(候选人):该角色的职责是竞选Leader。

这三种角色分别代表服务节点的三种状态,这三种状态之间可以互相转化。

对于raft角色的转化,在官网有一个动画,可以自己设置各节点的状态:https://raft.github.io/

《【RPC】分布式一致性与一致性协议》

从图中可以看出,集群最初的状态——所有服务器都是Follower,当这些服务启动完成后,由于起初没有Leader,所以Follower一定不会收到Leader的心跳信息。经过一段时间后,发生选举,此时Follower先增加自己的当前任期号并且转换到Candidate身份,然后投票给自己并且并行地向集群中的其他服务器节点发送竞选请求。

当满足以下三个条件中的一个时,Candidate身份会发生转变:

  • 集群内超过半数的服务节点同意该 Candidate 成为 Leader,也就是超过半数的节点响应了竞选请求,此时 Candidate 会变成 Leader。(这是对图中“receives votes from majority of servers”的解释)
  • 集群内其他的某个服务器节点已经成为 Leader,此时 Candidate 会变回 Follower。 因为当 Leader 产生后,它会向其他的服务器节点发送心跳消息来确定自己的地位并阻止新的选举。(这是对图中“discovers current leader or new term”的解释)
  • 如果有多个 Follower 同时成为 Candidate,那么选票可能会被瓜分,以至于没有Candidate 赢得过半的投票,也就是选举超时后还是没有选出 Leader,会通过增加当前任期号来开始一轮新的选举,但是这种情况有可能无限重复(这是对图中“times out, new election”的解释)。为了防止这种情况发生,Raft 算法使用超机选举超时时间的方法来确保很少发生选票瓜分的情况。也就是每个 Candidate 在开始一次选举的时候重置一个随机的选举超时时间,然后一直等待直到选举超时。该 Candidate 会增加当前的任期号,重新发起竞选请求,此时其他 Candidate 可能还在等待中,那么其他服务节点认为该超时的 Candidate 的任期号最大,所以它会被选为 Leader。

还有一种从Leader直接变成Follower的情况,这种情况多数出现在Leader发生了网络分区的时候。当Leader发生网络分区后恢复时,新的Leader已经产生,它会接受新Leader的心跳请求,发现新的Leader的任期号比自己的大,它会自动变成Follower。 而旧的Leader如果发送心跳请求给其他服务器节点时,Candidate和Follower都会对比任期号,如果任期号小于自己的任期号,则直接拒绝此次心跳请求。

    原文作者:牧心.
    原文地址: https://blog.csdn.net/Aibiabcheng/article/details/125633319
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞