Blog | CZCZCZ

Stay Hungry.Stay Foolish.

MIT 6.5840 Lab2 建议、记录与总结

建议开始前的准备工作: 整个Lab2完成后,感觉踩过的很多坑和一些奇怪的错误都是因为没有好好读实验要求建议和推荐的文章导致的,尤其是 Raft论文 中的 Figure2 ,里面的每一行要求都是 必须 完成的,基本上完成了Figure2和Figure2部分说明的要求大体框架部分就完成了,但部分细节和实验测试点仅仅实现论文里要求的内容是不够的,这些要求都在Lab2页面里提到了。 下面这些文章和资料基本可以解决你遇到的大部分问题: Debugging by Pretty Printing这是助教提供的DEBUG脚本,里面大部分是讲他怎么开发的脚本,强烈建议使用这个测试脚本和Util里面提供的DPrintf进行调试,不然2B 2C大量日志够受的,这里我们重点看一下使用方法 [!NOTE] 参数选项 --sequential / -s: 按顺序运行每个测试组中的所有测试。默认情况下,测试是并发运行的。 --workers / -p <数量>: 指定并行任务的数量。默认为1。 --iter / -n <次数>: 设置要运行的迭代次数。默认为10次。 --output / -o <路径>: 指定输出路径。如果没有指定,则不会保存输出结果。 --verbose / -v: 设置详细程度。每多一次-v,输出的信息就更详细。默认为0。 --archive / -a: 保存所有日志,而不仅仅是失败的测试日志。 --race / --no-race / -r / -R: 启用或禁用竞态检测器。默认情况下,不启用竞态检测。 --loop / -l: 持续运行测试。每次迭代后,迭代次数将根据增长率调整。 --growth / -g <比率>: 设置在使用--loop时迭代次数的增长率。默认为10。 --timing / -t: 报告运行时长。只在macOS系统上有效。 Students’ Guide to Raft :: Jon Gjengset 另一个助教关于Raft,其中 An aside on optimizations 关于NextIndex快速找到冲突下标的方法一定要实现,这里论文只提了一下,不然在2C的时候容易出现 One(XXX) Failed to reach agreement. nil.csail.mit.edu/6.5840/2023/labs/raft-locking.txt 关于锁使用的建议,如果对锁不是很熟悉的话一定要熟读文章,上锁的时候一定要提醒自己,拿锁的时候可能会遇到长时间阻塞,拿锁后记得检查状态是否变化,遇到需要等待的操作时一定记得解锁。 nil.csail.mit.edu/6.5840/2023/labs/raft-structure.txtRaft架构设计建议。 nil.csail.mit.edu/6.5840/2023/notes/raft_diagram.pdfRaft交互示意图。 运行测试时记得使用 -race检查数据竞争,发生数据竞争可能会导致无法预测的结果。 每个Lab下面的Hint一定认真阅读。 ...

March 19, 2024 · 4 min · 821 words · CZCZCZ

MIT 6.5840 Lab3 记录

前言 Lab3主要是让我们实现一个单体的KV服务器,我是做完Lab4以后再回来对Lab3做的复盘,时间有点久远部分细节记不太清了,总的来说Lab3写起来还是挺舒服的,最后一个测试的网页可视化工具对我们DEBUG也很方便,这里稍微复习和记录一下写Lab3时踩过的大坑。 写Lab4之前强烈建议多测试几遍Lab3的代码,Lab4的分布式KV很多代码和思想都可以复用Lab3,在这里打好基础后后面的Lab4就会舒服许多,可以专心实现Lab4的分布式细节. 一定多看实验页面给的Hint 线性一致性 Lab3最核心的部分就是对线性一致性的理解。具体定义可以看这两篇文章 Testing Distributed Systems for Linearizability nil.csail.mit.edu/6.5840/2023/notes/l-linearizability.txt 简而言之可以看这张图,有四个操作和四个客户端,他们操作耗时分别不一样,在分布式系统中,先开始的操作有可能再后开始操作之后才结束,而本次实验线性一致性的定义就是在这四个操作时间中,能找到 对应的时间点使得结果符合我们的预期 。比如看这幅图片,在这四个操作中无论什么时候开始结束,我们都能找到Put("x", "0"), Get("x") -> "0", Put("x", "1"), Get("x") -> "1",这就符合线性一致性。 再看这幅图,无论我们怎么尝试都无法画出时间点使得结果合理。 那么如何实现线性一致性呢? 就是利用我们的Raft,使得单体KV服务器群就操作(Get ,PutAppend)达成顺序一致共识。 Get操作也要放到Raft中实现一致性! Lab3的核心就是让我们设计实现符合线性一致性的KV系统。 重复检测(Replicate Detection) 看这篇实验给的文章就够了 nil.csail.mit.edu/6.5840/2023/notes/l-raft-QA.txt 每个Clerk客户端都应该有一个自己的独特ID和请求序列号,请求序列号用来标注这是当前clerk的第几次操作,用于在服务端那边过滤重复请求。 为什么要这么做呢? clerk将请求发送到server时要通过start函数将操作共识到Raft中,这个过程是有可能会失败(可能发生Leader更替等情况)的,对于clerk而言不知道是否会失败,所以在共识后如果发生超时,clerk会进行重新尝试,这个时候就有可能导致操作被执行两次,所以我们要在server端为每个clerk维护一个LastSeq记录每个clerk最后一次成功应用的操作,clerk的相同的操作的序列号不会变,操作完成后才允许增加自己的操作ID。server端通过对比发来的seq操作序列号判断这个操作有没有执行过。 同样,针对Get请求,我们也需要维护一个DuplicateTable,用来返回查询结果。为什么Get这种不会造成值变化的操作也要维护呢?这是为了满足线性一致性。 只要执行成功,就应该返回对应时间点的值 例子: C1 C2 put(x,10) first send of get(x), reply(10) dropped //回复10丢失 put(x,20) re-sends get(x), server gets 10 from table, not 20` // 重新发送get请求,此时还应该返回10而不是20 启动一个新协程执行来自applyCh共识结束的命令 func (kv *KVServer) ExectuteOp() { for { msg := <-kv.applyCh //如果是旧的日志,直接跳过 if kv.LastApplyIndex > msg.CommandIndex && msg.CommandValid { continue } //只能安装比上次快照大的index if kv.LastSnapshotIndex > msg.SnapshotIndex && msg.SnapshotValid { continue } if msg.CommandValid { op := msg.Command.(Op) kv.mu.Lock() //泛型判断操作类型 if op.Type == GetType { args := op.Cmd.(GetArgs) //如果操作已经被执行过 if args.Seq <= kv.LastSeq[args.ClientID] { kv.mu.Unlock() continue } if _, ok := kv.DupGetTab[args.ClientID]; !ok { kv.DupGetTab[args.ClientID] = make(map[int64]GetReply) } kv.DupGetTab[args.ClientID][args.Seq] = GetReply{Value: kv.Data[args.Key], Err: OK} kv.LastSeq[args.ClientID] = args.Seq } else if op.Type == PutType { args := op.Cmd.(PutAppendArgs) //如果操作已经被执行过 if args.Seq <= kv.LastSeq[args.ClientID] { kv.mu.Unlock() continue } kv.Data[args.Key] = args.Value kv.LastSeq[args.ClientID] = args.Seq } else if op.Type == AppendType { args := op.Cmd.(PutAppendArgs) if args.Seq <= kv.LastSeq[args.ClientID] { kv.mu.Unlock() continue } if _, ok := kv.DupGetTab[args.ClientID]; !ok { kv.DupGetTab[args.ClientID] = make(map[int64]GetReply) } kv.Data[args.Key] += args.Value kv.LastSeq[args.ClientID] = args.Seq } kv.LastApplyIndex = msg.CommandIndex kv.mu.Unlock() } else if msg.SnapshotValid { //Snapshot kv.mu.Lock() kv.readPersist(msg.Snapshot) kv.mu.Unlock() } } } Get PutAppend 客户端代码 ...

4 min · 768 words · CZCZCZ