title | date | category | tags | |||||
---|---|---|---|---|---|---|---|---|
MIT6.5840(6.824) Lab4: 分片KV数据库 3B |
2024-03-03 02:10:46 -0800 |
|
|
最新的更新在博客 ToniBlog
本文将介绍lab4A
部分的实现, lab4A
要求实现分片控制器, 其实就是lab3
的翻版, 基本上可以照搬lab3
。如果仅仅是为了通过测试,lab4A
倒是极其简单,只需要每次更新Group
后随机完成Shard
的映射就好了, 但还要考虑将配置项的改动最小话, 还是有一些坑的。
Lab
文档见: http://nil.csail.mit.edu/6.5840/2023/labs/lab-shard.html
我的代码: https://github.com/ToniXWD/MIT6.5840/tree/lab4A
分片数据库的组成结构是:1个controller
+ 多个replica groups
。controller
将数据进行分片, 存储在不同的集群上, 每个集群都是一个raft
集群, controller
负责管理分片, 也就是管理配置项。clients
和 servers
都会询问 controller
最新的配置信息。 分片控制器需要具备如下的功能:
- 为了负载均衡 -> 能够迁移分片
- 需要处理请求和配置项更新几乎同时到达的情况
- 建议的实现:
raft
集群也要保存配置项 - 配置更新后, 新的负责某分片的
replica groups
需要从旧的replica groups
复制旧分片
Shard controller
使用下面的RPC
来实现管理和查询:
Join
:- 重新分片时尽量平均
- 重新分片时移动的分片尽量少
- 允许重用
GID
Leave
- 重新分片时尽量平均
- 重新分片时移动的分片尽量少
Move
- 将某个
shard
分配给某个group
- 将某个
Query
- 返回配置信息
- 必须反映在其之前做出的配置信息更改
主要的坑包括:
- 需要滤除重复的
RPC
(照抄lab3
即可) - 执行碎片再平衡的状态机中的代码需要具有确定性(大坑)
由于我们的目标不仅仅是通过测例, 还需要完成下面2个目标:
- 实现负载均衡
实现负载均衡, 意味着所有的集群被映射的数量只差不能大于1(手动
Move
的情况除外), 因此需要对已经映射的集群号(gid
)实现统计计数 - 最小化改动 新配置和旧配置之间的改动要最小化
因此我的实现思路和逻辑如下:
- 首先继承旧配置
lastConfig
创建新配置newConfig
- 将要
Join
的newGroups
追加到newConfig.Groups
, 但需要注意下面的情况len(lastConfig.Groups) + len(newGroups) <= NShards
, 此时无脑将Join
的newGroups
追加到newConfig.Groups
即可len(lastConfig.Groups) + len(newGroups) > NShards
, 此时只需要将newConfig.Groups
填充至len(newConfig.Groups) == NShards
即可, 剩余的部分缓存起来, 记缓存区为CachedGid
- 统计
newConfig.Groups
中对Shard
的映射次数, 映射次数最高的和映射次数最低的绝对值不能大于1, 由于新增了目前没有映射的Join
的新集群, 此特性不可能满足, 因此需要:- 将目前对
Shard
的映射次数最少的gid
剥夺一个映射, 被剥夺的这个映射本来是被映射到拥有Shard
最多的一个gid
- 更新映射次数统计信息, 直到映射次数最少的
gid
(也就是之前新加入的)大于等于映射平均值(NShards / len(newConfig.Groups)
)即可
- 将目前对
- 首先继承旧配置
lastConfig
创建新配置newConfig
- 从
newConfig.Groups
和newConfig.CachedGid
中移除指定的gid
(因为可能目前所有的gid
数量大于NShards
, 部分被暂存到了CachedGid
), 被移除的gid
在newConfig.Shards
用0标记 - 如果
newConfig.CachedGid
还有剩余且len(newConfig.Groups) < NShards
, 将newConfig.CachedGid
中的gid
移动到newConfig.Groups
使其填充满或者使newConfig.CachedGid
为空 - 获取当前映射数量最少的
gid
的统计信息 - 在
newConfig.Shards
用0标记的位置用映射数量最少的gid
代替, 每代替一次后需要重新统计
这两个没啥坑, 就不说了
Go
的map
的迭代顺序Go
的map
的迭代顺序是无序的, 即使其插入时的顺序和数据都是相同的, 但多次进行迭代, 其顺序不一致, 因此在多个副本上想保证确定性的话, 需要先对map
的key
获取切片并排序gid
的数量和NShards
的关系 由于每个Shard
都会映射一个gid
, 因此如果gid
的数量大于NShard
的话, 会存在有gid
映射不到的情况, 这时我的处理是将其放到暂存区, 保证Group
中的gid
数量map
的复制Go
的map
的复制是浅复制, 复制一个map
时,只是复制了对底层数据结构的引用,而不是底层数据本身的副本。意味着如果在一个map
中做出了改变(比如删除或添加键值对),这些改变会在所有对这个底层数据结构的引用中体现出来。因此,对原始map
的修改会影响到复制后的map
,因为它们都引用同一个数据结构。
我自己实现的代码比较繁琐(很丑), 但逻辑其实很简单, 参见: https://github.com/ToniXWD/MIT6.5840/tree/lab4A
此测试经过50次没有报错