2021MIT6.824 Lab2总结:Raft

代码地址

https://github.com/c-toast/6.824-golabs-2021

基础概念

Raft:raft算法是能维护分布式系统日志一致性的算法,其保持一致性的关键在于采用多数人决议。

首先,为了减少server间的冲突并提升系统的效率,raft采用了强leader策略:raft将时间分为若干个不等长的任期,在每一个任期内存在(至多)一个leader server和其他follower server,leader负责接收client的请求并在日志中添加新日志项,其对整个分布式系统的日志拥有绝对的控制权。

当leader添加一个新日志项后,leader会在appendEntry心跳请求中通知各个follower添加该日志项。如果leader发现系统中大多数server都添加了新日志项时,leader提交该新日志项,并且也通知follower提交该新日志项。

当follower发现leader可能断线时,也就是一段时间内没有收到leader的心跳请求时,follower会转变为candidate,向其他所有server发起拉票请求,竞选下一轮的leader。当收到大多数server的选票时,candidate当选为leader,在新的一轮中发起心跳请求,组织分布式系统的工作。

无论是选举leader还是添加新日志项,都采用了多数人决议的方式。因此当系统中存在分区现象时,至多只有一个分区能汇集多数人的投票从而做出决议,这样就避免了脑裂问题:系统中存在两个可用的分区,两个分区的提交日志出现分歧。

以上是raft算法的主要内容,除此之外,还有几个重要问题和细节:

  1. 当follower发现和leader的日志项不一致时怎么办?
    leader对整个系统中的日志拥有绝对掌控权,当follower发现和leader的日志不一致时,以leader的日志为准,即follower会从不一致的位置截断日志,并追加leader发送过来的日志项。
  2. 如何保证已提交的日志项不会被覆盖?
    在raft中,leader对整个系统中的日志拥有绝对的控制权,包括覆写follower已添加的日志项。如果不加限制,就有可能导致已提交的日志被覆盖。为此,raft中做了若干项规定。首先规定,leader不会删除和覆盖自己的日志项。其次,只有拥有最新日志项的candidate可以被当选为leader。后一项规定保证了leader必定拥有已提交的日志项,前一项规定保证了leader不会修改已提交的日志项。由于leader对系统的掌控,进而保证follower的已提交日志项也不会被修改。
  3. 怎么保证拥有最新日志项的candidate当选为leader
    新日志项首先意味着该日志项的任期更新,其次是日志的索引更大。当收到candidate的拉票请求时,server会比较candidate的最新日志项的任期是否大于或等于自己最新日志项的任期,其次会比较candidate最新日志项的索引是否大于或等于自己最新日志项的索引。当二者都满足时,server才会投票给candidate。由于已经commit的日志项必存在于大多数server的日志当中,因而只有同样拥有commit日志项或更新日志项的candidate能通过对日志项的两项检查,从而收集到大多数server的选票,当选为leader。

MIT6.824 lab2 实现细节

  1. 没必要过于区分普通的心跳请求和包含日志项添加的心跳请求。心跳请求本身就应该包含两个功能,其一是通知leader依然在正常运作,其二就是leader根据nextIndex[]中的内容跟follower同步日志项。
  2. 当日志中只含有先前term的日志项时,leader不能更新LeaderCommit也不能尝试commit(日志只能在其对应的term被在该term下拥有绝对控制权的leader来commit,不能在未来的term被commit。这样才能保证commit过后,之后任期的leader必拥有该日志)。只有当leader在日志中添加了新的当前任期的日志项时,leader才能尝试通过appendEntry去和follower协商commit日志。
  3. lab2的test要求必须按照顺序依次commit日志项。这里可能需要一个额外的锁来实现顺序commit。这个锁尽量要和raft的锁分开上锁,不要同时上两把锁,这样能避免死锁问题。
  4. follower收到appendEntry请求时,除了其他的检查外,还要对发过来的日志进行检查,只有发现leader的日志和自己的日志不一致时,才进行截断和添加操作。这里的不一致包括长度不一致和内容不一致。这样做可以避免因为接受到旧的appendEntry请求而导致的日志截断。
  5. 不要在持有锁的状况下向applyChan传递ApplyMsg,在2D实验中这样做会导致死锁。建议使用一个额外的没有拥有锁的goroutine来负责传递操作。
  6. 不要直接用slice索引访问日志项,因为在2D实验中当生成快照后会对日志进行截断,此时slice索引不等于日志索引。在做前三个实验时,最好专门定义一个函数用于访问日志项,这样在做最后一个实验时可以很方便地对访问方法进行修改。
  7. 当读取snapshot时,注意更新commitIndex和applyIndex

其他注意点

可参考这篇文章
https://thesquareplanet.com/blog/students-guide-to-raft/#an-aside-on-optimizations

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值