MIT 6.824 分布式系统导论: lab5 Persistence实现设计

来源:互联网 发布:怎样解除红蜘蛛网络 编辑:程序博客网 时间:2024/06/06 06:30

lab5 Persistence

CSDN使用markdown语法编辑的博客,格式看起来,不是特别舒服,可在github上看此博客。

lab5要求在lab4的基础上,增加数据持久化的功能,从而支持节点在dead之后能够根据磁盘数据进行恢复

针对恢复时,磁盘的数据可能发生丢失,需要考虑以下恢复场景:

  • 本地同步:磁盘数据未丢失,节点同步本地数据后,再次提供服务。
  • 异地同步:磁盘数据丢失,节点同步其他节点的数据后,再次提供服务。

1. 数据持久化

1.1 paxos数据固化

  paxos被用于决策操作之间的执行顺序,每个节点的内存中记录了paxos实例状态,对于这些数据,我们需要在磁盘上记录下来。

  同时当前节点的状态,比如,min_done(当前集群中已经丢弃的最大实例号)这些信息也需要记录下来。

策略:

  • 每个实例的状态均使用一个单独的文件进行存储,以”inst_status-提案号”命名。
  • paxos节点的状态使用一个单独文件进行存储,以”paxos_status”命名。

1.2 key-vaule server数据固化

  对于每个副本集,其上执行的操作(get,putappend,view change等)均来自于paxos的抉择,而所有这些抉择的操作均保存在paxos的日志当中,由于paxos端会解决提案的固化,因此在k-v server端,我们只需考虑执行操作后,该节点状态发生的改变。这些改变我们可以分为两类:分片数据的改变和本节点状态的改变(执行的操作编号,config的状态等)。

策略:

  • 对k-v节点的状态使用一个单独文件进行存储,以”diskv_status”命名。
  • 对每个分片的数据使用一个单独文件命名,以”shard_编号”命名。(这里也可以采用为每个分片创建目录,以”key”作为文件的方式进行数据持久化)

1.3 更新频率

  对于paxos中数据的更新,为了做到“有迹可循”,采用“立即更新”的方式。比如,在prepare,accept阶段做出的回答,状态的更新,为了避免数据的丢失,均需在回复之前,将数据的状态更新到磁盘上。

  对于k-v server中数据的更新,为了减少频繁更新带来的性能下降,同时考虑到服务器大多数时间段是正常运行的,采用“周期更新”的方式,周期性将数据更新到磁盘上,这样能够有效减少写的次数。

  采用”周期更新”的方式,意味着只有真正将执行操作的结果写到磁盘上后,这些执行过的操作才不会出现redo的情况,因此,通知paxos已经完成操作的时机应该放在”周期更新”成功之后。

2 启动初始化

对于刚启动的节点,需分别处理三种不同的情况:

  • 初次启动
  • 重启,磁盘数据未丢失 —> 重启,磁盘数据可用
  • 重启,磁盘数据丢失 —> 重启,磁盘数据不可用

     而在这里,我并不是想具体阐述这三种情况的具体操作过程,而是想强调需要记录初始化是否成功。如果初始化成功后,我们需要在磁盘上记录已经成功初始化。

为什么需要这样做?

  考虑一下,如何进行磁盘数据未丢失的判断?单纯通过文件目录下是否存在数据么?显然这种方式,不太靠谱!因为初始化阶段存在固化数据的情况。比如,在”异地同步“的处理中,由于本地磁盘数据的丢失,需要从其他节点获取需要的数据,而这些数据在收到之后,第一时间,我们应该把它们持久化(防止再次的机器宕机),那这里,问题就来了,如果在持久化的过程当中,该节点再次宕机,而此时数据只写了一部分,当再次重启时,发现文件目录下存在数据,故使用本地磁盘上的数据进行恢复,而这导致了数据不一致

  因此,对于磁盘上数据丢失的判断,我们不仅需要检查是否存在数据,同时需要验证这些数据是否是某个状态完整的数据集。解决这个问题的方法很简单,增加一个"init"文件记录初始化结果,在初始化成功后,向该文件写'1'。重启的时候,通过判断该文件的数据是否为'1'即可。

  虽然,lab5的测试当中,并未考虑杀死刚启的节点,不过真实场景是有这种情况的,希望以后有这方面的测试吧。

3 本地同步

  当节点重启,在磁盘数据可用的情况下,节点只需从磁盘上获取数据,然后同步到内存当中即可。唯一需要注意的是,由于在key-value server端需要同时更新多个文件(diskv_status和shard_i),我采用了"事务写"的方式,在读取本地数据之前,如果存在未完成的"事务写"操作,需要先执行完未成功执行的操作后,才能开始进行恢复。

整体步骤:

  • 同步paxos的相关数据
  • 如果存在未完成的”事务写”
    • 执行未完成的文件修改操作
    • 通知paxos已经完成的paxos实例,更新本地节点的done
  • 同步key-value server的相关数据

关于”事务写”的具体操作,请见后。

4 异地同步

  该问题,属于本实验的一个难点:获得某一正常节点的paxos状态,k-v server状态之后,该节点是否就能够正常提供服务?

答案当然是否定的,lab5提示当中已经指出了下述问题:

If a server crashes, loses its disk, and re-starts, a potential problem is that it could participate in Paxos instances that it had participated in before crashing. Since the server has lost its Paxos state, it won’t participate correctly in these instances. So you must find a way to ensure that servers that re-join after disk loss only participate in new instances.

  这个问题不难理解,对于还未确定的paxos实例,恢复节点不能直接使用其他节点的实例状态,因为这可能违背了它之前所作出的决策。比如,有5台机器状态为:

[ <10,a> <10,a> <10,a> <8,b> <8,b> ] #最终确定 a[ <    > <    > <10,a> <8,b> <8,b> ] #前两台重启,数据丢失[ <8 ,b> <8 ,b> <10,a> <8,b> <8,b> ] #获得<8 ,b>,此时最终可能获得b

  既然该节点的paxos对曾经做出过响应的提案,不能再进行响应,如果能够通过获知它曾经对哪些提案做出过响应,而针对这些提案,恢复节点的paxos不再做出响应?

  这个想法简单,但是实际操作是无法实现的,获知曾经对哪些提案做出过响应,这一点就无法实现。那么,退而求其次,获知该节点dead前,paxos所有节点中提交的最大实例号。

实现方法:

  1. 维护最大实例号
    通过修改paxos,维护一个值high_seq, 表示该节点知道的最大实例号。在prepare阶段,将high_seq的值携带在原有的请求当中,相应的,在处理prepare请求时,需要更新high_seq的值。

  2. 获取最大实例号
    恢复节点询问其他节点,获取其知道的最大实例号high_seq,如果有majority的节点回复了请求,则成功,否则继续询问其他节点。
    max_high_seq是所有回复中的最大high_seq,即为所求。

  3. 强制decided实例
    当恢复节点得到max_high_seq后,通过自身的paxos节点不断提交空的提案,这期间该paxos不对任何prepare请求,accept请求进行处理,只对decide请求进行处理。

    当 <=max_high_seq的实例,都已经decided了,这个时候,paxos可以再次提供服务。

总结下,该阶段的步骤:

  1. 设定paxos只对decide请求进行响应
  2. 获取最大实例号,当majority节点回复,则成功,否则继续询问。
  3. 随意获取一个节点当前的paxos,k-v server的相关状态。
  4. 将收到的数据进行固化
  5. 提交空操作,强制<=max_high_seq的实例被decided。
  6. 恢复结束

5 关键技术点

5.1 事务写

使用文件batch_log记录事务写的状态,其中1,表示有未完成的事务写。

写步骤

  1. 初始化:向batch_log0
  2. 将数据记录到磁盘,以batch-作为文件名前缀。(batch-diskv_status)
  3. 当所有文件写成功后,向batch_log1
  4. 将以batch-作为文件名前缀的文件,更名为正确的文件名。(batch-diskv_status -> diskv_status)
  5. 结束:向batch_log0

恢复步骤
恢复时,如果发现batch_log的值为1,则表示之前的事务写未完成,执行第4,5步即可。

5.2 GC:Push与Pull的抉择

  受之前paxos的影响, 关于paxos中log回收,采用的将done的值携带在prepare的请求当中。但是在这里,虽然lab5的测试无法测出来,这种方式是不正确的!

Trick

done表示节点已经完成的操作
如图所示:五台机器,节点0: done=80,节点1: done=60,其他类似。

[done]: 该节点被其他节点获悉的done值                    |                      |                            done          |                      |       |---|                                      |  [80]                |       v   |80  60  90  90  90  |   60  60  90  90  90 |   60  90  90  90  90 ^                   |   ^   |              |  [80][90]                    |   |---|              |                         [0]         |           [1]        |          [2]             
  • 阶段0:节点0重启,磁盘数据不可用。

  • 阶段1:节点0进行恢复,从节点1获得所需状态,此时节点0:done=60,但是其他节点依然获悉的done=80.-

  • 紧接着节点1重启,磁盘数据不可用。

  • 节点1进行恢复,从节点2获得所需状态,此时节点1:done=90,这是,可能在节点2上有了新的提案,节点1发送了prepare请求,携带done=90。这是,节点2, 3, 4他们看见5台机器的done值为:[80, 90, 90, 90, 90], 节点2,3,4就会删除80之前的paxos log。

  • 而此时,节点0还需要61 ~ 80之间的paxos log,可惜啥都没有了~~~

对于这个场景,也许你会尝试一些补救,但是相信,都会有相应的方式造成错误。

  探究其原因,是因为每个节点记录其他节点过去某个状态的信息,而节点由于恢复,可能与曾经发送的done值,出现不一致的情况。而最好的改变方式,每次在做GC时,都需要知道,当前其他节点的状态,而不使用以前旧的数据,因此每次做GC,主动去pull其他节点的done值,而不是使用其他节点主动push的done值。

1 0