这是6.824的最后一个Lab了,个人觉得是最难的一个Lab,整个6.824最核心的就是Lab2和Lab4,Lab1是一个MapReduce算是熟悉一下go语言,Lab3算是Lab4的一个铺垫。
4B的难点在于搬迁(Migrate)的实现,即扩缩容后,相应的数据迁移问题,因为用到Raft,所以搬迁同样要同步到Raft集群中。
这部分一开始是自己尝试写的,试图绕过start(config),直接同步迁移命令,最后折腾了半天只能过4个Test,最后还是决定参考了别人的思路。链接:8.MIT 6.824 LAB 4B(分布式shard database) - 简书 (jianshu.com)。
因此,本文只记录自己复现过程中遇到的一个问题。
基本照搬的大佬的代码后,发现自己在TestConcurrent测试中会有超时的问题,甚至有的时候直接死锁了,但是大佬的博客并没有遇见这个问题,不知道是不是因为课程更新,还是说我自己抄代码也没抄明白。。。
- 尝试使用go-deadlock
go-deadlock给了报警信息是,tryPullShard和tryGC互相等待超时,大佬博客的实现中这两个函数类似,使用goroutine和waitGroup,推测应该是Call RPC太慢的问题。
- 定位超时操作
func (kv *ShardKV) myLock() {
kv.mu.Lock()
kv.s = time.Now()
}
func (kv *ShardKV) myUnlock(name string) {
if time.Since(kv.s) > time.Duration(1*time.Second) {
fmt.Printf("%v time out %v\n", name, time.Since(kv.s))
}
kv.mu.Unlock()
}
用上面两个函数替换加锁解锁操作,把占用锁超过1s的任务都暴露出来,运行发现越往后面越容易超时,且超时时间越长。
- 查看goroutine数量
推测是goroutine数量太多导致了系统运行速度减慢,使用runtime.NumGoroutine()查看goroutine数量,发现goroutine数量在300多的时候就会出现超时的现象???
我咋记得goroutine开几千个都不会有问题的呢?
不知道是不是自己的电脑太垃圾的原因,跑几百个goroutine都成问题。
- 不要随便使用go func
把大佬的博客中tryPullShard和tryGC里面的go func都抛弃了,最后更改的代码如下,两个函数都差不多。
func (kv *ShardKV) PullShard() {
for !kv.killed() {
_, isLeader := kv.rf.GetState()
kv.myLock()
if !isLeader || len(kv.need) == 0 {
kv.myUnlock("PullShard")
time.Sleep(30 * time.Millisecond)
continue
}
for shard, configNum := range kv.need {
config := kv.clerk.sm.Query(configNum)
args := MigrateArgs{Shard: shard, ConfigNum: config.Num}
gid := config.Shards[shard]
for _, server := range config.Groups[gid] {
srv := kv.make_end(server)
reply := MigrateReply{}
kv.myUnlock("PullShard")
ok := srv.Call("ShardKV.Migrate", &args, &reply)
if ok && reply.Err == OK {
kv.rf.Start(MigrateData{
Shard: shard,
ConfigNum: config.Num,
Data: reply.Data,
Mseq: reply.Mseq,
})
}
kv.myLock()
}
}
kv.myUnlock("PullShard")
time.Sleep(30 * time.Millisecond)
}
}
最后也算是通过了所有的测试,但是整体运行时间到了200s,大佬博客描述只要120s就可以完成,我也不想纠结这个问题了。
4B前前后后用了一周多的时间,只能感叹分布式程序的调试实在是太费劲了,整个824的课程陆陆续续做了3个多月,确实是学会了Raft算法,还是很有帮助的。
看这一条条的commit,都是泪哇。