Multi-Paxos算法及其优化

Posted 武力程序猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Multi-Paxos算法及其优化相关的知识,希望对你有一定的参考价值。

本文翻译自youtube视频:

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




在讲解Paxos和Raft算法时,通常都会引用一个replicated log问题。我们由这个问题开始,去分析Paxos和Raft算法是如何解决。


01

问题


有多台server的cluster。 Client可以发送一个指令到任何一台server上。某一个server(假设S1)接受指令后,会保存在一条log列表里,并且提交给状态机执行log。同时S1会将指令同步到别的server上,别的server也会将log提交给自己的状态机执行。 



理想的情况是,所有server执行指令的顺序都是一样的。如何保证这一点呢?


这个问题的核心是,如果创建一个replicated log。 




02


Multi-Paxos解决方案


为了解决这个问题,我们需要运行multi-paxos,log列表里每一个index都是单独的paxos。具体的流程如下图: 

Multi-Paxos算法及其优化

步骤:

1. client给一个server发送指令 

2. server运行一个Paxos,来决定这条指令放在log的哪个index

3. server等待log中前边所有的指令运行,然后运行这条新指令。 

4. server给client返回response。 

在这个过程中,最关键的问题是第二步:决定一个指令应该放在log的哪个index。举例说明:

Multi-Paxos算法及其优化

上图是一个三个server(S1~S3)的集群。

在上图的左侧,我们分析一下当前的状态。对于S1来说,当前的状态是: 


1. index 1/2/6已知被chosen了 

2. index 3被S1 accept了,但是不知道被chosen了。同时S1并不知道S3的index 3也是cmp指令。

3. index 4/5还是空的 


举例:当jmp指令到达了S1,发生了什么? 

1. 找到第一个没有被chosen的index,在这种情况下就是index3 

2. 在这个index3上运行paxos 

3. 在Prepare阶段,发现有的server返回 acceptedValue=cmp 

4. 转而提出cmp指令。最终cmp指令被chose在index3上。 

5. 对index4做出同样的操作,Prepare阶段发现有sub,运行Paxos之后把sub指令放在index4上 

6. 对于index5来说,假设这里majority是S1和S2,prepare没有返回acceptedValue,于是运行Paxos accept,jmp被chosen在index5上。 


到这里其实就结束了。细心的同学会发现S3上的index5上有另一条指令不是jmp,这不是矛盾了吗?其实不是的。在Paxos的世界里,只要majority的server accept了这个值,那这个值就已经被chosen了。

 

有些同学可能会想,index5上的另一个指令来的比jmp要早,但是jmp在这里被choose了。这难道不是错误的吗?其实并不是,如果客户端想让server按照一定顺序执行指令,这是客户端的任务。客户端需要等一个指令执行完成之后,再发送另一个指令,才能实现这一点。而在这里,客户端连续发送了很多指令,导致一些指令发送给了S3, 另一些给了S1,在分布式系统中,这样的情况无论如何也无法保证指令执行的顺序。 


03


性能优化


我们可以有多个措施来优化这个Multi-Paxos算法。

1. 为了避免死锁,在同一时间只有一个proposer leader 

2. 消除大部分的prepare指令 

* 对整个log prepare一次 (而不是每个index prepare一次) 

* 大部分的index的值在一轮paxos之后就可以定下来 


下边我们具体来分析上述措施

1) 选举proposer的leader 

最简单的方法是选择ID最大的那个 

1. server互相发送心跳 

如果一个server一段时间没收到来自更高ID server的心跳,他就是leader,leader的作用是:1)接受client指令 2)充当proposer和acceptor 

2. 如果一个server不是leader,会拒绝client指令,并且只充当acceptor。

大家会发现这个算法变得有点像Raft了……

2)消除大部分prepare指令 

为啥能消除prepare指令呢?首先我们复习一下prepare指令是干啥的: 

任务一:block 旧的proposal

为了完成这个任务,在这里我们可以改变proposal number的含义。以前proposal number代表一个log中一个index的proposal,现在我们将其代表这个log的proposal。也就是说我们一个prepare block一个index下旧的proposal,现在一个prepare block整个log的旧proposal。 

任务二:找到可能的chosen value

为了完成这个任务,在prepare阶段,acceptor如果发现在当前index以后没有accept的value了,acceptor还要返回一个noMoreAccepted字段。这样proposer就知道后边不可能有能被chosen的value了。 


这样的话,我们能达到两个效果 

1. 如果一个acceptor在prepare阶段返回了noMoreAccepted,我们可以跳过这个acceptor以后所有的Prepare阶段,除非有Accept阶段被reject了。 

2. 如果一个leader从大多数acceptor处收到了noMoreAccepted,就再也不需要Prepare阶段了。 


04


保证Full Replication


对于现阶段的Multi-Paxo算法,我们还有两个问题: 

1. 对于每一个index,被chosen的指令只保存在majority server上。目标:保存到所有的server上。 

2. 当一个index被chosen的时候,只有proposer知道。目标:所有的server都知道。 

大家仔细品味一下,注意这两个问题是有区别的。


解决方法有四步: 


1. 为了解决第一个问题,proposal持续retry Accept步骤直到所有的acceptor都response。 

* Paxos是当majority的acceptor都同意,就达到了一致。我们可以在background持续retry直到所有的acceptor都同意。 

* 这一步基本可以解决第一个问题。 

2. 为了解决第二个问题,acceptor需要知道自己的哪些value是被chosen状态,因此需要持续track chosen 的index 

* 对于已经被chosen的index,标记accptedProposal=正无穷 

* 每个server维护一个firstUnchosenIndex代表着最小的没有被chosen的index 

3. 进一步,Proposer要通知Acceptor哪个index被chosen了 

* 在Accept步骤,Proposer可以包含自己的firstUnchosenIndex,表示proposer已知的第一个没有被chosen的index,言外之意小于这个firstUnchosenIndex的index都已经被chosen了。 

* acceptor在以下两个情况都满足的情况下,标记所有的index i为chosen

    * i < proposal.firstUnchosenIndex 

    * acceptedProposal[I] = request.proposal 

为什么?这块比较难理解。下图举例说明。

Multi-Paxos算法及其优化

收到Accept之前,index1/2/3/5都已经被标记成chosen了,而index4表示accept了proposal number=2.5的proposal,index6表示accept了proposal number=3.4的proposal。 

现在来了一个Accept请求,proposal number=3.4, firstUnchosenIndex=7。这意味着Proposer告诉Acceptor,<7的index都已经被chosen了。Acceptor收到这个请求之后,会比较index 1~6,如果有acceptedProposal=3.4,就直接标记成chosen。因为:

    * 因为 acceptedProposal[I] = request.proposal,Acceptor就知道index=6的来源和这个Accetor request的来源是同一个Proposer

    * Acceptor还知道Proposer的index6已经被chosen了

    * Acceptor还知道,Proposer的index6最新值就是proposal number=3.4的值。因为Acceptor刚刚收到的Accept请求就是proposal number=3.4

需要注意的是,这个第三步只是解决了当前Proposer通知Acceptor的问题,Acceptor还可能保存着上一个Proposer的proposal。

4. 为了处理old leader问题 

* Acceptor需要在Accepte步骤返回自己的firstUnchosenIndex 

* 如果proposer的firstUnchosenIndex > 返回值的firstUnchosenIndex,说明Acceptor对于某些事情是不确定的。 

* 在这种情况下,Proposer需要另一个步骤 Success告诉Acceptor自己的值,来消除Acceptor的不确定。 


05


Client协议


1. 只给leader发送命令 

2. leader必须执行完这个指令,才返回。而必须决定了这个指令的index,才会执行这个指令。 

3. 如果client request timeout,retry command到另一个leader 


问题:leader在sync完指令,执行完执行,但是还没有回复client的时候崩溃了!我们不能让command执行两次啊。。 

解决:client在每一个指令中保存一个unique id

    * server在log中保存这个id 

    * 执行完指令之后,就保存下最新执行的指令id 

    * 在执行指令之前,检查一下这个command是不是已经被执行过。 

如果leader执行完指令之后crash了,client会retry同一个指令,这个时候新的leader就会发现这个指令被执行过了。 

这样只要client不崩溃,我们就可以完成 执行每一个执行 exactly-once的语义 


06


配置改变


系统配置: 

* 首先,每个server需要自己的ID 

* 系统配置非常重要,因为这决定了什么组成了majority。当server数量从3个变成5个,majority的数量也就从2个变成了3个。这一点非常重要,举例如下图: 

Config改变过程中会造成不一致。左边两个server还是旧的config,所以2个就是majority;右边三台机器有最新的config,他们知道majority其实应该是3台机器。这样左边两个choose了V1值,右边两个choose了V2值,这样就会造成不一致。

 

解决方案:把config也当做log中的一个index。下图中,C1和C2是两个config。我们在系统中定义了一个值α=3,α的意思是,index=1的config在index=1+α时才会生效。也就是index=4的时候开始用C1 config。同样index=6的时候才会用C2 config。 

Α取值过小,会造成cocurrency性能问题。因为我们在index=i的config被chosen之后,才能去决定i+α的值。当α=1的时候,系统退化成串行 

Α取值过大,新config生效就会需要等待很长时间。 






以上是关于Multi-Paxos算法及其优化的主要内容,如果未能解决你的问题,请参考以下文章

Paxos理论介绍: Multi-Paxos与Leader

学习《Paxos/Raft:分布式一致性算法原理剖析及其在实战中的应用》-阿里巴巴基础架构事业群 何登成

求解最短路的四个算法及其优化

基于蝙蝠算法优化BP神经网络的数据分类算法及其MATLAB实现-附代码

机器学习基础:kmeans算法及其优化

9.群智能算法及其应用: 粒子群优化算法及应用, 蚁群算法及其应用