程序员潇然 发表于 2022-12-1 20:10:01

分布式系统非拜占庭错误故障错误算法paxos 算法

拜占庭将军问题中,提出分为两种解决问题的方向

`https://www.crazybytex.com/thread-239-1-1.html`

一种是非拜占庭错误,一种是拜占庭错误。

把出现故障( crash 或 fail-stop,即不响应)但不会伪造信息的情况称为“非拜占庭错误”( non-byzantine fault)或“故障错误”( Crash Fault);

Paxos就是一种非拜占庭错误解决算法。

**The part-time parliament**

`https://dl.acm.org/doi/10.1145/279227.279229`

中文版 `https://www.crazybytex.com/thread-243-1-1.html`

最近在Paxos岛上的考古发现表明,尽管兼职的立法者到处流动,但议会仍在运作。

尽管议员们经常突然闯入参加会议,而且他们的信使也很健忘,但议员们仍然保持着议会记录的一致副本。

Paxon议会的协议提供了一种实现分布式系统设计的状态机的新方法。

类比到分布式系统中:

**议员->节点**

**议员游走流动/闯入参会->节点的上线/下线**

**信使靠谱/健忘->网络的联通/失效**

**议会记录一致->分布式系统达成共识**

关于这段论文发表的历史比较有趣,lamport在下面的网址中有描述

`http://lamport.azurewebsites.net/pubs/pubs.html#lamport-paxos`

> 为了进一步展示形象,我以印第安纳-琼斯式考古学家的身份做了几次讲座,戴着斯泰森帽子和臀部烧瓶。
>
> 我试图在这个主题中插入一些幽默,但失败了。参加我讲座的人记得印第安纳琼斯,但不记得算法。
>
> 1990年向TOCS提交了这篇论文。三位审稿人都说这篇论文有点有趣,虽然不是很重要,但所有Paxos的东西都必须删除。我对在这个领域工作的每个人似乎都很不幽默感到非常恼火,所以我没有对论文做任何事情。
>
> This paper won an ACM SIGOPS Hall of Fame Award in 2012.

另外lamport 在下面网址中说道:

`http://lamport.azurewebsites.net/pubs/pubs.html#paxos-simple`

在2001年的PODC会议上,我厌倦了每个人都说理解Paxos算法是多么困难,发表于。尽管人们对伪希腊语的名字非常着迷,可他们认为论文很难理解,但算法本身非常简单。

所以,我在会议上逮到几个人,口头向他们解释了算法,没有论文。回到家后,我把解释写成了一张简短的纸条,后来我根据弗雷德·施耐德和巴特勒·兰普森的评论对其进行了修改。当前版本长达13页,其中没有比n1>n2更复杂的公式。

2015年,亚马逊的Michael Deardeuff告诉我,这篇论文中的一句话是模棱两可的,错误的解释理解会导致错误的算法。

Deardeuff发现Github上的许多Paxos实现实现了这种不正确的算法。

显然,实现者没有费心阅读中算法的精确描述。我不会消除这种歧义,也不会揭示它在哪里。散文不是精确描述算法的方法。不要试图实现本文中的算法。改用。

指的就是**The part-time parliament**

**paxos-made-simple**

`https://www.microsoft.com/en-us/research/publication/paxos-made-simple/?from=https://research.microsoft.com/en-us/um/people/lamport/pubs/paxos-simple.pdf&type=exact`

简单说大神觉得这个事情很简单,你们怎么这么笨?

于是出了一个解释性的文档,当然,他提出还是需要**The part-time parliament**按照来实现算法,而不是这篇simple。

!(data/attachment/forum/202212/02/110826blkc59czh51cbwbl.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

这大概也就是为什么这一篇的Abstract为什么只有一句话的原因吧

**paxos made simple** 翻译 中文版 `https://www.crazybytex.com/thread-242-1-1.html`

关于paxos发展历史,详见转载的一篇文章:`https://www.crazybytex.com/thread-241-1-1.html`

**paxos made simple** 是对**The part-time parliament**的解释说明,他们并不是两回事,相对来说simple更加容易理解一些,尽管仍旧是不太容易理解

Raft作者讲解Paxos:**《Implementing Replicated Logswith Paxos》**

ppt内容地址:

`https://gitee.com/crazybytex/some-documents/blob/master/implementing%20replicated%20logs%20with%20paxos.pdf`

在线讲解:

`https://www.youtube.com/watch?v=JEpsBg0AO6o&t=41s`

`https://www.bilibili.com/video/av36556594/?vd_source=7741be735669cf4a8d044c6942e6c0e3`

**Fast Paxos**

在到后面,lamport提出了改进的算法,提出了fast paxos ,之前的paxos就被称之为classic paxos

这个算法就是字面意思,改进了的,更快速的paxos

`https://www.microsoft.com/en-us/research/publication/fast-paxos/`

!(data/attachment/forum/202212/06/145255ggknrinorzn4nktn.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

下面介绍的是经典的paxos

## Paxos算法

以上是一些背景说明、文献指引记录,下面开始简单分析算法。

论文提出的算法可以认为是包含两个部分:

Basic Paxos 算法,描述的是多节点之间如何就某个值(提案 Value)达成共识;

Multi-Paxos 思想,描述的是执行多个 Basic Paxos 实例,就一系列值达成共识。

Basic Paxos 是 Multi-Paxos 思想的核心,本文先介绍Basic Paxos。

## Basic Paxos

### 需求

1. Only a value that has been proposed may be chosen,
2. Only a single value is chosen, and
3. A process never learns that a value has been chosen unless it actuallyhas been.

以上是paxos made simple中关于达成共识的要求。

为了达成这几个目标,定义了几个角色,配合来执行

### 角色

在 Basic Paxos 中,有提议者(Proposer)、接受者(Acceptor)、学习者(Learner)三种角色

提议者(Proposer):提议一个值(Proposal),用于投票表决,Proposal信息包括提案编号 (Proposal ID) 和提议的值 (Value)。

接受者(Acceptor):对每个被提议的值进行投票(accept),并保存接受的值,也就是参与决策,回应Proposers的提案。收到Proposal后可以接受提案,若Proposal获得多数Acceptors的接受,则称该Proposal被批准。

学习者(Learner):被告知投票的结果,接受达成共识的值,存储保存,不参与投票的过程,也就是不参与决策,从Proposers/Acceptors学习最新达成一致的提案(Value)

!(data/attachment/forum/202212/03/150713e3tscb1uggngn15s.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

这是一种比较严格、明确的划分,但是在实际的分布式系统中,通常每一个节点都将是提案者、接受者、学习者,因为他们都有可能接收来自外部用户客户端的请求,此时他就是提案者,然后他也需要参与接受者的过程,最终达成一致后,每个人也都是学习者,都要知悉最终的结论,并且存储这些数据。

### 单个接受者

核心目标是达成共识,那么最简单的无脑处理方法就是简化为只有一个接受者

这个唯一的接受者,根据他自己的处理逻辑,比如选择收到的第一个请求,作为最终一致的一个值,也是可以实现核心目标的。

!(data/attachment/forum/202212/03/162653cpssbhgoyofojh3o.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

但是很显然,如果只有一个接受者Acceptor ,一旦这个唯一单机,将会使系统不可用,对于分布式系统来说是灾难性的。

解决方法就必须是多接受者,Multiple acceptors

这样只要大多数接受者可以达成一致,最终一个值被选择出来(chosen),单节点的宕机就不会影响整个系统。

### 必须接受收到的第一个值

假设可以不接受第一个值,那么如果只有一个Proposer时,这个唯一的proposal如果可以不接受,那么后面将不再有任何Proposer进行提议,那么永远无法达成共识,为了满足这种场景下可以继续达成共识,必须接受第一个值。

### 都必须可以接受多个值

基于“必须接受收到的第一个值”,假设每个acceptor仅仅接受他收到的第一个值,如果有三个proposer,分别提出了三个提议,发给了三个acceptor,那么因为他们仅仅接受收到的第一个值,不接受别的值,无法达成一致,所以,每个acceptor都还可以接受多个值,就是选你也可以,选他也可以。

### 【编号+值】对提议进行编号

如果根据到达acceptor的先后顺序做处理:先到的被接受,后面的都丢弃,那此时acceptor只能接受一个提议;
如果后到的都被接受,那么当某value被选定后,其他proposer提出新value,选定的value就会被覆盖,导致由选定进入不选定状态,甚至进入下个value的选定状态,违反了一致性。

如果根据value的大小做处理:由于acceptor要能接受多个提案,提案可能存在被覆盖的情况,因此要保证可能被选定的value在被其他提案覆盖的情况下,仍旧能够记录保留
另外假设经历多轮选举,对于同一个proposor发送的多个提案,如何区分他的轮次?

因此有了提案=全局唯一编号+value的设计,通过编号的偏序关系定义acceptor处理提案的方式。

简单说一个有序的不重复的自然数序列作为编号对值进行标记,可以解决很多麻烦。

### 所有被选择的具有相同的值

Only a single value is chosen(只有一个值被选择),而且每个acceptor都可以接受多个值。

那么必须要保证所有的被选择的proposals 是同样的值(we must guarantee that all chosen proposals have the same value),如果大家选择的五花八门,就没有办法保证最终能够达成一致。

By induction on the proposalnumber, it suffices to guarantee:
P2. If a proposal with value v is chosen, then every higher-numbered proposal that is chosen has value v.

通过归纳法,如果一个proposal值v被选择,那么每一个更高序号的被选择的proposal 他的值也是v,就可以保障“所有被选择的具有相同的值”。

又因为被选择的,肯定是要被接受的,被接受的肯定是要被提出来的

所以:

P2b. If a proposal with value v is chosen, then every higher-numbered proposal issued by any proposer has value v.

也就是如果一个proposal值v被选择,那么每一个,被任意proposer提出的,一个更高编号的proposal的值是v。

### 图解

上面的一个简单的推导过程图解如下,**右键新标签查看大图**

!(data/attachment/forum/202212/06/153605sod0mgevoos4wsu4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

### 如何满足?

关于P2b如何满足?

也就是上图的最下面的这个要求

#### 数学归纳法

!(data/attachment/forum/202212/06/152140icumbyac5m5ag0y6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

#### 证明

**命题**:假设某个提议{m, v}被选择,任何编号n>m的提议的值都是v。

①设(m,v) 是被选中的,提议的值是v
②假设编号从m到n-1的每个提议值都是v(i..j 表示从i到j的所有整数的集合),如果可以由此推得,m=n时,提议的值是v,那么命题成立。

**过程:**

对于已被选择(chosen)的编号为m的提议,必定存在某集合C,由接受了这个提议的acceptor多数派组成。
也就是说C中的每一个acceptor 都接受了编号为m..(n-1)中的一个提议(因为是C就是由接受了v的多数acceptor组成的),又依据假设②,被任意一个acceptor接受的编号为m..(n-1)的提议,他们的值都是v。

而且,任何一个多数派集合S,都必然包含一个元素属于C,因为任意两个多数派集合必然有交集,否则两个多数派并集就超出了原有集合了,
如果能够满足下面的条件,就可以保障编号n的提议,他的值也是v

**对于任何值v 和n ,如果一个 proposal具有编号n 和值v ,那么由多数派 acceptor组成的集合S满足以下其中一个条件**:

* S 中的 acceptor不会接受任何编号小于 n 的proposal。
* 所有编号小于n并被S集合中的所有acceptor接受的提议中,v是编号最大的提议的值,也就是S中 acceptor接受的最大编号的 proposal的值为 v 。

### 两阶段提交

两阶段提交-通过两个阶段来完成一次提交动作。

直白的说就是开始干活之前,有一个确认的过程,先问一下是否准备妥当,大家都准备好了之后,就进行下一步;否则直接失败

!(data/attachment/forum/202212/03/155057ka1aovbxm9vyzbm9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

类比到paxos算法,协调者就是提议者proposer,参与者就是接收者acceptor

### 解决过程

**对于任何值v 和n ,如果一个 proposal具有编号n 和值v ,那么由多数派 acceptor组成的集合S满足以下其中一个条件**:

* S 中的 acceptor不会接受任何编号小于 n 的proposal。
* 所有编号小于n并被S集合中的所有acceptor接受的提议中,v是编号最大的提议的值,也就是S中 acceptor接受的最大编号的 proposal的值为 v 。

关于这个条件,也就是论文中的P2 `<sub>c</sub>`

如何保证?

一个proposer想要提出一个编号为n的proposal,就必须要知道目前所有的编号小于n的最高编号的proposal,这是很容易的,但是接下来的未来是很难预测的,所以无法预测,就直接拒绝,也就是不接受任何编号小于n的proposal

所以可以提出如下推导逻辑,这个就是proposer的算法处理逻辑。

!(data/attachment/forum/202212/06/154016gi5azz694jdzi4id.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

#### prepare

一个 proposer选择一个新的编号为n 的 proposal,并发送请求到 acceptor的集合,并要求得到以下其中一个回应:

一个不会接受编号值小于n 的 proposal的承诺。
如果 acceptor已经接受过 proposal,则响应已接受的小于编号n 的最大编号的 proposal(如果有的话)。
将该请求称为编号为n 的prepare请求。

#### accept

如果proposer接受到了大多数的acceptor响应的请求,然后他就可以提出来一个proposal,编号为n,并且选择这些响应中编号最大的proposal中的值v,作为值v,如果没有人响应值,他就可以自己选一个其他的值。然后将这一次提议发送给acceptor集合,这就是accept请求。

上面是proposer的处理逻辑,对于acceptor,acceptor可以从 proposer接收两种类型的请求:

prepare和 accept请求。

acceptor如果不响应任何请求,并不会带来安全性的问题(最开始提出来的需要解决的问题,只有一个可以被选中那三条)

所以只需要说明,需要响应的时候的场景。

对于prepare请求,他总是可以响应的。

他也可以响应accept请求,接受这个提议,如果他没有承诺不接受(他是承诺不会接受编号值小于n 的 proposal),换句话说:

acceptor没有响应过编号大于n 的 prepare请求的话,那么可以接收一个编号为n 的 proposal。

假设 acceptor收到编号为n 的 prepare请求,但是已经响应过一个编号值大于n 的 prepare请求。

因此承诺不会接受任何编号为n 的新的 proposal请求。(不接受编号更小的)

这样,acceptor就没有理由响应新的 prepare请求,因为它不会接受 proposer想要发出的编号为n 的 proposal。

因此,我们让 acceptor忽略这样的 prepare请求。

我们也可以忽略它已经接受的 proposal的 prepare请求。
通过这种优化

接受者只需要记住它曾经接受的编号最高的 proposal以及它已响应的编号最高的 prepare请求的数量。

最终归结为两个阶段:

!(data/attachment/forum/202212/06/170046dedy2c88p6rar6yv.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

下图来自John Ousterhunt的ppt,一个更形象的描述。

`https://gitee.com/crazybytex/some-documents/blob/master/implementing%20replicated%20logs%20with%20paxos.pdf`

!(data/attachment/forum/202212/06/163437ke7o44kb7kkb4lik.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

比如这样一个场景

!(data/attachment/forum/202212/06/174834hmptqss7tgujstj7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

**首先是prepare阶段**

初始时,P1、P2 进行广播发送(初始时是没有值的,这个值是选出来或者最后自己定的)

横向的是时间轴,靠右表示时间的推移

!(data/attachment/forum/202212/06/174738e5db7tuxe507te6d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

在每个节点第一次收到消息的时候,会接受消息,并且会返回当前没有选择的提议

A1、A2 第一次收到消息来自于P1,A3 第一次收到消息来自于P2

!(data/attachment/forum/202212/06/175022jyosl45i22yui555.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

对于非第一次收到prepare消息,也就是A1、A2 收到P2,A3 收到P1 的时候

因为A1,A2 目前也都还没有通过任何提议,而且,编号7 大于编号3,所以她给P2 也会返回未接受任何提议的响应;

但是对于A3 收到P1的请求时,之前已经响应过A2的编号为5的prepare请求了,就不会响应这个P1的编号为3的请求了

!(data/attachment/forum/202212/06/175328tczssboiybbrcuqf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

**接来来是accept阶段**

此时,针对于P1,他已经收到了A1,A2(大多数)的未接受任何提议的响应了,所以他准备发送accept

针对于P2,他也收到了A1,A2,A3(大多数)的未接受任何提议的响应了,所以他准备发送accept

因为之前的节点都没有返回给P1和P2 他们有接受的提议,所以没有值,这个值就由P1,P2各自定

注意

虽然下图时间轴画的比较靠前,但是已经是对比prepare阶段往后推移过了的,可以认为开始的地方就比前面的最后晚。

!(data/attachment/forum/202212/06/175713mtb5x1bpp65ieq1p.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

因为A1、A2、A3已经承诺过不接受编号小于5的请求了,所以就不会对P1进行响应

只会对P2进行响应,所以都会对P2 响应对应的值9,所以就达成了一致性了

!(data/attachment/forum/202212/06/175939ubrbqabifwhwwed6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


#### 小结

提议编号顺序简单的就可以理解为优先级,根据优先级(编号的大小),acceptor有三个承诺:

如果prepare请求的编号,小于等于acceptor已经响应过得prepare编号,那么承诺不响应这个prepare请求。

如果accept请求的编号,小于acceptor已经响应的prepare请求的编号,那么acceptor承诺不通过这个提议。

如果acceptor之前有通过的提议,那么就会在prepare请求的响应消息里面,把通过的最大编号的提议信息发送过去。


## Multi-Paxos

lamport描述了几种实现 multi-Paxos 的可行方法, 但缺失了细节。

也就是不是完备的、论证的算法。

后面大多都是基于multi-paxos算法进行的完善,但是很难证明。

所以也可以说:兰伯特提到的 Multi-Paxos 是一种思想,不是算法。

而 Multi-Paxos 算法是一个统称,它是指基于 Multi-Paxos 思想,通过多个 Basic Paxos 实例实现系列值的共识的算法(比如 Chubby 的 Multi-Paxos 实现、Raft 算法等)。

Chubby的 Multi-Paxos 实现,代表了Multi-Paxos思想在生产环境中的真正落地,它将一种思想变成了代码实现,其实也是因为Chubby的出现,让Paxos开始火了起来。

不过Chubby不是开源的。

这部分可以更多的参考原始论文**The Part-Time Parliament**

multi-Paxos如果直接多次执行basic-Paxos的话:

如果prepare阶段,没有协商成功,比如5个节点,有三个是proposer,必然不会有大多数进行响应,最多得到两个prepare的响应,准备阶段就失败了,也就是所谓的提议冲突

本身分布式系统基于rpc进行通讯,大量消息往返会消耗资源、时间,浪费性能。

可以通过引入Leader角色,通过Leader进行唯一提议者,也就不存在冲突了

!(data/attachment/forum/202212/06/203308s552jk5ivsj5i5if.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

论文中没有表明如何选出Leader,chubby是通过一轮Basic Paxos进行选举的。

本身prepare阶段就是发现选出被大多数通过的提议值,但是通过Leader,就不存在选举冲突,也就不存在多个值,都是Leader说了算,prepare就不再有意义,所以可以省略掉prepare阶段。也就是所谓的,领导者处于稳定状态时,省略掉准备阶段。

大大减少了消息的交互,退化为相当于单纯的主从节点数据通信交互。

Leader的本质就是我不要你们不断地商量,我只需要指定一个即可。毕竟很多场景下,比如选主,是谁都不重要,选出来最重要。

也就是不关心具体的值,只关心尽快达成一致。


!(data/attachment/forum/202206/16/141330jha7st9soow8772i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "common_log.png")
`转载务必注明出处:程序员潇然,疯狂的字节X,https://crazybytex.com/thread-240-1-1.html `
页: [1]
查看完整版本: 分布式系统非拜占庭错误故障错误算法paxos 算法