MIT 6.5840(6.824) Lab2:Key/Value Server 设计实现

1 实验要求

在本次 Lab 中,你将在单机上构建一个键/值服务器,以确保即使网络出现故障,每个操作也只能执行一次,并且操作是可线性化的。

客户端可以向键/值服务器发送三个不同的 RPC: Put(key, value)Append(key, arg)Get(key) 。服务器在内存中维护键/值对的map。键和值是字符串。 Put(key, value) 设置或替换map中给定键的值, Append(key, arg) 将 arg 附加到键的值并返回旧值, Get(key) 获取键的当前值。不存在的键的 Get请求应返回空字符串;对于不存在的键的 Append 请求应该表现为现有值是零长度字符串。每个客户端都通过ClerkPut/Append/Get 方法与服务器进行通信。 Clerk 管理与服务器的 RPC 交互。

你的服务器必须保证应用程序对Clerk Get/Put/Append 方法的调用是线性一致的。 如果客户端请求不是并发的,每个客户端 Get/Put/Append 调用时能够看到之前调用序列导致的状态变更。 对于并发的请求来说,返回的结果和最终状态都必须和这些操作顺序执行的结果一致。如果一些请求在时间上重叠,则它们是并发的:例如,如果客户端 X 调用 Clerk.Put() ,并且客户端 Y 调用 Clerk.Append() ,然后客户端 X 的调用 返回。 一个请求必须能够看到已完成的所有调用导致的状态变更。

一个应用实现线性一致性就像一台单机服务器一次处理一个请求的行为一样简单。 例如,如果一个客户端发起一个更新请求并从服务器获取了响应,随后从其他客户端发起的读操作可以保证能看到改更新的结果。在单台服务器上提供线性一致性是相对比较容易的。

Lab 在 src/kvsrv 中提供了框架代码和单元测试。你需要更改 kvsrv/client.gokvsrv/server.gokvsrv/common.go 文件。

2 无网络故障的KV Server

2.1 任务要求

此任务需要实现一个在没有丢失消息的情况下有效的解决方案。你需要在 client.go 中,在 Clerk 的 Put/Append/Get 方法中添加 RPC 的发送代码;并且实现 server.go 中 Put、Append、Get 三个 RPC handler。

当你通过了前两个测试 case:one client、many clients 时表示完成该任务。

2.2 设计实现

这个任务比较简单,我们只需要根据实验要求的逻辑进行实现即可。

  • server.go

    使用map保存键值信息,三种操作都需要通过锁来保证互斥访问共享map

    func (kv *KVServer) Get(args *GetArgs, reply *GetReply) {kv.mu.Lock()defer kv.mu.Unlock()reply.Value = kv.data[args.Key]
    }func (kv *KVServer) Put(args *PutAppendArgs, reply *PutAppendReply) {kv.mu.Lock()defer kv.mu.Unlock()kv.data[args.Key] = args.Value
    }func (kv *KVServer) Append(args *PutAppendArgs, reply *PutAppendReply) {kv.mu.Lock()defer kv.mu.Unlock()oldValue := kv.data[args.Key]kv.data[args.Key] = oldValue + args.Valuereply.Value = oldValue
    }
    
  • client.go

    只需要添加RPC的发送代码。

    func (ck *Clerk) Get(key string) string {args := &GetArgs{Key: key,}reply := &GetReply{}ck.server.Call("KVServer.Get", args, reply)return reply.Value
    }
    func (ck *Clerk) PutAppend(key string, value string, op string) string {arg := &PutAppendArgs{Key:   key,Value: value,}reply := &PutAppendReply{}ck.server.Call("KVServer."+op, arg, reply)return reply.Value
    }func (ck *Clerk) Put(key string, value string) {ck.PutAppend(key, value, "Put")
    }
    

3 可能丢弃消息的KV Server

3.1 任务要求

现在,您应该修改您的解决方案,以便在遇到丢失的消息(例如 RPC 请求和 RPC 回复)时继续工作。如果消息丢失,则客户端的 ck.server.Call() 将返回 false (更准确地说, Call() 等待响应直至超市,如果在此时间内没有响应就返回false)。您将面临的一个问题是 Clerk 可能需要多次发送 RPC,直到成功为止。但是,每次调用 Clerk.Put()Clerk.Append() 应该只会导致一次执行,因此您必须确保重新发送不会导致服务器执行请求两次。

你的任务是在 Clerk 中添加重试逻辑,并且在 server.go 中来过滤重复请求。

Hint
  1. 您需要唯一地标识client操作,以确保KV Server仅执行每个操作一次。
  2. 您必须仔细考虑server必须维持什么状态来处理重复的 Get()Put()Append() 请求(如果有的话)。
  3. 您的重复检测方案应该快速释放服务器内存,例如让每个 RPC 暗示client已看到其前一个 RPC 的回复。可以假设client一次只向Clerk发起一次调用。

3.2 方案设计

根据提示,我们可以为PutAppend消息添加标识ID(Get消息只需不断重试,不会有影响),这里我们还需要用到sync.Map用于在键/值服务器中跟踪处理过的请求ID,以防止重复处理请求。每当服务器接收到一个新的RPC请求时,它会检查请求ID是否已存在于sync.Map中。如果存在,则表明该请求已经处理过,服务器可以跳过重复的处理,直接返回之前处理过的值。否则,服务器会记录该请求ID处理请求,并将回复结果记录。这种机制确保了操作的幂等性,避免了由于网络故障或重试机制导致的重复执行。

当然,还需要考虑一个问题,就是服务器会不断积压处理过的请求ID信息,所以我们需要快速释放服务器内存,即让Client通知Server这个任务操作已经完成,删除相关的记录信息。故我们还需要给消息结构添加一个Type字段标识为Modify还是Report

整个流程图如下所示:

image-20240515111905159

3.3 代码实现

实验代码实现仓库:https://github.com/unique-pure/MIT6.5840/tree/main/src/kvsrv,实验代码已通过实验测试。

  • common.go

    type MessageType intconst (Modify = iotaReport
    )// Put or Append
    type PutAppendArgs struct {Key   stringValue stringMessageType MessageType // Modify or ReportMessageID   int64       // Unique ID for each message
    }
    
  • server.go

    type KVServer struct {mu sync.Mutexdata   map[string]stringrecord sync.Map
    }func (kv *KVServer) Get(args *GetArgs, reply *GetReply) {kv.mu.Lock()defer kv.mu.Unlock()reply.Value = kv.data[args.Key]
    }func (kv *KVServer) Put(args *PutAppendArgs, reply *PutAppendReply) {if args.MessageType == Report {kv.record.Delete(args.MessageID)}res, ok := kv.record.Load(args.MessageID)if ok {reply.Value = res.(string) // 重复请求,返回之前的结果return}kv.mu.Lock()old := kv.data[args.Key]kv.data[args.Key] = args.Valuereply.Value = oldkv.mu.Unlock()kv.record.Store(args.MessageID, old) // 记录请求
    }func (kv *KVServer) Append(args *PutAppendArgs, reply *PutAppendReply) {if args.MessageType == Report {kv.record.Delete(args.MessageID)}res, ok := kv.record.Load(args.MessageID)if ok {reply.Value = res.(string) // 重复请求,返回之前的结果return}kv.mu.Lock()old := kv.data[args.Key]kv.data[args.Key] = old + args.Valuereply.Value = oldkv.mu.Unlock()kv.record.Store(args.MessageID, old) // 记录请求
    }
    
  • client.go

    func (ck *Clerk) Get(key string) string {args := &GetArgs{Key: key,}reply := &GetReply{}for !ck.server.Call("KVServer.Get", args, reply) {} // keep trying foreverreturn reply.Value
    }
    func (ck *Clerk) PutAppend(key string, value string, op string) string {MessageID := nrand()arg := &PutAppendArgs{Key:         key,Value:       value,MessageID:   MessageID,MessageType: Modify,}reply := &PutAppendReply{}for !ck.server.Call("KVServer."+op, arg, reply) {}arg = &PutAppendArgs{MessageType: Report,MessageID:   MessageID,}for !ck.server.Call("KVServer."+op, arg, reply) {}return reply.Value
    }
    
❯ go test
Test: one client... Passed -- t  3.3 nrpc 20037 ops 13359
Test: many clients ...... Passed -- t  3.7 nrpc 85009 ops 56718
Test: unreliable net, many clients ...... Passed -- t  3.3 nrpc  1161 ops  632
Test: concurrent append to same key, unreliable ...... Passed -- t  0.4 nrpc   131 ops   52
Test: memory use get ...... Passed -- t  0.6 nrpc     8 ops    0
Test: memory use put ...... Passed -- t  0.3 nrpc     4 ops    0
Test: memory use append ...... Passed -- t  0.5 nrpc     4 ops    0
Test: memory use many put clients ...... Passed -- t 36.7 nrpc 200000 ops    0
Test: memory use many get client ...... Passed -- t 22.6 nrpc 100002 ops    0
Test: memory use many appends ...
2024/05/15 12:48:26 m0 411000 m1 1550088... Passed -- t  2.6 nrpc  2000 ops    0
PASS
ok      6.5840/kvsrv    75.329s

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/327979.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】进程间通信(一)---- 匿名管道

【Linux】进程间通信(一)---- 匿名管道 一.序1什么是进程间通信2.进程间通信的标准3.为什么需要进程通信 二.匿名管道1.原理2.使用3.四种情况4.五个特点 一.序 1什么是进程间通信 进程间通信 通信我们大致知道是啥,就是互相传递信息 那进程…

【软考】设计模式之组合模式

目录 1. 说明2. 应用场景3. 结构图4. 构成5. 优点6. 缺点7. java示例 1. 说明 1.将对象组合成树型结构以表示“部分-整体”的层次结构。2.Composite使得用户对单个对象和组合对象的使用具有一致性。3.组合模式(Composite Pattern)是一种结构型设计模式 …

德昂信息-Wyn助力构建HR人员信息分析看板

”葡萄城的Wyn商业智能软件产品为德昂信息提供了强大的支持,借助Wyn商业智能软件,可以通过可视化方式展示整个公司的人员信息及其分析看板。“ ——德昂信息技术(北京)有限公司 公司简介 德昂信息技术(北京)有限公司(以下简称德昂信息&…

STM32_HAL_TIM_通用计时器_实现计时

项目思路 1使用定时器计数每秒一次 2使用一个变量记录定时器响应多少次 3使用UART将记录的次数发出 1STM32Cude设置 1配置时钟源 2打开UART 3打开TIM2 3.1界面介绍 3.2选项介绍 Slave Mode(从模式):当设备被设置为从模式时&#xff0c…

计算机组成原理(超详解!!) 第八节 总线系统

1.总线的概念和结构形态 1.总线(BUS)的基本概念 是构成计算机系统的互联机构,是多个系统功能部件(运算器、控制器、存储器、输入/输出设备)之间进行数据传送的公共通路。 由传输信息的电路和管理信息传输的协议组成…

数据结构【顺序表】

文章目录 1.顺序表的概念线性表物理结构逻辑结构 2.顺序表的分类2.1静态顺序表2.2动态顺序表 3.顺序表接口的实现头文件(SQList.h)如下源文件初始化顺序表销毁顺序表插入扩容尾插头插 封装扩容函数删除尾删头删 查找元素在指定位置前插入数据情况一(指定的位置不是首元素)情况二…

【机器学习】逻辑回归:智能垃圾邮件分类实例

逻辑回归:智能垃圾邮件分类的利器 一、引言二、逻辑回归概述三、垃圾邮件分类实例数据准备特征选择与建模 四、总结与展望 一、引言 随着互联网的迅猛发展,电子邮件已成为人们日常生活和工作中不可或缺的一部分。然而,与此同时,垃…

[蓝桥杯]真题讲解:合并数列(双指针+贪心)

[蓝桥杯]真题讲解&#xff1a;班级活动&#xff08;贪心&#xff09; 一、视频讲解二、正解代码1、C2、python33、Java 一、视频讲解 [蓝桥杯]真题讲解&#xff1a;合并数列&#xff08;双指针贪心&#xff09; 二、正解代码 1、C #include<bits/stdc.h> #define in…

k8s 网络组件详细 介绍

目录 一 k8s 有哪些网络组件 二 k8s 网络概念 1&#xff0c; k8s 三种网络 2&#xff0c;K8S 中 Pod 网络通信 2.1 Pod 内容器与容器之间的通信 2.2 同一个 Node 内 Pod 之间的通信 2.3 不同 Node 上 Pod 之间的通信 三 Flannel 网络组件 1&#xff0c;Flannel …

K8s源码分析(一)-K8s调度框架及调度器初始化介绍

本文首发在个人博客上&#xff0c;欢迎来踩&#xff01; 文章目录 调度框架介绍K8s scheduler 介绍K8s scheduler的初始化Cobra介绍K8s scheduler中初始化的源代码解析 调度框架介绍 这是官方对于v1.27调度框架的介绍文档&#xff1a;https://v1-27.docs.kubernetes.io/docs/…

使用vue3+ts+vite从零开始搭建bolg(五):layout(持续更新中)

五、layout搭建 5.1静态搭建 在src下创建如图文件夹 这里用logo举例&#xff0c;在scripts里export <script lang"ts">export default {name: Logo,}</script> 然后在layout里引入 //引入左侧菜单顶部用户信息 import Logo from ./logo/index.vue 接…

Spring AOP(概念,使用)

目录 Spring AOPAOP是什么什么是Spring AOPAOP实际开发流程1. 引入依赖2. 编写AOP程序 Spring AOP详解Spring AOP中的核心概念Spring AOP的通知类型六种类型PointCutOrder(切面优先级) Spring AOP AOP是什么 Aspect Oriented Programminig(面向切面编程)切面指的是某一类特定…

“Linux”目录结构and配置网络

了解完命令格式和vi、vim编辑器后&#xff0c;我们来认识一下目录的结构&#xff1a; 一、目录 &#xff08;1&#xff09;目录的特点 windows特点&#xff1a; Windows中有C、D、E盘&#xff0c;每个都是一个根系统 Linux特点&#xff1a; linux中只有一个根&#xff08;单…

C++auto关键字、范围for循环

一、auto关键字 1.1auto简介 在早期C/C中auto的含义是&#xff1a;使用auto修饰的变量&#xff0c;是具有自动存储器的局部变量。 C11中&#xff0c;标准委员会赋予了auto全新的含义即&#xff1a;auto不再是一个存储类型指示符&#xff0c;而是作为一个新的类型指示符来指示编…

记录用python转换headers

转换前 转换后效果 代码如下。注意需要在控制台切换到content.txt所在位置&#xff0c;不然运行代码会报file not found错误 # 假设txt文件内容如下 txt open(content.txt).read()# 使用splitlines()方法将txt内容分割为行&#xff0c;然后使用json.loads()方法将每一行转换为…

每日两题 / 437. 路径总和 III 105. 从前序与中序遍历序列构造二叉树(LeetCode热题100)

437. 路径总和 III - 力扣&#xff08;LeetCode&#xff09; 前序遍历时&#xff0c;维护当前路径&#xff08;根节点开始&#xff09;的路径和&#xff0c;同时记录路径上每个节点的路径和 假设当前路径和为cur&#xff0c;那么ans 路径和(cur - target)的出现次数 /*** D…

【吊打面试官系列】Java高并发篇 - 多线程的价值?

大家好&#xff0c;我是锋哥。今天分享关于 【多线程的价值&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 多线程的价值&#xff1f; 1、发挥多核 CPU 的优势 多线程&#xff0c;可以真正发挥出多核 CPU 的优势来&#xff0c;达到充分利用 CPU 的目的&#…

Linux学习笔记(Socket)

Linux-Socket 1、基础知识2、服务端3、客户端4、读写操作4.1、读写函数4.2、阻塞IO和非阻塞IO 5、例程 1、基础知识 socket用于计算机之间的网络通信&#xff0c;无论是构建服务器还是客户端&#xff0c;我们仅需要三个信息&#xff0c;服务器的ip地址&#xff0c;对应进程的端…

openlayers 热力图 天地图

openlayers 实现热力图 样式可调 在https://blog.csdn.net/qq_36287830/article/details/131844745?spm1001.2014.3001.5501基础上改进来的 关键代码 如果你有数据可以不使用for循环 var blurInput document.getElementById("blur");var rediusInput document.g…

PyQt5编写的一个简易图像处理软件

文章目录 1. 简介2. 准备工作3. 主界面设计4. 功能构建5. 总结 1. 简介 通过编写简易图像处理软件&#xff0c;你可以学习如何使用 PyQt5 构建用户界面&#xff0c;以及如何与用户交互。同时&#xff0c;你还可以学习图像处理技术&#xff0c;如图像读取、傅里叶变换、滤波、增…