Paxos算法是Lamport宗师提出的一种基于消息传递的分布式一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一。本文的目的就是带领大家深入浅出理解Paxos算法,理解它的执行流程,然后通过一个例子来了解Paxos算法在分布式系统中起到的作用。如果有能力的同学可以直接拜读原文。
分布式系统通常由异步网络连接的多个节点构成,每个节点有独立的计算和存储。通常来说,分布式一致性一般指的式数据的一致性。比如分布式存储系统,通常以多副本冗余的方式实现数据的可靠存储。同一份数据的多个副本必须保证一致,而数据的多个副本又存储在不同的节点中,这里的分布式一致性问题就是存储在不同节点中的数据副本的取值必须一致。
如果不保证一致性,那么就说明这个系统中的数据根本不可信,数据也就没有意义,那么这个系统也就没有任何价值可言。
在分布式系统中,各个节点之间需要进行通信来保持数据的一致性,而通信的实现方式有共享内存和消息传递两种模型。基于消息传递通信模型的分布式系统,不可避免的会发生下面的错误:机器宕机,进程可能会慢、被杀死或者重启,消息可能会延迟、丢失、重复。那么在这种复杂的情况下该如何保证一致性呢?
而Paxos算法就是如何快速正确的在一个分布式系统中对某个数据的值达成一致,并且保证不论发生任何异常,都不会破坏整个系统的一致性。
注: 这里某个数据的值并不只是狭义上的某个数,它可以是一条日志,也可以是一条命令,根据应用场景不同,某个数据的值有不同的含义。
Paxos算法的目标:在分布式环境下确定一个值,这个值被所有节点承认。
Paxos将系统中的角色分为提议者 (Proposer),决策者 (Acceptor),和最终决策学习者 (Learner):
Acceptors的接受,则称该Proposal被批准。
在具体的实现中,一个进程可能同时充当多种角色。比如一个进程可能既是Proposer又是Acceptor又是Learner。
Propser有两个重要属性,提案编号N, 提案V, 简记 Proposer(N, V)。
Acceptor有三个重要属性,响应提案编号ResN, 接受的提案编号AcceptN, 接收的提案AcceptV, 间记Acceptor(ResN, AcceptN, AcceptV)。
第一阶段: Prepare准备阶段
Proposer: Proposer生成全局唯一且递增的提案编号N,,向所有Acceptor发送Prepare请求,这里无需携带提案内容,只携带提案编号即可, 即发送 Proposer(N, null)。
Acceptor: Acceptor收到Prepare请求后,有两种情况:
第二阶段: Accept接受阶段
Proposer: Proposer收到响应后,有两种情况:
Acceptor: Acceptor收到accept请求后,分为两种情况:
Proposer: Proposer收到ok超过半数,则V被选定,否则重新发起Prepare请求。
第三阶段: Learn学习阶段
Learner: Proposer收到多数Acceptor的Accept后,决议形成,将形成的决议发送给所有Learner。
前面讲述了paxos算法细节,可能你还是不明白paxos算法在实际场景中有什么用处,我们现在讲个实际的使用案例。
我们以一个分布式的KV数据库为例,分析Paxos的应用场景。
3个server组成一个分布式内存数据库,他们的状态必须保持同步,也就是每个server 节点都需要维护有顺序的操作日志,同时保持一致。
- +---+-------------+
- |1 |Put("a", 1) |
- +---+-------------+
- |2 |Put("b", 2) |
- +---+-------------+
- |3 |Put("c", 3) |
- +---+-------------+
-
多个客户端并发在发送请求的时候,服务端多个节点需要协商,选择其中一个大家都认可的数据(指令), 存入到操作表中,这里协商确定指令的过程就是Paxos。
比如我们将paxos算法封装到下面的方法中:
- Op doPaxos(int seq, Op v){...}
-
以上面图示的例子详细分下这个过程:
Paxos算法还是挺难以理解的,如果对整个推导过程感兴趣的话,可以阅读Lamport的原文。
根据前面讲述的Paxos算法流程,不知道大家有没有发现一个问题,如果两个Propser依次提出编号递增的提案,最终回陷入死循环,进入死锁状态,如下图:
我们可以通过选取主Proposer,就可以保证Paxos算法的活性, 这样是ZAB协议的由来。