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

news2024/9/24 5:29:27

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.Value
    	reply.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 int
    
    const (
    	Modify = iota
    	Report
    )
    
    // Put or Append
    type PutAppendArgs struct {
    	Key   string
    	Value string
    	MessageType MessageType // Modify or Report
    	MessageID   int64       // Unique ID for each message
    }
    
  • server.go

    type KVServer struct {
    	mu sync.Mutex
    
    	data   map[string]string
    	record 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.Value
    	reply.Value = old
    	kv.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.Value
    	reply.Value = old
    	kv.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 forever
    	return 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.coloradmin.cn/o/1678070.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目中使用Elasticsearch的API相关介绍

项目中使用Elasticsearch的API相关介绍 0、域映射类型 text:会分词,不支持聚合对当前搜索关键词,先自身分词,分成多个词,然后去一个一个的词去利用倒排索引去查询es索引库一般应用在搜索关键字匹配的字段的类型。 商…

如何解决 CentOS 双网卡配置的内外网通信问题?

🐯 如何解决 CentOS 双网卡配置的内外网通信问题?🐾 博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的…

Spring学习①__Spring初识

Spring Spring初识一、框架二、Spring(春天)简介Spring官网Spring是什么?Spring介绍拓展 Spring初识 一、框架 ​框架就是一些类和接口的集合,通过这些类和接口协调来完成一系列的程序实现。 JAVA框架可以分为三层: 表示层业务…

【网络安全】【Frida实战案例】某图xx付费功能逆向分析(二)

文章目录 一、目标应用二、环境三、步骤1、重打包2、运行打包后apk3、找到签名信息(1)、查看apk签名信息(2)、hook Android方法获取apk签名信息(3)、转为md5验证 4、hook apk签名信息 四、总结五、相关源码…

【稳定检索|投稿优惠】2024年医学、药学与生物工程国际会议(ICMPB 2024)

2024年医学、药学与生物工程国际会议(ICMPB 2024) 2024 International Conference on Medicine, Pharmacy, and Biotechnology 【会议简介】 2024年医学、药学与生物工程国际会议将于长沙召开。此次会议将汇聚全球医学、药学与生物工程领域的顶尖学者…

【BOSS直聘爬取系统功能介绍】

完整代码关注公众号 : 爬取网站:BOSS直聘:https://www.zhipin.com/ 难点 1. boss直聘不论什么岗位都只会展示10页数据,就算在网页里加到了11,内容也会和10一样。 2.多次访问会有验证码需要登录,这部分需…

香港电讯高效网络,助力新消费品牌抓住拓展香港市场新风口

自今年初香港与内地全面恢复通关,两地同胞跨境消费热潮持续升温。港人“北上”消费掀起风潮的同时,香港市场也成为内地新消费品牌拓展的热门目标。从糕点、茶饮、连锁餐饮到服饰,越来越多内地品牌进驻香港。新消费品牌要想在香港开设门店&…

jdk安装多个版本,但是java -version显示最早安装的版本,换掉Path或者JAVA_HOME不生效问题

问题一:当你的电脑上又多个jdk版本,如17 或者8时,使用命令行 java -version显示最早安装的,如下图所示:环境变量配置的17,但是命令行显示的是8。 原因:windows电脑装jdk17后 会在你的环境变量…

【吊打面试官系列】Java高并发篇 - 并发编程三要素?

大家好,我是锋哥。今天分享关于 【并发编程三要素?】面试题,希望对大家有帮助; 并发编程三要素? 1、原子性 原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要…

2024年5月18日(星期六)骑行香杆箐

2024年5月18日 (星期六)骑行香杆箐,早8:30到9:00,郊野公园西门集合,9:30准时出发【因迟到者,骑行速度快者,可自行追赶偶遇。】 偶遇地点:郊野公园西门集合 ,家住东,西,南…

字节跳动发布豆包大模型,主力模型比行业价格低99.3%

5月15日,字节跳动豆包大模型在火山引擎原动力大会上正式发布。火山引擎是字节跳动旗下云服务平台,据火山引擎总裁谭待介绍,经过一年时间的迭代和市场验证,豆包大模型正成为国内使用量最大、应用场景最丰富的大模型之一&#xff0c…

Chromium 调试指南2024 Windows11篇-条件断点、函数断点(十一)

1. 前言 在调试过程中,步进代码和条件断点/函数断点是非常有用的工具和技术,它们可以帮助开发者更加精确地定位和解决问题。本文将介绍步进代码的常用工具以及条件断点/函数断点的设置方法,帮助开发者更加高效地进行调试工作。 2. 步进代码…

MySQL创建索引报错 Specified key was too long;max key length is 1000 bytes.

MySQL对创建索引的大小有限制,一般索引键最大长度总和不能超过1000个字节。 问题描述 MySQL创建索引时报错 Specified key was too long;max key length is 1000 bytes. 解决办法 (1) 修改存储引擎 InnoDB的索引字段长度限制大于MyISAM,可以尝试改成…

欧拉公式的讲解

啊,哈喽,小伙伴们大家好。我是#张亿,今天呐,学的是欧拉公式 在不同的学科中有着不同的含义和应用。在复变函数中,欧拉公式表述为e^(ix)(cos xisin x),其中e是自然对数的底,i是虚数单位&#x…

Springboot+Vue项目-基于Java+MySQL的制造装备物联及生产管理ERP系统(附源码+演示视频+LW)

大家好!我是程序猿老A,感谢您阅读本文,欢迎一键三连哦。 💞当前专栏:Java毕业设计 精彩专栏推荐👇🏻👇🏻👇🏻 🎀 Python毕业设计 &…

Linux连接文件那点事

什么是连接文件 将一个文件和另一个文件建立联系,分为硬链接和软连接(符号连接)。 硬链接 Linux中,所有的文件都有一个inode,这个东西就是文件的ID号,硬链接的方式就是通过这个inode来产生新的文件名来建…

盘点那些年我们一起玩过的网络安全工具

一、反恶意代码软件 1.Malwarebytes 这是一个检测和删除恶意的软件,包括蠕虫,木马,后门,流氓,拨号器,间谍软件等等。快如闪电的扫描速度,具有隔离功能,并让您方便的恢复。包含额外…

xilinx fpga bit流文件转成bin/mcs/hex文件的tcl语句操作

xilinx fpga bit流文件中包含工程的相关信息以及主体程序的二进制文件,bit文件只能在线加载,掉电丢失。因此需要转成bin/mcs文件,固化到flash中。 此处以转成bin文件为例 vivado2018软件中似乎没有单独将已经生成的bit转成bin文件的小工具&…

QT状态机1-三态循环状态机

#include "MainWindow.h" #include "ui_MainWindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent)

微调Stable Diffusion生成你专属的毛利兰

最近在研究AIGC🤖️技术,看了很多微调方法、论文层。但搞技术这么能纸上谈兵呢?今天本柯南迷就想来实践生成美少女小兰👸 在这篇文章里,我选用了 textual inversion和dreambooth📷来做比较,这两…