MIT-6.5840 Lab2笔记
任务目标
Lab 2
要求我们实现一个和K-V Server。字面意思就是一个键值对存储服务,键值对存储在服务器的内存里。
用于测试的文件在src/kvsrv/test_test.go
,运行命令go test
或go test -race
进行测试。命令go test -v -run ${func_name}
对指定测试样例进行测试,也可以在VSCode中进入文件点击Test按钮
前置知识
文档资料
以下列出完成Lab 1所需要阅读的资料:
- Lab 2 2024文档 介绍了Lab2的任务和一些规范,同时给出了一些实现提示
- MIT 6.824 6.824的官方视频课程,完成Lab 2只需要看第五节甚至不需要看
- Go语言教程 对于有一定编程基础,要快速入门书写Go程序的。这本国内的教程足够,既涵盖了基本语法,也提供了一些常用标准库的API供随时查看
课程程序规范
Lab2主要关注如下文件
- src/kvsrv/client.go 客户端,负责进行RPC调用,只需根据Lab文档进行略微修改
- src/kvsrv/server.go KV Server的定义,需要在这里完成各操作的具体实现
- src/kvsrv/common.go 一些公用的类型定义
- src/kvsrv/test_test.go 测试文件,一般不需要动。如果某个测试样例过不去可以看该样例的函数,了解该样例大概需要完成什么任务,有助于Debug
实现思路
数据结构
Clerk
type Clerk struct {
server *labrpc.ClientEnd
// You will have to modify this struct.
instructionCounter InstructionId
clientId ClientId
}
Clerk就是客户端,server已经为我们提供,用于发送RPC请求
ClientId
是客户端的唯一标识,由一个全局变量ClientCounter
计数,由于Lab中Client不会并发创建,就不需要加锁
instructionCounter
是本客户端的指令计数器,为该客户端的指令提供唯一标识,由于Lab中Client不会并发发送RPC请求(见Task2的Hint),就不需要加锁
CacheData
type CacheData struct {
ClientId ClientId
InstructionId InstructionId
value string
}
Task2要求我们对Client的重复请求进行过滤,因此我们需要做一个类似日志/缓存的队列保存过去的请求和请求结果,确保一致性。我使用ClientId
和InstructionId
来作为请求的唯一标识,同时记录请求的返回值。ClientId
和InstructionId
两个类型是Int16
的Alias。
KV-Server
type KVServer struct {
mu sync.Mutex
// Your definitions here.
kvState map[string]string
InsCache map[ClientId][]CacheData
}
Lab要求我们在内存中保存KV Pairs,显然使用map是最合适不过。对于缓存我为每个Client创建了一个缓存队列,保存该Client的请求缓存。写到一半发现其实完全没必要做缓存队列,因为本Lab中其实每个Client只会有一个请求缓存,因为Client在请求失败后会阻塞不断地尝试同一个请求,此请求前的其他请求对它已经过时了没有用(见Task2)。这里可以修改成map[ClientId]CacheData
KV-Server
实现起来其实没有什么太明显的难点,这里把要注意的地方列出来,相信可以解决读者的瓶颈:
Append
函数要返回的是操作前的Value值而不是append后的新值- KV-Server的状态在读写时记得加锁,由于Cache和State是紧密同步的,要把他们的分布更新作为一个原子操作。即放在同一段临界区
- Clerk的每个操作都用for循环包裹直至成功