引子
GFS的三副本要求数据在三个副本都写成功,才会更新元数据,标志数据写入成功,为何不使用多数派机制呢?
这是因为,写多少乍一想非常简单,但是要设计一个正确的、可容错的、严格一致性的多数派写入方案还是非常复杂的,本文给出一个可选的写入方案。
基本背景和名词解释:
实现方案:
冻结可能在两种情况下触发:
-
primary写满(比如64MB)之后,主动发起冻结
-
secondary发现primary lease过期,并且元数据也没有处于冻结状态,也会发起冻结,并补齐多数派上的数据
-
primary写满(比如64MB)之后,主动发起冻结
secondary发现primary lease过期,并且元数据也没有处于冻结状态,也会发起冻结,并补齐多数派上的数据
为了在这一分布式事件中保持数据的一致性和完备性,并说明其正确性,还需引入paxos算法,下面对这一过程和paxos的对应关系和代码简要设计说明:
-
paxos实例
在写入数据到三副本以及冻结的过程中,每个record就是一个实例,每个实例之间是没有关系的,实例的集合是recondid属于[0, 无穷大)
-
primary给secondary同步数据
在这个过程中只有一个proposer,即primary,其被创建时就指定了身份,相当于 primay已经给所有副本以无穷小的一个proposalID发送了prepare请求,后续的record同步都是以这个无穷小的proposalID发送accept请求
-
冻结与multi-paxos
冻结的含义,是把[0, 无穷大)集合中所有实例都“确定”下来,其中要分为两个集合,(0, MaxRecordID] 这个集合的实例的值已经确定,(MaxRecordID, 无穷大)这个集合的值全部确定为NULL,注意,空值也是一个确定的值。
其中,primary写满一个partition之后,发送“tail”标志给secondary,相当于以无穷小的proposalID发送(MaxRecordID, 无穷大)区间的所有paxos实例的ACCEPT请求,这些实例的值都为NULL。
secondary发起冻结,相当于这个paxos group中有了新的proposer,需要用新的proposalID发送(StartRecordID, 无穷大)范围的prepare请求,其中,StartRecordID即为本地最大RecordID。根据paxos的约束,所有副本作为acceptor,需要返回本地已经存在的accept记录(即为本地的ID大于StartRecordID的record),收集到多数派的回应后,此proposal已经拥有全量数据,并且可以确定出全局的MaxID,之后可以随同accept请求把这些信息发给多数派。
全局MaxRecordID的值并不是由专门的paxos实例来决定的,而是在确定了(0, 无穷大)这个集合内所有Record的值之后,基于这些paxos实例的结果,推倒出来的。推倒的方法就是找到最大的非NULL值得RecordID。
-
每个replica需要记录的值
-
PPid:已经应答过的最大的Prepare的请求所携带的proposalID,初始化为0。
-
(Apid, maxID):已经应答过的最大的Accept请求的proposalID, 以及此请求所携带的最大得非NULL的RecordID,初始化均为0
-
state: 如果冻结成功则将其设置为FROZEN状态,并将此文件移动到冻结目录
-
每个replica对外暴露的接口以及相关实现
-
send_record()
primary给secondary(包括自己)同步record,如果PPid不为0,则拒绝;
-
prepare_record(pid,data_range)
如果pid<APid,则拒绝;否则返回返回本地已经位于data_range区间的record,并使得PPid =pid
-
accept_record(pid,maxID,Record_data)
如果pid<PPid,则拒绝,否则将MaxID记录在本地(标记(maxid,无穷大)已经为NULL值,如果有需要还需要将Record_data(其他节点已有而本地没有的Record)写入本地,并使得APid=pid
-
set_frozen(maxID)
某个replica执行frozen成功后,则发送此消息广播给所有replica,收到此消息后,修补或者裁剪本地record数据,修改stat状态,并将此文件移动到冻结目录。
-
冻结的触发以及执行
primary写满之后,直接发送accept_record(0, maxID, NULL)给secondary,收集到多数派回应后,更新元数据,并发送set_frozen()给所有replica;如果primary执行此步骤失败,则放弃自己primary的身份,之后处理逻辑与secondary相同。
secondary发现primary lease过期,并且元数据还长时间处于未冻结状态,则取当前时间戳加ip作为pid发送prepare请求给所有replica,如果收集到多数派的回应则发送accept请求给有过回应的replica,并携带此replica缺失的record.成功之后,需要更新元数据表,将此Partition设置为冻结状态,并广播此消息给所有repliaca。上述过程失败,则定期重试。