前言
paxos作为大名鼎鼎的算法,关于它的文章数不胜数,除去原作者的Paxos Made Simple(学习paxos必看)之外,网上也有众多的梳理讲解。本文相当于是对paxos的学习笔记,但是对paxos的介绍会简单带过,主要侧重于从一个初学者的角度去看学习paxos时一些可能会产生以及比较关键的问题,通过对这些问题的思考来学习理解paxos。
为了解决什么问题
任何算法发明出来都是为了解决实际问题的,那么鼎鼎大名的paxos是为了解决什么问题呢?简单来说paxos是为了使分布式系统中对某个“值”达成一致,也就是分布式一致性问题。
- 什么是分布式一致性问题?
众所周知分布式系统的特点是分布式(好像是废话。。),那必然要有其分布式的优点,否则和单点系统又有什么区别呢?分布式系统的一大特点是可以避免单点故障保证HA,而能做到这一点正是因为数据多副本。数据多副本也就引出了分布一致性问题,某个节点的数据发生变化时,必须要保证其他节点也会更新数据副本,不能产生数据不一致的现象。当然实际上数据一致性存在多种一致性级别,但不管是什么级别,分布式一致性都是迈不过去的砍,而pasox就是解决分布式一致性的最经典的算法。注意这里的数据是很宽泛的一个概念,它可以是一个数据库记录,一个选举leader的数据等等。
有关分布式一致性的知识参考从分布式一致性谈到CAP理论、BASE理论
paxos算法简介
关键词
- 初衷:各节点之间尽快就取值达成一致
- 抢占式
- 两段提交
- 后者服从前者,服从已确定提议
- 无拜占庭将军问题(消息内容不会被篡改)
算法简介
paxos算法分为两个阶段。具体如下:
- 阶段一:
- (a) Proposer选择一个提案编号N,然后向半数以上的Acceptor发送编号为N的Prepare请求。
- (b) 如果一个Acceptor收到一个编号为N的Prepare请求,且N大于该Acceptor已经响应过的所有Prepare请求的编号,那么它就会将它已经接受过的编号最大的提案(如果有的话)作为响应反馈给Proposer,同时该Acceptor承诺不再接受任何编号小于N的提案。
- 阶段二:
- (a) 如果Proposer收到半数以上Acceptor对其发出的编号为N的Prepare请求的响应,那么它就会发送一个针对[N,V]提案的Accept请求给半数以上的Acceptor。注意:V就是收到的响应中编号最大的提案的value,如果响应中不包含任何提案,那么V就由Proposer自己决定。
- (b) 如果Acceptor收到一个针对编号为N的提案的Accept请求,只要该Acceptor没有对编号大于N的Prepare请求做出过响应,它就接受该提案。
问题思考
问题
- 为什么需要有两个阶段?
- 为什么需要引入propose编号?如果不引入会有什么问题?
- paxos活锁问题怎么解决?
- 为什么要有learner?
- paxos可以最多容忍多少proposer同时挂掉?
- paxos可以最多容忍多少acceptor同时挂掉?如何在这么多acceptor挂掉的情况下仍然保证paxos正常运行?
- paxos两阶段分别在做什么?
- 什么时候可以认为value取值确定,不会再被更改?
- 是否会存在两个proposer以相同的编号进入到了第二阶段?
- 为什么第二阶段proposer取值时选取响应的编号最大的那个?
答案
-
- 因为只有一个阶段无法保证决议的一致性,两阶段要结合其他过程来看,两阶段中的其中两个关键是返回已接受的提议值和proposer服从已决议值,两阶段搭配他们才能达到保证一致性的效果。如果只有一阶段,举个例子:p1向a1、a2提议v1值,p2向a2、a3提议v2值,且都采纳并返回了(p2先后接受了p1、p2的提议),此时p1和p2获取到的值就不一致了。
- 如果没有编号,那可能会有两个问题:
- 可能有多个proposer在几乎同时向不同的acceptor提出了决议,但是没有没有一个决议被超过一半以上的acceptors通过,那就无法达成一致
- 某节点如果被所有acceptor都接受了,但还没提出propose,如果这个时候它挂了,那其他的proposer也不会被再次接受,集群陷入死锁状态。显然,这种方案下是不能容忍集群内任一proposer故障的,是不可取的。
- 有以下几种解决思路:
- 随机改变 编号的增长幅度
- 增加Proposer发送新一轮提案的间隔
- 选取一个主proposer
- learner负责将取值同步给其他未确定的Acceptor。要点是learner是逻辑上的概念,并不一定物理上和proposer或者acceptor分开,抽象出这样一个角色使得算法描述上更为严谨。
- 任意个,proposer即使全挂掉那没有新的提议就是
- 一般acceptor会选取奇数个(偶数个浪费且没意义),2N+1个acceptor最多容忍N个同时挂掉,因为proposer必须收到半数以上的acceptor的同意prepare响应才会发起第二阶段的accept, 只要保留了半数以上的acceptor,那最终还是能达成一致的:
- 如果还未确定value,则剩余存活的acceptor仍然足够确定value
- 如果已经确定value,那么说明之前有半数以上的acceptor确定了该value,此时只挂掉了半数以下,剩余的acceptor中至少有一个有该确定的value,且其对应的编号是最大的。那么就一定可以获取到这个value,仍然可以保证最终取值的一致性
- 第一阶段尝试获取提交提案的权利,同时发现之前是否存在已被选定的value以及将旧的未通过的提案进行阻塞,以减少竞争。第二阶段执行真正的提交。
- 一旦存在半数以上的acceptor接受了某个proposer的取值,则认为value取值被确定,不可再更改
- 不会,进入到第二阶段需要半数以上的acceptor对prepare请求进行相应,同一个编号下只会有一个proposer进入第二阶段;但是会可能会存在不同编号进入第二阶段的情况。比如acceptors[1,2,3],proposers[1,2],proposer1以以(n1,v1)进入了第二阶段,第二阶段还未完成,此时proposer2以n2的编号向全部acceptor发送prepare请求,显然会全部同意,但是会返回已经接受了proposer1提出的v1值。这样虽然存在多个proposer同时进入第二阶段的情况,但是他们提出的值一定是相同的。
- 因为acceptor只会接受编号更大的prepare请求,且编号更大的请求会采取之前的取值。这样在第二阶段选取最大的编号一定不小于已经被多数派接受的提议编号,它对应的取值就一定和选取的值相同。