当前位置: 首页 > news >正文

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

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

👨‍💻 Charles

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

✍️ 本系列前文

  • MIT 6.5840 (Spring, 2024) 通关指南——入门篇
  • MIT 6.5840 (Spring, 2024) 通关指南——Lab 1: MapReduce

文章目录

  • MIT 6.5840 (Spring, 2024) -- Lab 2: Key/Value Server
    • common.go
    • server.go
    • client.go
    • 实验结果

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

完整代码见 github

common.go

// Put or Append
type PutAppendArgs struct {Key   stringValue string// You'll have to add definitions here.// Field names must start with capital letters,// otherwise RPC will break.ClerkID int64RequestID 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]stringlastRequest map[int64]int64    // clerkID -> its last requestIDlastReply   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.Valuereply.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.Valuereply.Value = oldValuekv.lastRequest[args.ClerkID] = args.RequestIDkv.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        int64requestID 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 := 0success := falsefor !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 := falsetry := 0for !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博客

http://www.dtcms.com/a/360740.html

相关文章:

  • 【Docker】Docker容器和镜像管理常用命令
  • Spring Bean 生命周期中的 @PostConstruct 注解
  • TCP实现线程池竞争任务
  • LeetCode Hot 100 Python (31~40)
  • 运动规划实战案例 | 基于行人社交模型的移动机器人动态避障(附ROS C++仿真)
  • Linux Tun/Tap 多队列技术
  • 【STM32】贪吃蛇 [阶段2](嵌入式进阶方向)
  • 【含文档+PPT+源码】基于SpringBoot+微信小程序的饮水健康之净水器保养管理系统设计与实现【包运行成功】
  • 【Linux】模拟实现Shell(下)
  • 打开模板打印
  • Ajax笔记(下)
  • 《探索C++11:现代C++语法的性能革新(上篇)》
  • 医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(八)
  • Redis 核心概念解析:从渐进式遍历、数据库管理到客户端通信协议
  • 《C++进阶之STL》【红黑树】
  • C语言数据结构之双向链表
  • 基于 DNA 的原核生物与微小真核生物分类学:分子革命下的范式重构​
  • 【JavaWeb】之HTML(对HTML细节的一些总结)
  • Notepad++近期版本避雷
  • 【golang长途旅行第35站】Redis
  • Objective-C 的坚毅与传承:在Swift时代下的不可替代性优雅草卓伊凡
  • 云市场周报 (2025.09.01):解读腾讯云向量数据库、阿里云西安节点与平台工程
  • 从零开始的云计算生活——第五十五天,黑云压城,kubernetes模块之网络组件和CoreDNS组件
  • 数组(3)
  • Proteus8 仿真教学全指南:从入门到实战的电子开发利器
  • GitHub 热榜项目 - 日榜(2025-09-01)
  • 基于YOLOv11的脑卒中目标检测及其完整数据集——推动智能医疗发展的新机遇!
  • MySQL下载及安装(Windows 11)
  • 【LeetCode】3524. 求出数组的 X 值 I (动态规划)
  • 【LeetCode 155】—最小栈 - 详解与实现