Paxos算法详解

本博客是Dustin J. Mitchell的Clustering by Consensus的学习笔记。如有不当之处请指正。

举个例子

银行账户管理服务系统是一个典型的分布式系统。简单的操作包括deposit(存款)、transfer(转账)和get-balance(获取余额)。对账户A进行转账操作,账户信息如果存储在一个服务器中,那容易处理,设置一个lock避免并行计算就可以保证余额信息的实时准确更新,避免转账额度大于余额。但是单个服务器是不安全的,一旦宕机系统将无法运行。故将账户信息存储在多个服务器中,并且每个服务器运行相同的程序,但程序间运行是独立的,用户可以对任何一个服务器发起操作。但问题随之而来!假设在同一时间对账户进行转账操作,一次操作在服务器node1上,一次在node2,那么node1和node2上的余额信息会不一致,哪个是正确的呢?两次的转账额度之和超过余额怎么办?注意问题的产生原因在于服务器使用了各自的本地账户信息,并且在执行操作前尚未确保本地账户信息与其他服务器上的账户信息一致。解决思路是确保所有服务器接收相同操作请求时,执行相同的决定性状态机(deterministic state machine),那么输出结果必然一致。但还需要一个一致性算法(consensus algorithm)来保证每个服务器接受相同的输入。Paxos算法及其衍生算法是最重要的一致性算法。

不一致行为

我们知道Paxos算法受启发于爱琴海的Paxos岛上对兼职议会的管理机制。那么自然想到兼职议会出现的问题正是分布式系统中可能出现的不一致问题。首先介绍下Paxos岛上的兼职议会。
今天的议会都会有一个秘书长期呆在会议室记录会议过程,记录通过的法令。而Paxos岛上的人重商,喜好逍遥自在,没有人愿意长期呆在会议室。但是每个议员都有一个“账本”记录通过的法令,当然这些记录具有时序性。现在就引出一个问题,要如何保证所有议员的账本上记录的法令相同,不彼此造成矛盾。因为一批议员离开后,下一批议员就不知道上一批议员通过的法令是啥。会议室内议员甚至需要信使去传递信息,那么信使可能忘记传递或者说传递不及时。类比到分布式系统上,可能出现的不一致行为就是:
1. 集群节点间通信出现延迟,甚至中断
2. 节点宕机
3. 新的节点加入

一致性算法(consensus algorithm)

所谓一致性算法,可以理解为一种节点间通信的协议(protocal),这个协议能够保证集群中的节点在同一时刻接受相同的value。

Simple-Paxos

三个重要对象

proposer:负责接受客户端操作请求,并将其包装成提案发送给acceptor
acceptor:信息的接收者,决定是否接受proposer的提案
learner: 学习已经决定的提案,通常是新加入的节点

基本数据类型

整个算法中采用两种数据结构来描述协议。
ballot: number+identity. 由proposer发送给acceptor。可以直观地理解为proposer在拉票。number是票的编号,代表该票的优先级(越新优先级越高)。identity是拉票者的ID
proposal:client_id+value+identity.发送完ballot后,proposer接着发送proposal。可以直观理解为proposer正式发出操作命令。client_id就是客户端的id,value就是客户端出入的操作请求

Paxos instance

Paxos算法的实例。就是集群对同一个value达成一致的完整实现。实例化一个Paxos,意味着对一个value达成一致,即对集群中所有服务器执行了相同的操作,完成了状态的统一转变。

通信过程

Phase1: proposer发出prepare请求,acceptor返回一个promise。
prepare请求就是发送一个ballot(number=N0)。如果acceptor已经接受了一个ballot(number=N),且N>N0,那么promise返回已接受的ballot和相应的proposal,并保证不接受number小于N的ballot以及proposal。
Phase2:如果proposer接收到大多数acceptor返回的promise。其将发送一个accept请求。
若返回的promise中存在number大于N0,那么accept请求发送最大ballot number对应的proposal。反之,发送原本的想要发送的proposal。acceptor在不违背promise的前提下必须接收proposal并返回accepted信号告知接受。

存在的问题

  1. 当存在多个proposer同时发出proposal时,可能会发生冲突。例如:A先发出proposal(number=1)的prepare,当在A发出的accept尚未完成之前,B发出propasal(number=2)的prepare。那么问题来了,acceptor将不会接受A得proposal,因为B的number更大,于是A再次发送number=3的proposal。同样地,B的accept也被拒绝。如此造成死锁。
  2. 每一次instance的完整实施,需要两次round(prepare/accept),这比较耗费资源。
  3. 当某个acceptor答应接收一个proposal,但该proposal迟迟未接收到时,该acceptor将无法继续工作。

Multi-Paxos

Multi-Paxos就是多个instance(或者slot)的有序序列,每个slot需要有一个slot number,即实例编号。可以理解为连续执行一系列操作的情况。
Multi-Paxos激活一个节点的leader角色,且该节点拥有最新的操作(proposal)(例如最近的一次对账户的操作),毫无疑问,这个最新的proposal拥有最大的number,那么可以直接发送accept,而集群中其他节点将必须接受该accept包含的proposal。当然,这个leader角色的激活过程几乎和Simple-Paxos的Phase1过程一致。

Python实现一个Multi-Paxos的基本框架

基本框架中,集群被做成一个cluster库,该库提供一致性服务,供应用程序调用。集群是由各个服务器构成,每个服务器上运行守护进程,保证节点间的通信。当客户端应用程序通过某个节点启动集群后,发送操作请求,同时调用cluster库中的相关类进行节点间的通信,从而使得所有节点都执行相同的操作,保证一致性。

cluster库中的类

Network:负责存储所有node的信息,实现添加node,发送信号的方法。
Member:负责客户端应用程序与Network的交互。应用程序在节点上通过实例化Member启动一致性服务。
Requester:负责将应用程序的操作请求包装称Invoke信号发送给Replica,并反馈的Invoked信号,然后将反馈结果传递给Member。
Node:实现接收并处理信号的基本方法。登记和删除node在集群中承担的角色。
Role:角色的基类。隶属于Node的实例。
Acceptor:若某node登记了该角色,则表明该node负责接收来自leader的提案。
1. 处理来自Scout的Prepare信号并反馈Promise信号(若接收到的Prepare信号的ballot number比本地node的大,则还需要发送Accepting信号给本地Replica)
2. 处理来自Commander的Accept信号并反馈Accepted信号。
Replica:该角色扮演了秘书的角色,功能尤为复杂。
对于拥有激活的Leader角色node上的Replica,负责的功能如下:
1. 接收来自Leader的Adopted信号,明确自身与Leader并存
2. 处理来自Requester的Invoke信号,将其转化为Propose信号发送给Leader
对于拥有激活Acceptor角色node的Replica,负责的功能如下:
1. 负责接收来自Accepting信号,更新自身记录的Leader的位置
对所有node上的Replica,负责的功能如下:
1. 负责接收来自Leader的Active信号,知道Leader是活的
2. 处理来自Commander的Decision信号,执行其中的提案(若Replica位于leader上,还需要将输出结果反馈给Requester)
3. 处理来自Bootstrap的信号,并反馈Welcome信号欢迎新节点的加入
Leader、Scout、Commander::一次slot的核心。功能如下:
1. 负责派生Scout和Commander类。并接收来自Scout的Adopted信号和来自Commander的Decide和Preempted信号(当存在Acceptor拥有更大ballot number时,发送Preempted信号,反之Decided信号,Decided信号实际并不起作用)
2. 与Replica交互,具体过程见Replica
* Bootstrap:*: 当有新的节点加入集群时,该类保证新的节点与现存节点建立联系并保证状态的同步。负责发送Join信号给Replica并接收Welcome反馈信号。
Seed:用于启动集群,实例化时传入一个起始node和初始状态。启动的过程就是新节点逐渐加入的过程。同样负责接收来自Bootstrap的Join信号并反馈Welcome信号。

各个类关系图

Multi-Paxos

代码

代码请参考Dustin J. Mitchell原文。

猜你喜欢

转载自blog.csdn.net/slx_share/article/details/80494529