https://liu-jianhao.github.io/2019/05/paxosmulti-paxos%E8%AF%A6%E8%A7%A3/
原始的Paxos算法(Basic Paxos)只能对一个值形成决议,决议的形成至少需要两次网络来回(在prepare阶段需要广播到所有节点,这可能造成O(N^2)的灾难性后果),在高并发情况下可能需要更多的网络来回,极端情况下甚至可能形成活锁。如果想连续确定多个值,Basic Paxos搞不定了。因此Basic Paxos几乎只是用来做理论研究,并不直接应用在实际工程中。
实际应用中几乎都需要连续确定多个值,而且希望能有更高的效率。Multi-Paxos正是为解决此问题而提出。Multi-Paxos基于Basic Paxos做了两点改进:
-
针对每一个要确定的值(log entry),运行一次Paxos算法实例(Instance),形成决议。每一个Paxos实例使用唯一的Instance ID标识。
-
在所有Proposers中选举一个Leader,由Leader唯一地提交Proposal给Acceptors进行表决。这样没有Proposer竞争,解决了活锁问题,而且消灭了prepare环节。
- 原理:
Multi-Paxos通过改变Prepare阶段的作用范围至后面Leader提交的所有实例,执行一次Basic Paxos实例来选举出一个Leader,从而使得Leader的连续提交只需要执行一次Prepare阶段,后续只需要执行Accept阶段,将两阶段变为一阶段,提高了效率。为了区分连续提交的多个实例,每个实例使用一个Instance ID标识,Instance ID由Leader本地递增生成即可。
Multi-Paxos允许有多个自认为是Leader的节点并发提交Proposal而不影响其安全性,这样的场景即退化为Basic Paxos。
select log entry
- entry的jmp按序找出没有被确认的块/空缺的块(以S1为例,没有裱框的3457都是,逐步填充34直到找到自己的归宿5没有人AC<冲突>)
- 跑Basic Paxos来propose value
- (继续第一步)或者(选择jmp然后结束)
leader election
- server选择最高ID作为leader
- 每个server发送心跳包给每个其他server每隔 T ms()
- 如果2T ms里没有接收到更高ID的心跳包:
(1)从客户端接收请求
(2)既做 proposer 也做 acceptor - 如果不是leader:
(1)拒绝client的包(重定向给leader,我也不是谦虚)
(2)只做acceptor
- 实际上大多数系统都不会采用这种选举方式,它们会采用基于 租约 的方式(lease based approach)
减少prepare
-
第一个问题是阻止所有的提议,我们可以通过改变提议序号的含义来解决这个问题, 将提议序号全局化,代表完整的日志 ,而不是为每个日志记录都保留独立的提议序号。这么做要求我们在完成一轮准备请求后,当然我们知道这样做会锁住整个日志,所以后续的日志记录不需要其他的准备请求。
-
第二个问题有点讨巧。因为在不同接受者的不同日志记录里有很多接受值,为了处理这些值,我们扩展了准备请求的返回信息。和之前一样,准备请求仍然返回 acceptor 所接受的最高 ID 的提议,它只对当前记录这么做,不过除了这个, acceptor 会查看当前请求的后续日志记录,如果后续的日志里没有接受值 ,它还会返回这些记录的标志位 noMoreAccepted 。
-
使用了这种领导者选举的机制,领导者会达到一个状态,每个 acceptor 都返回 noMoreAccepted ,领导者知道所有它已接受的记录。所以一旦达到这个状态,对于单个 acceptor 我们不需要再向这些 acceptor 发送准备请求,因为它们已经知道了日志的状态。
-
不仅如此,一旦从集群大多数 acceptor 那获得 noMoreAccepted 返回值 ,我们就 不需要发送准备的 RPC 请求 。也就是说, leader 可以简单地发送 Accept 请求,只需要一轮的 RPC 请求。这个过程会一直执行下去,唯一能改变的就是有其他的服务器被选举成了 leader ,当前 leader 的 Accept 请求会被拒绝,整个过程会重新开始。
- 为什么要prepare?
(1)阻止其他old请求跟我们new的竞争
(2)找出被chosen的值(如果有),要不然就用自己的
信息不对称问题
(1)log entry 没有被完整复制(majority only)
(2)只有proposer知道entry什么时候被选中
无法知道什么value被chosen不知道该不该应用到 state machine
-
Solution1(leader后台广播同步entry到所有节点):坚持retrying直到所有acceptor回复收到entry,这步可以放在后台执行不影响其他工作
-
Solution2(标记被选中的entry):如果它发现一个entry已经被chosen,Acceptor设置proposal ID为+∞,从而保证它无法被窜改,因为它是最大值。每个server保留一个firstUnchosenIndex标记未被chosen的entry所在位置。
- Solution3(Piggybagging P广播Accept RPC给A们):proposer夹带它的firstUnchosenIndex到给Accepter的RPC中,acceptor的entry chosen原则如下:
(1)i < request.firstUnchosenIndex
(2)acceptedProposal[i]==request.proposal
我们知道它已经被 proposer 选定,但是它可能被另外一个值所取代。所以还需要多做一步。
- Solution4(leader 的旧日志项 <老早以前未AC都可能失效了,比如第4个位置的2.5>):
(1)Acceptor 返回它的 fristUnchosenIndex 在 Accept回答中
(2) 如果 proposer 的 firstUnchosenIndex > acceptor回答的 firstUnchosenIndex(说明acceptor 中的某些状态不确定),proposer就会在后台发送 Success RPC
(3)Success(index, v):通知 acceptor 该日志项被选中了
acceptedValue[index] = v
acceptedProposal[index] = 无穷大
return firstUnchosenIndex
如果需要(可能存在多个不确定的状态),Proposer 发送额外的Success RPC(出现不止一个2.5)
image.png客户端协议(leader挂了怎么办,不认识leader怎么办)
(1)发送命令给 leader
- 如果不知道哪个是 leader,随便选择一个服务器
- 如果该服务器不是 leader,重定向给 leader
(2)leader 直到日志项被选中并且被 leader 的状态机执行才回应客户
(3)如果请求超时(leader挂了)
- 客户重新发送命令给其他服务器(重新选举?)
- 最终重定向给新 leader
- 给新 leader 重发请求
命令反复执行的问题(命令对应唯一id)
(1)如果 laeder 在执行完命令但在回应之前挂掉
- 该命令不能执行两次
(2)解决:客户端需要为每条命令提供一个唯一的 id
- 服务器会将该 id 存入日志项中
- 状态机会记录每个客户最近执行的命令,即 最高 id 序号
- 在执行命令之前,状态机会检查该命令如否被执行过,如果执行过:
- 忽略
- 返回之前执行的结果
(3)结果(客户端不崩没事,崩了可能会有没执行的问题):只要客户端不崩溃,就能获得 exactly once 的保证,每个客户端命令仅被集群执行一次。如果客户端出现崩溃,就处于 at most once 的情况,也就是说客户端的命令可能执行,也可能没有执行。但是如果客户端是活着的,这些命令只会执行一次
配置变更
image.png系统配置:
- 每台设备ID,IP
- 决定majority的大小
配置更新的原因:
- 替换失效的机器
- 扩容
- 一种扩容后导致的配置问题:
- lamport大佬的建议:
- 配置被按照entry存储
- 跟别的log entry一样复制
- 选用哪个配置由a步之前的配置决定,具体如下:(以a=3为例)
a的大小会影响entry执行的效率以及配置更新的速度。a如果很大,那么可以在a范围内不受限制地确认entry并执行,但是这样一来配置更新就得等a个entry执行完,更新速度放缓。如果a很小,配置更新的速度就很快,但是因为a受限,一但网络延迟影响前面的执行就会拖慢执行的速度。
网友评论