MIT6824——lab4(实现一个分片kv存储)的一些实现,问题,和思考

news2025/1/18 6:13:40

Part A 分片控制器

1. 整体思路

  • 和lab3A一样,shardctler也是一个服务,由客户端调用。这个服务建立在raft集群上,保证容错。

  • shardctler也应该保证线性一致性和重复请求的问题,因此也需要记录clientid和messageid。

  • shardctler保存了当前的分片信息,称为配置

    • Num:当前配置号
    • Shards:每一个分片对应的副本组id—gid
    • Groups:每个组(gid)对应哪些服务器
    type Config struct {
    	Num    int              // config number
    	Shards [NShards]int     // shard -> gid [gid1, gid2, gid3,...]
    	Groups map[int][]string // gid -> servers[] {gid1:[x1,y1,z1], gid2:[x2,y2,z2], ...}
    }
    
  • shardcltr包含4个操作,Join、Leave、Move、Query

    • Join:添加组 { gid : servers, …},新配置应尽可能将分片均匀地分配到整组组中,并应移动尽可能少的分片以实现该目标
    • Leave:移除组,并将该组上的分片均匀分配给其他组
    • Move:转移分片**,**一些组可能比其他组负载更多,因此需要移动分片来平衡负载
    • Query:查询当前的Config
  • 一些需要注意的地方

    • Join和Leave都涉及到对Groups 的修改,因此需要重新调整Shards(这里涉及到负载均衡),因为有可能gid已经没有对应的服务器了,因此分片也不应该由该gid管理
    • 除了Qeury,其他三种操作都涉及到修改config,因此需要添加新的config

2 流程

  • 客户端对shardctler发出请求
  • shardctler通知raft Start()
  • raft执行AppendEntries,通知其他raft节点日志复制
  • raft收到过半确认后,通知shardctler执行请求 applyCh管道
  • shardctler执行 收到信号(实际上shardctler会在applyRoutine这个协程上一直等applyCh),根据clientid,messageid判断是否执行过该条指令,没有执行过就执行
  • shardctler执行完后,通知 可以 返回结果给客户端 broadcastCh管道

3 一些细节

  • 通常来说,分片比组多,相当于每个组都会负责多个分片。
  • Join操作
    • 向当前Config中新增一些组
    • 新增后,需要调整shard,保证负载均衡
  • Leave操作
    • 移除一些组
    • 移除后,需要调整shard,保证负载均衡
  • 如何实现负载均衡?
    • 每当Join、Leave后,Groups都会发生变化,因此需要重新调整Shard。由于分片比组多,因此每个组都会负责多个分片。为了让服务器实现均衡,通过shard%len(gids)找到,shard对应的gid,这样,每个组负责的shard就是尽可能平衡的

      sort.Ints(gids)                    // 假设:gids = [1,2,3,4]
      for shard := range config.Shards { // 0-9
      	config.Shards[shard] = gids[shard%len(gids)] // 负载均衡算法,shard_0映射到gids[0], shard_1映射到gids[1], ..., shard_5映射到gids[1], ...,
      }
      

4 问题

  • 如何实现负载均衡?
    • 方案一:取模映射 O(nlogn),但是移动的并不是最少

      • 通过shard%len(gids)找到,shard对应的gid,这样,每个组负责的shard就是尽可能平衡的

        sort.Ints(gids)                   // 排序后,才能保证移动最少
        for shard := range config.Shards { // 0-9
        	config.Shards[shard] = gids[shard%len(gids)] // 负载均衡算法,shard_0映射到gids[0], shard_1映射到gids[1], ..., shard_5映射到gids[1], ...,
        }
        

        请添加图片描述

    • 方案二:直接移动 O(n^2),移动的次数最少

      • 需要一个map,记录当前的gid有多少个shard

        gid_num_shard_map{
        	1:5
        	2:5
        }
        
        minNum = 5;
        maxNum = 5;
        
      • 循环让拥有最多shard的Group分一个给拥有最少的Group,直到maxNum - minNm=1
        请添加图片描述

Part B 多集群KV存储—Multi-Raft

请添加图片描述

1 整体思路

  1. 更新配置

    • 什么时候会更新配置??

      • 当此时分片没有分配对应组时(shard[i]=0),kv需要询问分配控制器,最新的配置
    • 代码:作为一个协程单独跑,定时检查是否要更新

      func (kv *ShardKV) configurationRoutine() {
      	for !kv.killed() {
      		kv.mu.Lock()
      		defaultShard := true // 默认的切片,就是初始化的时候,此时切片还没分配组
      		for _, shard := range kv.state {
      			if shard.Status != Default {
      				defaultShard = false
      				break
      			}
      		}
      		num := kv.config.Num
      		kv.mu.Unlock()
      
      		_, isLeader := kv.rf.GetState()
      		if isLeader && defaultShard {
      			// 询问分配控制器,当前配置的下一个配置
      			nextConfig := kv.clerk.Query(num + 1)
      			nextNum := nextConfig.Num
      
      			// 如果获取了新配置,需要通知kv,改变当前的配置,因为这是一个命令,需要raft进行日志复制,然后再执行
      			if num+1 == nextNum {
      				command := Configuration{
      					Config: nextConfig,
      				}
      				index, _, isLeader := kv.rf.Start(command)
      				if isLeader {
      					// 等到kv执行了更新config请求,或者超时才会返回
      					kv.consensus(index)
      				}
      			}
      		}
      
      		time.Sleep(100 * time.Millisecond)
      	}
      }
      
    • 得到新配置后,需要处理一下两种情况

      • 设置Pull状态:新配置shard属于kv && 旧配置shard分片不属于kv,也就是说,此后,kv应该负责当前分片,但是数据库中(kv.state)中没有数据,此时需要拉取过来.
      • 设置WaitDelete状态:新配置shard不属于kv,旧配置shard属于kv,也就是说,此后kv不应该负责当前分片,但数据库中(kv.state)有数据,此时需要删除。但也不是马上删除,而是在删除完对方的数据后 **DeleteShardRPC()**返回,再删除自己的
  2. 拉取数据

    • 什么时候才会拉取成为拉取状态?

      • 新配置shard属于kv && 旧配置shard分片不属于kv,也就是说,此后,kv应该负责当前分片,但是数据库中(kv.state)中没有数据,此时需要拉取过来
    • 更新配置后,并不表示所属分片可以立刻对外提供服务,还需要等待在上一个版本的Config中不属于自身的shard从它之前所属的Group中迁移到本Group

    • 拉取数据的方法应该怎么进行,同步?异步?

      • 不能同步,因为如果在更新日志的时候同步阻塞整个协程,会影响其他的对外请求
      • 不能异步,leader 可能会在 新配置之后新数据被异步拉取到并提交日志之前宕机,而 follower 虽然会apply 配置但是不会去拉数据,这样这些数据将永远无法被更新。
      • 在更新配置中,我们只根据情况改变状态
      for shard, gid := range nextConfig.Shards {
      		targetGid := kv.config.Shards[shard] // 当前配置shard分片的gid
      
      		// 新配置shard属于kv && 旧配置shard分片不属于kv,也就是说,此后,kv应该负责当前分片,但是数据库中(kv.state)中没有数据,此时需要拉取过来
      		if gid == kv.gid && targetGid != kv.gid && targetGid != 0 {
      			kv.state[shard].Status = Pull
      		}
      		// 新配置shard不属于kv,旧配置shard属于kv,也就是说,此后kv不应该负责当前分片,但数据库中(kv.state)有数据,此时需要删除
      		if gid != kv.gid && targetGid == kv.gid && gid != 0 {
      			kv.state[shard].Status = Push
      		}
      }
      
    • 在拉取数据,更新配置时,如何保证线性一致性,也就是如何让用户不会发现这个过程?

      • 在拉取数据时,将kv的此时已经接受的客户端请求信息也给对方

        // 将给当前kv发送过请求的client信息给对方,因为后续客户端可以再向对方请求数据,这样在更新配置时,也能保证线性一致性
        client := make(map[int64]int)
        for clientId, messageId := range kv.client {
        	client[clientId] = messageId
        }
        
  3. 数据删除

    • 什么时候要删除数据?
      • 当拉取数据成功后,需要将通知数据来源方删除自己的数据,标记为Collection状态

        // 把数据给 对应的Pull状态的分片
        for shard := range state {
        	if kv.state[shard].Status == Pull {
        		for k, v := range state[shard].State {
        			kv.state[shard].Put(k, v)
        		}
        		// 拉取数据后,成为Collection状态
        		kv.state[shard].Status = Collection
        	}
        }
        
      • 当对方删除完毕后,**DeleteShardRPC()**返回,此时还要删除自己WaitDelete状态的数据

2 流程

  • 初始下,共有10个分片,3个shardkv集群组,1个分片控制器集群
  • 客户端通过key映射得到分片,然后更具配置,找到对应的集群组
  • 集群组初始化下还没有配置,发出更新配置请求 configurationRoutine()
  • 更新配置后,如果新配置shard属于kv && 旧配置shard分片不属于kv,需要拉取数据 pullShardRoutine()
  • 拉取数据后,还需要让数据来源方删除shard对应的数据 deleteShardRoutine()
  • KV数据库执行请求,返回数据给客户端

4 问题

  • 为什么要使用多集群?

    • 之前,只有一个集群时,所有请求都由leader负责,当数据量大后,请求增多,leader面临的压力非常大,请求响应的时间也会延长,这种情况下,简单增加机器并不会由性能提升。因此可以将数据分开存储到不同集群,将不同请求引流到不同集群,降低单一集群的压力。
  • 如何保证一个集群组丢失了一个分片,需要立即停止向该分片中的键提供请求?

    • 每次kv在响应请求前,都会通过staleShard(shard int),判断当前shard是不是自己负责

      /*准备响应Get请求*/
      kv.mu.Lock()
      if kv.staleShard(shard) {
      		response.Err = ErrWrongGroup
      		kv.mu.Unlock()
      		return
      }
      if method == "Get" {
      		response.Value = kv.state[shard].Get(key)
      }
      kv.mu.Unlock()
      
      /*准备响应Put、Append请求*/
      if !kv.staleShard(shard) && kv.client[clientId] < messageId {
      		switch method {
      		case "Put":
      			kv.state[shard].Put(key, value)
      		case "Append":
      			kv.state[shard].Append(key, value)
      		}
      		kv.client[clientId] = messageId
      }
      
  • 如何保证在分片移动后的线性一致性?

    • kv在拉取数据时,对端也会把client信息返回,这样当前kv就有了当前客户的信息,如果该客户端发送了一个已经执行过的请求,将不再执行。
  • 如何实现在配置更改时,对于那些没移动的分片,可以正常响应给客户端呢?

    • 只要是Collection和Default状态的切片,都认为是拥有此分片的
    • Collection只会在拉取到数据后被设置
    • Default就是拥有这些切片
    • staleShard会去判断是否拥有当前切片,只要拥有,就可以对外提供服务

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

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

相关文章

BFT 最前线 | 张一鸣成立个人基金;马斯克:AI是双刃剑;阿里首席安全科学家离职;卡内基梅隆究团队:解决农业虫卵问题的机器人

文 | BFT机器人 名人动态 CELEBRITY NEWS 01 字节跳动创始人张一鸣 在香港成立个人投资基金 在卸任CEO两年后&#xff0c;字节跳动创始人张一鸣在香港成立了一家个人投资基金。香港公司注册处网站显示&#xff0c;该基金名为Cool River Venture&#xff0c;性质是私人股份有限…

doris索引

目前 Doris 主要支持两类索引&#xff1a; - 内建的智能索引&#xff1a;包括前缀索引和 ZoneMap 索引。 - 用户创建的二级索引&#xff1a;包括 Bloom Filter 索引 和 Bitmap倒排索引。其中 ZoneMap 索引是在列存格式上&#xff0c;对每一列自动维护的索引信息&#xff0c;包…

Go 语言实战案例:猜谜游戏在线词典SOCKS5代理服务器 Go学习路线

字节跳动后端入门 - Go 语言原理与实践& vscode配置安装Go 3.1猜谜游戏 3.1.2 生成随机数v2 package mainimport ("fmt""math/rand""time" )func main() {maxNum : 100rand.Seed(time.Now().UnixNano())secretNumber : rand.Intn(maxNum)fmt…

OS之页面置换算法

目录 一、最佳置换算法(OPT) 定义 案例 二、先进先出置换算法(FIFO) 定义 案例 FIFO特有的异常 三、最近最久未使用置换算法(LRU) 定义 案例 四、时钟置换算法(CLOCK) 定义 案例 五、改进型的时钟置换算法 定义 案例 一、最佳置换算法(OPT) 定义 每次选择淘汰…

GoWeb -- gin框架的入门和使用(2)

前言 书接上回&#xff0c;在gin的框架使用中&#xff0c;还有着许多方法以及它们的作用&#xff0c;本篇博客将会接着上次的内容继续记录本人在学习gin框架时的思路和笔记。 如果还没有看过上篇博客的可以点此跳转。 map参数 请求url&#xff1a; http://localhost:8080/us…

全志V3S嵌入式驱动开发(驱动开发准备)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 之前的文章都是教大家怎么搭建环境、看原理图、编译内核和根文件系统、做镜像&#xff0c;直到现在才进入驱动开发的主题。毕竟整个专栏的目的&…

Python 基础(十四):类和对象

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 一、面向对象编程二、创建类三、创建实例3.1、访问属性3.2、调用方法 四、属性默认值4…

网络通信协议-ICMP协议

目录 一、ICMP协议 二、ICMP协议通信过程 &#xff08;1&#xff09;机制 &#xff08;2&#xff09;原理 &#xff08;3&#xff09;相关术语 丢包率 网络延时率&#xff08;延迟&#xff09; 请求超时【类似表白对方压根不搭理你】 没有任何回复数据&#xff0c;回复…

DNS/ICMP协议/NAT技术

本博文分享DNS&#xff08;简单认识&#xff09;、ICMP&#xff08;简单认识&#xff09;和NAT技术&#xff08;重点学习&#xff09;。 DNS DNS是一整套从域名映射到IP的系统&#xff0c;TCP/IP中使用IP地址和端口号来确定网络上的一台主机的一个程序。但是IP地址不方便记忆&…

windows下mysql中binlog日志分析和数据恢复

1.首先查看是否开启了binlog show variables like %log_bin%;看到了是没有开启的。 2.开启binlog日志&#xff0c;并重启mysql服务 不能通过命令的方式去打开&#xff0c;因为会提示说这个参数是只读的。如下图&#xff1a; 所以&#xff0c;打开mysql的配置文件&#xff…

Three.js--》实现3d地球模型展示

目录 项目搭建 实现网页简单布局 初始化three.js基础代码 创建环境背景 加载地球模型 实现光柱效果 添加月球模型 今天简单实现一个three.js的小Demo&#xff0c;加强自己对three知识的掌握与学习&#xff0c;只有在项目中才能灵活将所学知识运用起来&#xff0c;话不多…

配置MIT6.S081环境

配置环境 1实现环境2更换源3.安装RISC-V交叉编译工具4.安装QEMU5.检测安装6.下载源码7.检查调试工具8.安装vim9.进行初步调试 1实现环境 虚拟机&#xff08;我用的VMware Workstation Pro&#xff09;Ubantu20.04安装QEMU 注意必须使用ubuntu20.04版本&#xff0c;因为后面安装…

Webpack打包图片-JS-Vue

1 Webpack打包图片 2 Webpack打包JS代码 3 Babel和babel-loader 5 resolve模块解析 4 Webpack打包Vue webpack5打包 的过程&#xff1a; 在webpack的配置文件里面编写rules&#xff0c;type类型有多种&#xff0c;每个都有自己的作用&#xff0c;想要把小内存的图片转成bas…

华为OD机试之不含101的整数(Java源码)

不含101的数 题目描述 小明在学习二进制时&#xff0c;发现了一类不含 101的数&#xff0c;也就是&#xff1a; 将数字用二进制表示&#xff0c;不能出现 101 。 现在给定一个整数区间 [l,r] &#xff0c;请问这个区间包含了多少个二进制不含 101 的整数&#xff1f; 输入描述…

WalkRE--刷图流程(超具体)

1、打开WalkRE软件&#xff0c;界面如下&#xff1a; 2、选择“根据模板新建工程”。操作如下&#xff1a; 3、导入数据。需要入准备好的dxf格式的CAD地形数据。操作如下&#xff1a; 在空白处右键&#xff0c;先关闭所有层&#xff08;大部分层在刷图时用不上&#xff0c;仅打…

自动化测试2:selenium常用API

目录 1.webdirver API 1.1.定位元素 1.2CSS 1.语法 2,使用 1.3XPath定位 1.语法 2.使用 2.操作测试对象 2.1.鼠标点击与键盘输入 2.2submit 提交表单 2.3text 获取元素文本 3.添加等待 3.1.sleep休眠 3.2.智能等待 3.2.1.隐式等待 3.2.2显示等待 4.打印信息 …

web前端 --- CSS(04) -- 盒子模型、div+css网页布局、css3新特性

盒子模型 &#xff08;1&#xff09;网页标签分类&#xff1a; 行内元素&#xff1a;块级元素&#xff1a;有宽高&#xff0c;可以设置大小&#xff0c;同时不会让其他块元素默认占据当前行 &#xff08;2&#xff09;内边距&#xff1a; 内容和边线之间存在空白区域&#x…

路径规划算法:基于猫群优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于猫群优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于猫群优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法猫群…

数据库基础——3.SQL概述及规范

这篇文章我们来讲一下SQL概述和使用规范 目录 1.SQL概述 1.1SQL背景 1.2 SQL语言排行榜 1.3 SQL分类 2.SQL规则与规范 2.1基本规则 2.2 SQL大小写规范 &#xff08;建议遵守&#xff09; 2.3 注 释 2.4 命名规则&#xff08;暂时了解&#xff09; 2.5 数据导入指令 1…

【硬件】嵌入式电子设计基础之数字电路

数字电路与模拟电路的设计思想和应用方法有许多不同之处。 计算器是一个典型的由数字电路实现的电子设备&#xff0c;用户通过数字或符号摁键输入运算式&#xff0c;计算器经过运算之后把结果显示在屏幕上。现代数学电子学始于1946年&#xff0c;其标志是一台以电子管为核心器件…