目录
前言
做2020的MIT6.824,完成了实验 Lab4A,通过了测试,对于之前的Raft实现的实验请参考Lab 2A, Lab 2B 和 Lab 2C 和 Lab 3A 以及 Lab 3B
Lab4A主要是做DB的分片,也就是Client向Server请求,Server根据不同的Request类型,向不同的DB获取/更改数据。实验重点需要完成master对DB server的Join/Leave/Move的三个操作,并且实验Client对Server的configs查询的操作。
整个实验有三个点需要注意
- load balance的算法
- config需要拷贝而不是引用
- labgob需要注册JoinArgs,LeaveArgs,MoveArgs 和 QueryArgs
一、Overview
1.1 架构图
实验是通过数据库分片来提升性能的,Master负责数据库的分片,大概架构如下
1.2 架构细节
第一,对于本次分片来说,最多可以有10个Group,这里Common文件可以看出来
// The number of shards.
const NShards = 10
第二,对于Master来说,也是有多个servers的,所以需要通过Raft保证shards configuration的一致性,从config可以看出来,一个有三个属性需要维护的,其中Shards就是分片configuration, 初始是[0,0,0,0,0,0,0,0,0,0],Groups就是对应每个shard分片中Raft servers的information,比如Group1 -> server[a, b, c]
type Config struct {
Num int // config number
Shards [NShards]int // shard -> gid
Groups map[int][]string // gid -> servers[]
}
举个例子,shards初始为[0,0,0,0,0,0,0,0,0,0],那么Join了Group1 -> servers[a, b, c] 之后,整个系统就有1个Group了,那么,shards就会变成[1,1,1,1,1,1,1,1,1,1],如果JoinGroup2 -> servers[e, f, g]之后,整个系统就有2个groups了,那么,10个shards就需要尽量平均分配给两个Groups,也就是[1,1,1,1,1,2,2,2,2,2]
这里也就是涉及到了load balance的算法,我的算法实现参考了 网上 的方法,并没有用到高级的数据结构比如heap来实现
二、client
这个跟kvraft中client的实现非常相似,基本搬过来就行
其中,Clerk的构造函数是
type Clerk struct {
servers []*labrpc.ClientEnd
mu sync.Mutex
leaderId int
clientId int64
sequenceId int64
// Your data here.
}
func MakeClerk(servers []*labrpc.ClientEnd) *Clerk {
ck := new(Clerk)
ck.servers = servers
// Your code here.
ck.clientId = nrand()
return ck
}
增加一些新的属性, 主要就是ClientId很SequenceId
type JoinArgs struct {
Servers map[int][]string // new GID -> servers mappings
ClientId int64
SequenceId int64
}
type LeaveArgs struct {
GIDs []int
ClientId int64
SequenceId int64
}
type MoveArgs struct {
Shard int
GID int
ClientId int64
SequenceId int64
}
type QueryArgs struct {
Num int // desired config number
ClientId int64
SequenceId int64
}
Join/Move/Leave/Query 的实现,对于Join/Move/Leave 基本都是一样的
func (ck *Clerk) Query(num int) Config {
args := &QueryArgs{
}
// Your code here.
args.Num = num
args.ClientId = ck.clientId
args.SequenceId = atomic.AddInt64(&ck.sequenceId, 1)
for {
reply := QueryReply{
}
if ck.servers[ck.currentLeader()].Call("ShardMaster.Query", args, &reply) && !reply.WrongLeader {
return reply.Config
}
ck.leaderId = ck.changeLeader()
time.Sleep(100 * time.Millisecond)
}
}
func (ck *Clerk) Join(servers map[int][]string) {
args := &JoinArgs{
}
args.Servers = servers
args.ClientId = ck.clientId
args.SequenceId = atomic.AddInt64(&ck.sequenceId, 1)
for {
reply := JoinReply{
}
if ck.servers[ck.currentLeader()].Call("ShardMaster.Join", args, &reply) && !reply.WrongLeader {
return
}
ck.leaderId = ck.changeLeader()
time.Sleep(100 * time.Millisecond)
}
}