本文来自知行学社视频:https://www.bilibili.com/video/BV1Lt411m7cW/
问题
一段数据更新序列:[op1, op2, … , opi],要在所有节点中对这个序列建立共识,确定第i个操作opi是什么。
这里将var比作opi,proposer提交var即提交操作。
- 角色:
- Acceptor:存储管理var
- Proposer:并发调用API,提交不同的var
- API:propose(var,V) ----> <ok, f> or <error>
- 问题:保证var的一致性,如何管理Proposer的并发执行?
方案一,互斥锁:
通过互斥锁:
P向A请求到互斥访问权限,A才可以接收请求。
存在的问题:P在释放锁之前出现故障,会导致整个系统死锁。
方案二,抢占式访问权:
引入抢占式访问权:
1. A可让某个P的访问权失效,再重新发放
2. P向A申请访问权时,需要指定epoch(越大越新)
3. A采用喜新厌旧的原则:
* 一旦接受大的epoch,马上让旧的访问权失效,不再接收他们的请求
* 给新的epoch发放访问权,只接受新epoch的请求
4. 新epoch可以抢占旧epoch,让旧的epoch访问失效
5. 为了保持一致性,不同epoch的proposer之间采用**后者认同前者**的原则:
* 在肯定旧的epoch无法生成确定性取值时,新的epoch会提交自己的value。不会冲突。
* 一旦旧的epoch生成确定性取值,新的epoch一定会认同此取值,不会破坏。
Acceptor的实现:
-
Acceptor保存的状态:
- 当前var的取值<acceptor_epoch, acceptor_value>
- 最新访问权的epoch:latest_prepared_epoch
-
Acceptor::prepare(epoch):
只接受比latest_prepared_epoch更新的epoch,给予访问权,返回var的值
-
Acceptor::accept(var, prepared_epoch, v):
- 先验证prepared_epoch是不是最新的epoch
- 更新var的取值为:<prepared_epoch, v>
Proposer的实现
propose(var, V)的两阶段实现:
-
第一阶段,获取epoch轮次的访问权和当前var的取值:
- 以当前时间戳为epoch,调用Acceptor::prepare(epoch)获取访问权限和当前var的值
- 不能获取则返回<error>
-
第二阶段,采用后者认同前者的方案执行
- 若获取的var为空,说明旧的epoch没有生成确定性取值,可以提交自己的数据Acceptor::accept(var, prepared_epoch, v),成功则返回<ok, v>;失败返回<error>,说明访问权被更新的epoch占用(也有可能时acceptor故障)
- 若var取值存在,说明是确定性取值,认同它不再更改,返回<ok, accepted_value>
运行过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eCzdpzJI-1638085316971)(新建 Markdown.assets/image-20211128143002603.png)]
总结
可以避免死锁问题,并保证var的一致性,但是存在acceptor的单点故障问题
Paxos
-
在方案2的基础上,引入多个acceptor:
-
Acceptor实现不变。
-
Acceptor采用少数服从多数思路
-
一旦某个epoch的取值f被半数以上acceptor接收,则该值被确定,不会被更改
Proposer的实现
-
propose(var, V)第一阶段不变:
- 获取半数以上acceptor的访问权和对应一组var的取值
-
propose(var, V)第二阶段,采用后者认同前者原则:
-
获取的var都为空,说明旧epoch无法生成确定性取值。努力使<epoch, V>成为确定性取值:
- 向epoch对应的acceptor发送提交取值<epoch, V>
- 收到半数以上成功,返回<ok, V>
- 否则,返回<error>
-
如果var存在取值f,努力使<epoch, f>成为确定性取值:
- f出现半数以上,已经是确定性取值,可以直接返回<ok, f>
- 否则,向epoch对应的所有acceptor提交取值<epoch, f>
假设一种情况:epoch1的取值f是确定性取值(100个acceptor里面,51个接收该值),epoch2获取的过半数acceptor是49个未接收epoch1的acceptor+2个接收的acceptor,epoch2会收到来自那2个acceptor的返回值f,因为后者认同前者,epoch2会认同f,不会更改,并向所有acceptor提交f。
为什么acceptor返回的值不统一?因为epoch1刚刚写入51个acceptor,就被epoch2占用了访问权
-