MIT 6.5840 (Spring, 2024) 通关指南——Lab 2: Key/Value Server

发布于:2025-09-02 ⋅ 阅读:(20) ⋅ 点赞:(0)

MIT 6.5840 (Spring, 2024) – Lab 2: Key/Value Server

👨‍💻 Charles

🔗 实验手册:6.5840 Lab 2: Key/Value Server

✍️ 本系列前文

该 lab 需要实现一个简单的 K/V 存储系统,由于还没涉及分布式,且底层存储可以直接用 map ,写起来难度不大。貌似也是 6.5840 中唯一一个难度标注为 easy 的 lab 了,直接上代码吧。

完整代码见 github

common.go

// Put or Append
type PutAppendArgs struct {
	Key   string
	Value string
	// You'll have to add definitions here.
	// Field names must start with capital letters,
	// otherwise RPC will break.
	ClerkID int64
	RequestID int64
}

type PutAppendReply struct {
	Value string
}

type GetArgs struct {
	Key string
	// You'll have to add definitions here.
}

type GetReply struct {
	Value string
}

非常简单,唯一要注意的就是为了“客户端请求去重”, PutAppendArgs 需要携带一下客户端 ID 和这次请求的 ID(唯一):

在不可靠网络中,客户端可能会多次重发相同的 RPC 请求。如果服务器简单处理,多次处理同一个请求会导致重复操作 —— 例如一个写操作被执行多次,导致数据不一致或者越界。为了避免这些问题,服务器需做请求去重并返回幂等的结果。因此这里采用一种常见策略:

  • 客户端维护一个单调递增的请求 ID(RequestID),在每次请求 RPC 中带上。
  • 服务器保存每个客户端最近处理过的 lastRequestID 和对应的 lastReply
  • 当它收到一个请求时,如果发现 RequestID = lastRequestID(即重复请求),它就直接返回已保存的 lastReply,而不重复执行。

由于 RequestID 是单调递增,且只有客户端确认收到该请求的回复后,才会让 RequestID 加一,所以不会出现 client's RequestID < server's lastReuqestID[client] 的情况。

详细实现见下面的 server.go

server.go

type KVServer struct {
	mu sync.Mutex

	// Your definitions here.
	db          map[string]string
	lastRequest map[int64]int64    // clerkID -> its last requestID
	lastReply   map[int64]string // clerkID -> its last reply value
}

func (kv *KVServer) Get(args *GetArgs, reply *GetReply) {
	// Your code here.
	kv.mu.Lock()
	defer kv.mu.Unlock()
	reply.Value = kv.db[args.Key]
}

func (kv *KVServer) Put(args *PutAppendArgs, reply *PutAppendReply) {
	// Your code here.
	kv.mu.Lock()
	defer kv.mu.Unlock()

	if kv.lastRequest[args.ClerkID] == args.RequestID {
		return
	}

	kv.db[args.Key] = args.Value
	reply.Value = args.Value
}

func (kv *KVServer) Append(args *PutAppendArgs, reply *PutAppendReply) {
	// Your code here.
	kv.mu.Lock()
	defer kv.mu.Unlock()

	if kv.lastRequest[args.ClerkID] == args.RequestID {
		reply.Value = kv.lastReply[args.ClerkID]
		return
	}

	oldValue := kv.db[args.Key]
	kv.db[args.Key] = oldValue + args.Value
	reply.Value = oldValue
	kv.lastRequest[args.ClerkID] = args.RequestID
	kv.lastReply[args.ClerkID] = oldValue
}

func StartKVServer() *KVServer {
	kv := new(KVServer)

	// You may need initialization code here.
	kv.db = make(map[string]string)
	kv.lastRequest = make(map[int64]int64)
	kv.lastReply = make(map[int64]string)

	return kv
}

client.go

type Clerk struct {
	server *labrpc.ClientEnd
	// You will have to modify this struct.
	id        int64
	requestID int64
}

func nrand() int64 {
	max := big.NewInt(int64(1) << 62)
	bigx, _ := rand.Int(rand.Reader, max)
	x := bigx.Int64()
	return x
}

func MakeClerk(server *labrpc.ClientEnd) *Clerk {
	ck := new(Clerk)
	ck.server = server
	// You'll have to add code here.
	ck.id = nrand()
	return ck
}

func (ck *Clerk) Get(key string) string {

	// You will have to modify this function.
	args := GetArgs{Key: key}
	reply := GetReply{}
	try := 0
	success := false
	for !success {
		try++
		success = ck.server.Call("KVServer.Get", &args, &reply)
		DPrintf("CK[%d] Get[%s], %v; try time: %d", ck.id, key, success, try)
	}
	DPrintf("CK[%d] Get[%s] = '%s'", ck.id, key, reply.Value)
	return reply.Value
}

func (ck *Clerk) PutAppend(key string, value string, op string) string {
	// You will have to modify this function.
	args := PutAppendArgs{
		Key:       key,
		Value:     value,
		ClerkID:   ck.id,
		RequestID: atomic.AddInt64(&ck.requestID, 1),
	}
	reply := PutAppendReply{}
	success := false
	try := 0
	for !success {
		try++
		success = ck.server.Call("KVServer."+op, &args, &reply)
		DPrintf("CK[%d] %s[%s]='%s', %v\ntry time: %d\nrequestID: %d",
			ck.id, op, key, value, success, try, args.RequestID)
	}

	return reply.Value
}

func (ck *Clerk) Put(key string, value string) {
	ck.PutAppend(key, value, "Put")
}

// Append value to key's value and return that value
func (ck *Clerk) Append(key string, value string) string {
	return ck.PutAppend(key, value, "Append")
}

实验结果

用测试脚本跑 2000 轮(打开数据竞态检测),全部通过:

请添加图片描述

测试脚本配置方法见 MIT 6.5840 (Spring, 2024) 通关指南——入门篇-CSDN博客


网站公告

今日签到

点亮在社区的每一天
去签到