本文来自知行学社视频:https://www.bilibili.com/video/BV1Lt411m7cW/
一段数据更新序列:[op1, op2, … , opi],要在所有节点中对这个序列建立共识,确定第i个操作opi是什么。
这里将var比作opi,proposer提交var即提交操作。
通过互斥锁:
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::prepare(epoch):
只接受比latest_prepared_epoch更新的epoch,给予访问权,返回var的值
Acceptor::accept(var, prepared_epoch, v):
propose(var, V)的两阶段实现:
第一阶段,获取epoch轮次的访问权和当前var的取值:
第二阶段,采用后者认同前者的方案执行
运行过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eCzdpzJI-1638085316971)(新建 Markdown.assets/image-20211128143002603.png)]
可以避免死锁问题,并保证var的一致性,但是存在acceptor的单点故障问题
在方案2的基础上,引入多个acceptor:
Acceptor实现不变。
Acceptor采用少数服从多数思路
一旦某个epoch的取值f被半数以上acceptor接收,则该值被确定,不会被更改
propose(var, V)第一阶段不变:
propose(var, V)第二阶段,采用后者认同前者原则:
获取的var都为空,说明旧epoch无法生成确定性取值。努力使<epoch, V>成为确定性取值:
如果var存在取值f,努力使<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占用了访问权