实验简介
这个实验我感觉比前三个,更难理解。最后是根据下面的这段话,才懂得。
Sworduo(http://sworduo.net/2019/08/16/MIT6-824-lab4-shardKV/#more):
Lab2和Lab3构成基础分布式数据库的框架,实现多节点间的数据一致性,支持增删查改,数据同步和快照保存。然而,在实际应用中,当数据增长到一定程度时,若仍然使用单一集群服务所有数据,将会造成大量访问挤压到leader上,增加集群压力,延长请求响应时间。这是由于lab2和lab3所构成的分布式数据库的核心在于分布式缓存数据,确保在leader宕机后,集群仍然可用,并没有考虑集群负载问题,每时每刻,所有数据请求都集中在一台机器上,显而易见的,在数据访问高峰期,请求队列将会无比漫长,客户等待时间也会变长。一个非常直接的解决方法,就是将数据按照某种方式分开存储到不同的集群上,将不同的请求引流到不同的集群,降低单一集群的压力,提供更为高效、更为健壮的服务。
Lab4就是要实现分库分表,将不同的数据划分到不同的集群上,保证相应数据请求引流到对应的集群。这里,将互不相交并且合力组成完整数据库的每一个数据库子集称为shard。在同一阶段中,shard与集群的对应关系称为配置,随着时间的推移,新集群的加入或者现有集群的离去,shard需要在不同集群之中进行迁移,如何处理好配置更新时shard的移动,是lab4的主要挑战。
一个集群只有Leader才能服务,系统的性能与集群的数量成正比。lab3是一个集群,lab4A要实现的是多个集群之间的配合。
参考:
- https://www.jianshu.com/p/6e8d33c3c799
4A
First you’ll implement the shard master, in shardmaster/server.go
and client.go
.
// 数据库子集和集群之间的关系
type Config struct {
// config的版本号
Num int
// shards[0] = g0:代表数据库子集[0]由集群g0负责
Shards [NShards]int // shard -> gid
Groups map[int][]string // gid -> servers[]
}
CLient
Client的实现跟3A一样。
Server
逻辑和3A也是一样。每一次变化,都要新增一个Config。
- Join:把这些GID 组,加到MASTER的管理范围里来。
- Leave:移除几个集群。
- Query:查询具体的Config
- Move:指定某个数据库子集归哪个集群管。
注意
如果你的Op中包含自定义字段,请在lagob中注册。因为Raft需要把我们Start的命令追加到日志中,然后Leader通过RPC发送追加日志请求
给Follower。需添加如下代码:
labgob.Register(Op{})
labgob.Register(JoinArgs{})
labgob.Register(LeaveArgs{})
labgob.Register(MoveArgs{})
labgob.Register(QueryArgs{})
Rebalance
测试是通过查看Config.Shards
来判断。
因为有集群的加入和离去,就需要重新调整数据库子集和集群之间的对应关系。一个Shared只能有一个Group管理,而一个Group可以管理多个Shared。我的思路是使用最大堆和最小堆。
type ShardMaster struct {
mu sync.Mutex
me int
rf *raft.Raft
applyCh chan raft.ApplyMsg
// Your data here.
configs []Config // indexed by config num
...
shardCount []Relation
}
type Relation struct {
Gid int
Shards []int // one group 管理 哪几个shard
}
有新集群加入
从多的几个组中,分一点过来。因为懒得实现最大堆和最小堆,就直接用了暴力排序
avg := NShards / len(cfg.Groups)
relation := Relation{
Gid: gid,
Shards: make([]int, 0),
}
i := 0
for ; i < avg && len(sm.shardCount) != 0; i++ {
// 从大到小排序,每次选出最大的
sort.Sort(MoreByCount(sm.shardCount))
sharedNo := sm.shardCount[0].Shards[0]
// update `sm.shardCount`:从最多的中,移除一个Shard
if len(sm.shardCount[0].Shards) == 1 {
sm.shardCount[0].Shards = sm.shardCount[0].Shards[:0]
} else {
sm.shardCount[0].Shards = sm.shardCount[0].Shards[1:]
}
// 把这个shard分给新的group
relation.Shards = append(relation.Shards, sharedNo)
// update `Config.Shards`
cfg.Shards[sharedNo] = gid
}
if len(sm.shardCount) == 0 {
for i := 0; i<NShards; i++ {
relation.Shards = append(relation.Shards, i)
cfg.Shards[i] = gid
}
}
sm.shardCount = append(sm.shardCount, relation)
有集群离去
划分给Shared少的几个组
// 这个gid拥有哪几个shared
var shards []int
for i, _ := range sm.shardCount {
if sm.shardCount[i].Gid == gid {
shards = sm.shardCount[i].Shards
// update sm.shardCount
if i == len(sm.shardCount) -1 {
sm.shardCount = sm.shardCount[:i]
} else {
sm.shardCount = append(sm.shardCount[:i], sm.shardCount[i+1:]...)
}
break
}
}
// 一个集群也没有了
if len(sm.shardCount) == 0 {
cfg.Shards = [NShards]int{}
return
}
// 分配这几个shards
for _, s := range shards {
// 从小到大排序,每次选出最大的
sort.Sort(LessByCount(sm.shardCount))
// 分区数最小的集群 add a shard
sm.shardCount[0].Shards = append(sm.shardCount[0].Shards, s)
// update `Config.Shards`
cfg.Shards[s] = sm.shardCount[0].Gid
}