Paxos协议仿真实现

103 阅读6分钟

一、实验环境

操作系统:Ubuntu 18.04
开发语言:Go 1.18

二、操作步骤

实验中仿真了6个Proposer节点和20个Acceptor节点,每个节点在一个Goroutine中运行,节点之间通过RPC进行调用,每个节点通过单独定时器实现不同速率发送proposal,全局定时器每20ms检查节点一致性状态,主要交互流程如下图所示。

paxos.png

实验内容

① 提议被拒绝后,不会立即重试,而是等待定时器再次发起新的请求,方便实现全局一致性的目标。

② 设置Acceptors一致性检验进程,对达成一致性的耗时进行量化。

③ 通过为不同Proposer节点设置不同延迟测试提议并发对最终一致性的影响。

④ 实现仿真网络延迟、Proposer到Acceptor网络链路断开、Acceptor宕机一段时间、Acceptor到Proposer网络链路断开等多种分布式系统常见的问题,探讨不同情景对一致性的影响。

Proposer的功能是向Acceptor提出提议,作为paxos两阶段的发起人。Proposer结构体形式如下:

type Proposer struct {
mu                   sync.Mutex
serverID             int         // proposer的编号,用于生成proposerID
acceptorPeers        []string    // acceptor的地址
roundNumber          float32     // seen max proposerID
currentID            float32     // 本轮的提案ID
currentValue         interface{} // 本轮的提案值
highestAcceptedID    float32     // 收到acceptor中最高接受过的proposerID
highestAcceptedValue interface{} // 收到acceptor中最高接受过的proposerID的value
}

  其中roundNumber用于生成提议ID,可以存储第二阶段被拒绝时返回的minProposal,避免重试依然存在ID过小被拒的情况。 提案ID由两部分组成,即ID = roundNumber + ‘.’+ serverID,如果多个Proposer发起提议,则serverID被接受的可能性最大。

Acceptor的角色则是投票提议并存储具体数值,结构体如下:

type Acceptor struct {
mu            sync.Mutex
localAddr     string      // 本地tcp地址
learnerPeers  []string    // learner的tcp地址
minProposal   float32     // 最小的即将 accept 的 Proposal number
acceptedID    float32     // 接受的proposeID
acceptedValue interface{} // 接受的值
listener      net.Listener
isDead        bool
isUnreliable  bool // 用于模拟不可靠网络

}

Proposers与Acceptors之间通过RPC调用实现,Proposer作为RPC客户端,而Acceptor作为RPC服务器端。在实例化Acceptor的时候注册自身函数,采用TCP协议传输,每当客户端调用服务器端函数时,使用go原语开辟新的线程处理,避免阻塞。两节点交互信息用彩色打印在控制台中,如下图所示。 图片1.png

在main函数中,程序调用了周期为20ms的检查一致性的线程,一旦返回结果为通过,通过信号量机制通知所有Proposers线程停止提议,并统计达成一致性所消耗的时间。

实验结果与数据处理

为减少其他因素干扰,实验统一设置Acceptor到Proposer的网络延迟为100ms。不同因素对最终达成一致性的影响如下表所示。

变量因素影响分析
网络延迟如果各个Proposer到Acceptor的网络延迟不一样,高的网络延迟,意味着在时间上较前发出的proposal无法到达,被抢占的可能性更大。
Proposer发送间隔时间改变Proposer发送间隔时间意味改变并发度,更短的间隔时间意味着最初的提议完成prepare阶段后,在accept阶段被拒绝的可能性更大,不过也意味着可以更快地将已有达成的提议扩散到全网。
Proposer到Acceptor网络链路断开Proposer可能将无法收集到足够多的支持回复,而且Acceptor需要下次提议才会改变,无法满足全局一致性要求。
Acceptor宕机一段时间Proposer可能将无法收集到足够多的支持回复,全局Proposers都无法在宕机时间改变Acceptor值,无法满足全局一致性要求。
Acceptor到Proposer网络链路断开Proposer可能将无法收集到足够多的支持回复,但此时minProposal或者提议值已经被接受。

  设定其他条件不变,只考虑Proposer发送间隔时间这一因素,得到间隔时间(ms)与达成最终一致性时间(s)的折线图,如下图所示。 图片2.png

间隔时间与网络延迟共同对最终一致性时间造成影响,在间隔时间较短时,第一个提议的,有较大可能在accept阶段被拒绝,然后发起新的提议,图中一致性时间为30s的表示当前多Proposer进入活锁状态,无法在短时间内达成一致性。在间隔时间为200ms时,第一个提议被accept,此后其他Proposer的提议都会得到acceptValue的返回,进一步广播该提议,实现全局的一致性,在200ms前,各Acceptor达成的一致性值为随机值,在200ms后,一致性值总是第一个提议值。

下面讨论Proposer到Acceptor网络链路断开几率的影响,这里设定间隔时间为40ms,链路正常情况下,能够很快达到共识值,概率步长为0.005,进行20次实验得到下图。 图片3.png 各Acceptor节点达成一致性的时间随着断联概率增加而波动,多次因为断联无法进行paxos第一阶段和第二阶段,断联使得Proposer提议冲突的几率更大,所以多次陷入“活锁”状态。

当按同样步骤设置Acceptor到Proposer网络链路断开的几率时,整体曲线比上图平缓,这是因为Acceptor已经accept刚到的提议,保留AcceptValue,新的提议过来时,直接返回已存值,对整个共识算法影响较小,如下图所示。

图片4.png

下面讨论Acceptor宕机几率对达成一致性所需时间的影响,从下图可以看出,两者整体成正相关关系,这是因为要使得全局Acceptor达成共识,就要等待已有宕机节点恢复,在实际生产中,尽量避免全局一致性要求,等待时间具有随机性。

图片5.png

分析与讨论

本实验完成了Paxos算法的仿真,并讨论仿真网络延迟、Proposer到Acceptor网络链路断开、Acceptor宕机一段时间、Acceptor到Proposer网络链路断开等多种分布式系统常见的问题对算法达成一致性时间的影响,结果发现,即使是10%的几率损坏,也需要消耗更长时间达成共识,而且时间具有随机性,最终导致不满足全局一致性的要求。实验中多次出现“活锁”状态,改进的方法有随机化重试间隔时间或者选举Leader,在实施Paxos算法时,需要选择合适的共识要求,尽量不采用全部节点一致的要求,很容易因为宕机影响整体系统,出现超量的网络开销。

 

参考文献:

[1]  L. Lamport. Paxos made simple[J]. ACM SIGACT News, 2001, 32(4):18–25
[2]  vision9527. Basic Paxos. github.com/vision9527/…. Accessed July 14, 2022
[3] John Ousterhout,Diego Ongaro. Implementing Replicated Logs with Paxos. ongardie.net/static/raft…. Accessed July 14, 2022