一个简单的分布式场景
角色说明
- 客户端Client,负责发起请求
- 服务端Server,接收客户端的请求,并保存请求的数据
现在部署了3个Server,每个Server会保存客户端请求的信息,并以日志的方式进行存储。 当客户端向Server_1发起请求后,Server_1会记录这个日志,并同步给Server_2,Server_3保持数据一致性,这个看起来没有什么问题。
但是加入是Client同时请求多个Server呢?看下会有什么问题
如何保证各Server的日志顺序保证一致?如每个服务器最后的log顺序都是log1,log2 或者 log2,log1
简单的数据复制方式肯定是不可以的,例如下面这种情况
最终Server_1日志顺序为log1 log2, Server_2日志顺序为log2 log1,Server_3日志顺序两种都有可能。
这就是分布式场景下经典的一致性问题,Paxos的目的就是为了解决这个问题,让所有Server的数据保持一致。
那么Paxos是如何解决这个问题的呢?也就是接下来要说明的问题。
在了解paxos之前,假如对2pc的概念不太清楚,建议先了解2pc再看paxos,因为最后会发现paxos也是一种2pc的思想,不过进行了一些优化。
Paxos
如果直接讲paxos的各种概念,像Proposal、Proposer、Accepter等,会增加理解的难度,所以我用生活中很常见的一个示例举例说明会好很多。
拍卖
拍卖,相信大家都比较熟悉,稍作修改,我们现在的拍卖流程是这样:
竞价部分:
1. 每个人都可以发起竞价,告诉其他人竞价的筹码。
2. 同理,其他人发起竞价时,每个人也会收到竞价信息,有两个可能
a. 假如竞价筹码 <= 最大筹码,回复(no)
b. 假如竞价成功 > 最大筹码,记录最大筹码 = 竞价筹码
ⅰ. 如果商品没被购买,回复(yes,null)
ⅱ. 如果商品已被购买,回复(yes,购买筹码,购买人信息)
3. 当一个人发起竞价后,会收到其他人的竞价结果的回复,超过一半人告诉你竞价成功(少数服从多数),就可以发起购买的请求了(会告诉其他所有人)。
复制代码
购买部分:
1. 发起购买时候,携带两个信息(购买的筹码,个人信息)
2. 当收到其他人的购买请求后,有两种可能:
a. 如果 购买筹码 >= 最大筹码,记录购买者的购买筹码和个人信息,然后回复(yes)。
b. 如果 购买抽买 < 最大筹码,回复(no)。
3. 收到购买回复后,如果为超过半数yes,则表示购买成功,结束流程。反之,提高筹码,重新发起竞价。
复制代码
最后要到达的效果是:每个人记录的商品购买筹码和个人信息应该是相同的。
流程说明
先看看没有竞争对手的情况下,如何运行的,了解概念后,现在改用专用语表示。
概念说明
- Propose : 等同筹码
- Proposer : 等同发起竞价和购买请求的惧色
- Acceptor : 等同接收请求的角色
变量说明
- id:表示筹码的大小
- max_id:表示当前记录的最大筹码
- acc_id:表示已购买的筹码
- acc_val:表示购买人信息
步骤一:协商
收到yes时候,acceptor会将acc_id
和 acc_val
返回
- 假如acc_val都为null,说明还没有设置过val,则将自己的val在第二阶段发送给acceptor
- 存在acc_val不为null的情况,proposer必须无条件使用acc_id最大的acc_val作为第二阶段发送的val
if (id > max_id){
max_id = id;
reutrn (yes, acc_id,acc_val);
} else {
return (no);
}
复制代码
步骤二:提交
当步骤一中超过半数的yes时候,进入步骤二:
if (id >= max_id){
max_id = id;
acc_id = id;
acc_val = val;
return yes;
}else {
return no;
}
复制代码
接收到达响应的结果有两种
- 收到超过一半的yes,流程结束。
- 少于一般的yes,则自增id,重新执行步骤一。
至此,paxos的流程就走完了,对于发生竞争的情况,paxos是如何做到保持一致的,我的建议是根据上述的判断条件,自己动手模拟下(当初也是自己模拟后,帮助我更快的理解了整个流程)。
参考
- 《凤凰架构》周志明,第6章 Paxos
- 《软件架构设计》余春龙