lab2单次测试结果go_race_test_6_824_raft.html

2022 Lab1

遇到的小问题:生成的中间文件要删掉;

总体流程:

  map 任务放入 channel,工人从协调器处 channel 取任务(保证并发安全),由协调器决定当前阶段为 map 还是 reduce,工人处理并按照 ihash 分桶后保存在本地后发起完成任务的rpc,协调器将对应任务id的数组置为 完成任务 状态;在协调器发出任务时,启动一个定时器,并且开一个goroutine监听,如果接受任务的map崩溃了,没有在10s内返回 ack,则协调器将对应任务重新放入队列供其他工人拿取,并重复流程;工人返回done给协调器后,协调器直接在done数组置位,只可能false -> true,不可能反过来;当最后协调器没有任务,但是ack还没收集完时,其他work的请求会返回一个waiting的状态,work会sleep1s;   reduce阶段和map类似,完成map后将对应任务放入channel中,一样的处理,使用的done数组和map阶段的是同一个(一开始make时直接取reduce的大小),完成后删除中间文件,并退出,工作线程不会收到结束信号,当它连接不上协调器时默认任务结束,就关闭了;

老师(好像是助教)的解法:

  发任务直接加锁后,遍历数组拿对应编号;   条件变量让协调器等待,因为代码的RPC调用没有设置超时时间,所以当协调器c.cond.Wait()时,工人会阻塞等待rpc的返回,相比于work循环发起rpc,减少网络流量,效果是一样的;   关于读写加锁的问题:写有锁,但是读没锁时,会发生数据竞争 –> 是因为当两个或多个 goroutine 同时访问同一块内存区域,并且至少有一个是写操作时,如果没有正确的同步操作来确保它们之间的正确执行顺序,会导致未定义的行为。

2022 lab2

后面具体抽离的apply的逻辑等,没有记录,直接coding了; 逻辑分析放在了L8里;

PartA

  1. rf的初始化要记得;
  2. ticker的重置分为收到心跳包后或者节点是leader,这里的实现是本代码使用channel,当收到心跳包并确认信息无误后,向channel发送信号,ticker检查channel是否有数据,有则重置,无则发起选举;
  3. 注意胜选后只需开启一个goroutine发送心跳包,这里使用sync.Once实现;

PartBCD

  1. 主要是start里:
  2. 
    func (rf *Raft) Start(command interface{}) (int, int, bool) {
        term, isLeader := rf.GetState()
        index := rf.CommitIndex + 1 //注意此处要+1,因为是leader确定下一个需要被提交的日志的index
        ...
    }
    

    要+1一直没发现,浪费了好久时间,日志看复制提交日志条目都是正常的,,

  3. applyMsg要在节点提交日志后发送对应ok的信息;
  4. applyMsg := ApplyMsg{
    CommandValid: true,
    Command:      rf.Log[rf.CommitIndex].Command,
    CommandIndex: rf.CommitIndex,
    }
    rf.ApplyMsg <- applyMsg
    
  5. follower日志不可能新过leader(对于已提交的日志而言);
  6. 注意nextIndex胜选后初始化为领导者最后一个日志+1;
  7. 请求append和心跳应该在一起发送;
  8. 落后的follower,通过leader的nextIndex前移,找到follower最新日志在leader的nextIndex的位置时,通过心跳包将其发送出去,由此follower正常接收后会返回true,nextIndex右移,如果此处leader的log有日志,则再次将其发送给follower,直到follower追上leader;
  9. 考虑怎么同步提交日志;设计了一个结构体,日志index和对于的map存是否达成大多数,但是map一直计数不正确; -> 小心循环,初始化要放在循环外;
  10. 一定要打开-race
  11. 出现的bug:首先第一个影响大的bug:当 follower 正常投票后,如果 follower 没有收到响应(投给某一个 candidate 后得到的响应),那么此时votedFor在接收到下一个 appendRPC 前都是已经投票的状态;所以避免这种问题,如果下一个投票请求任期高于当前任期,则直接向他投票;第二个影响不大bug:leader成功当选后我先写间隔200ms才发送 appendRPC (后面改回100ms),导致 leader 还没发送就挂了,这里有点时间差,应该立刻发送的;
  12. 如果leader发来的日志,就是当前follower拥有的最新日志,任期相同,则直接返回false;如果任期不同,直接强制覆盖掉follower多余的部分;
  13. 问题,正常情况下follower追加日志后返回true,leader会增加nextIndex和大多数的计数,如果follower收到重复的请求,如果此时返回false则会被认为nextIndex回退?如果返回true,会导致重复计数;
  14. 不应该用map存储日志同步的大多数的情况,应该使用nextindex或者matchindex来判断是否达成大多数;
  15. leader不能主动提交旧日志;
  16. leader要及时转换回follower;
  17. 有可能日志同步慢了点,包括追等过程 -> 导致的fail;
  18. 使用ctx终止goroutine后,需要重置,ctx用一次cancel貌似就不能再用了;
  19. 如果在snapshot时加锁:提交L还没完成就调用snapshot,提交L阻塞在apply chan上了,导致snapshot也阻塞在拿锁,解决方法:将apply抽离出去,开一个goroutine使用条件变量监听;
  20. 修复选举时bug -> 判断日志最新的依据应该先判断两个日志的任期,大的新,如果相同,则再判断日志索引,长的新;
  21. 修复追日志bug -> 当follower的日志的index和leader的不匹配时,即此时follower有过多过期的日志,任期落后于leader发送的日志,但是index短于当前follower的最新日志(最新换句话说是最长的,该日志是过期的,应该被覆盖),所以此时判断follower需要的日志索引应该是当前commit后的一个日志(暂时,感觉这样会有冗余);
  22. 对于leader而言,不可以主动提交旧任期的日志;
  23. 不应该用map存储日志同步的大多数的情况,应该使用nextindex或者matchindex来判断是否达成大多数;