• 写在前面的话

年前工作的间歇期计划学一些优秀的开源代码充充电,侧面打听到微信团队开源的Paxos源码在微信内部有大量的线上应用加之自己对Paxos之前的理解并不算深刻,所以想借着这个机会彻底搞懂它。年后回来趁着还“热乎"整理成系列文章巩固一下记忆。

为什么选择PhxPaxos作为学习Paxos的源码,这就涉及到优秀开源项目的标准。个人理解优秀的开源项目需要具备以下几点:

  1. 经过大量的生产环境验证。
  2. 高质量的文档。
  3. 活跃的社区。
  4. 高质量的代码,以及良好的代码风格。

经过大量的生产环境验证代表着源码在稳定性、可运维性、吞吐性能上得到了充分验证,很多开源的项目PR文章写得很吸引眼球但是用起来却差强人意,正是没有大规模线上运行的原因,这也是PhxPaxos最吸引我的原因,其他因素并不是选择PhxPaxos的主要因素,因此不在这里展开了。

本系列文章主要分为以下六部分进行介绍:

  1. PhxPaxos源码解析之(1)概述篇
  2. PhxPaxos源码分析之(2)提案申请篇
  3. PhxPaxos源码分析之(3)提案发起篇(Paxos协议核心)
  4. PhxPaxos源码分析之(4)Learner篇
  5. PhxPaxos源码分析之(5)CheckPoint篇
  6. PhxPaxos原发分析之(6)工程优化篇

本系列文章在介绍相关源码部分会有类图、时序图、流程图方便理解,所有截图都是第一遍阅读源码时自己画给自己看的,方便自己理解记忆,基本都是不符合UML模型的规范在此声明一下。

  • 协议概述

开始之前先对Paxos协议做一个简单的回顾,Paxos协议论文、详细的推理证明以及相关介绍网上已经很多了不在此重复。

1. 协议过程

ph1.a:Proposer 向集群中所有节点发送Prepare请求。请求中只包括Proposalid,Proposalid全局唯一且递增。

ph1.b:Acceptor应答Prepare请求。如果Acceptor接收Prepare请求,则

             a. 不再接受 <= Proposalid 的Prepare请求。

             b. 不再接受 < Proposalid 的Accept请求。

             同样,如果不满足上述要求,则拒绝prepare请求。接收请求的同时,返回Accept过的最大的Proposalid以及内容,没有则                          返回空。

ph2.a:Acceptor收集Prepare的应答。如果收到多数派的回答,比较所有应答中Proposalid最大的内容作为提案内容发起                                      Accept请求,如果内容为空则用Proposer的内容进行提案。

ph2.b:Acceptor应答Accept请求。满足ph1.b中的条件,则持久化Proposalid以及内容(用于Learn)。Proposer收到多数的应答                            后形成最终决议。

2. 理解

下面通过一些问题进一步的理解Paxos协议:

a.在什么情况下可以认为提案(值)被确定不再更改?

多数Acceptor接受,即可以任务提案被接受了。

b.Paxos两个阶段都在做什么?

第一阶段,协商Proposalid以及Proposalid代表的轮次应该提交的内容。

第二阶段,提交提案,达到半数以上认为提交成功。

c.一个Proposalid是否会有多个Proposer在运行第二阶段?

不会,根据ph1.b中的规则,只会有一个Proposer获取多数的Prepare应答。

d.Proposer在什么情况下可以将自己提案作为内容发起提议?

Proposer收到的PrepareReply中Proposalid最大的内容为空时,即上一轮提案(多数派返回则最大的Proposalid一定是上一轮提                 案)Learned(学习之后的内容会被删除)。

e.第二阶段如果获取的内容为空,为何保证旧的Proposalid无法形成新的提案?

第二阶段如果获取的内容为空表示当前Proposalid已经被Accepted,旧的Proposalid根据ph1.b中的规则会被拒绝。

f.新的Proposalid获取成功,旧的Proposer如何工作?

如果处于第一阶段,正常发起prepare请求,但是不会获取多数投票。由于新的Proposalid获取成功,旧的Proposer也不会工作在               第二阶段。

g.如何保证新的Proposalid不会破坏旧的提案?

ph1.b中投票规则,不再接受 <= Proposalid 的Prepare请求。

h.为什么在第二阶段,只需要考虑最大Proposalid的accpted的提案?

表示上一轮(距离当前Proposalid最近的Proposalid)提议的结果,如果不为空表示还没有被learned但是被多数accept,需要重                 新提交。

i.提案确定之后,如何保证在任意半数以下Acceptor故障时,提案不被更改?

半数以上以上已经accepted,如果已经Learned,那么其他节点也会发现并Learn,如果还没有Learn,下次提议时会提交。

j.如果Proposer运行过程中半数以下Acceptor故障时,如何运行?

Prepare完成之后恰好半数Acceptor故障全是接收了Prepare的,仍能进行Accept请求,此时Accept由于没有被多数Acceptor同意,             提案被拒绝。

k.正在运行的Proposer和半数以下Acceptor故障时,提案内容会怎样?为何后面新的Proposer可以完成新的提案?

第一阶段期间故障,提案失败。第二阶段故障,提案成功。因为第一阶段会协商Proposalid以及提案内容。

3. 活锁

高并发时,多个Proposer同时发起Prepare请求,导致没有人格一个Proposer获得多数派的投票进而重试,循环往复的现象称为活锁。解决方案,第一是控制Proposer发起提案的频率,第二是Proposer重试加一个随机避让时间。

4. Muti-Paxos

如果一个Proposer连续发起n个提案,要进行n次prepare + n次Accept,显然n-1次prepare是浪费的。Muti-Paxos就是解决这个问题提升效率的。Muti-Paxos的解决办法是,Proposalid复用如果Proposer没有变换沿用之前的Proposalid,直到其他Proposer发起提案时进行Proposalid = Instance+1打破之前Proposalid的局面。从效果上看相当于多个提案被压缩成一个大的提案。

很多网上资料介绍Muti-Paxos需要一个Leader,参考资料中有说明,Leader不是必须的。

  • 核心代码架构

协议的核心代码路径:phxpaxos/src/algorithm,包括了Proposer、Acceptor、Leaner类以及协议的两阶段逻辑。

Group:Paxos集群,奇数个节点(PNode)。PNode:代表一个Paxos节点。Instance:PNode的具体实现。

Instance:聚合了Proposer、Acceptor、Leaner,他们三个在一个线程上。

下图是核心类的关系图,通过类名和函数可以大概了解其作用。

Base是Proposer、Acceptor、Leaner的基类,维护InstanceID变量对应协议中提案的编号,注意每个节点上Proposer、Acceptor、Leaner各有一个InstanceID单独维护的,每次提案被确认之后都会调用Base的NewInstance进行+1。

Commiter负责提案的生成,IOLoop线程主循环负责事件的注册与回调。

PhxPaxos源码解析(1)之概述篇

  • 参考资料

https://www.bilibili.com/video/av36134550/

https://mp.weixin.qq.com/s?__biz=MzI4NDMyNTU2Mw==&mid=2247483798&idx=1&sn=42dd222ae255b13f1f67cd9e6d3f3dc0&scene=21#wechat_redirect

https://github.com/brpc/braft/blob/master/docs/cn/paxos_protocol.md

 

 

 

 

 

 

 

 

 

相关文章:

  • 2022-12-23
  • 2021-07-22
  • 2021-09-11
  • 2021-10-05
  • 2022-12-23
  • 2021-08-31
  • 2021-05-01
  • 2022-02-14
猜你喜欢
  • 2021-09-07
  • 2021-06-16
  • 2021-11-16
  • 2022-12-23
  • 2021-08-12
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案