Lab3 Fault-tolerant Key/Value Service
一、前期准备
课堂翻译强烈推荐(里面还有相应论文),虽然可能没有上课听老师讲那么好,但是对于英语渣渣的我还是非常有用的。
二、实验要求
相较于Lab2,Lab3的实验简单不少,但是Lab3是在Lab2的基础上构建一个KV服务,Lab2要是有些细节没搞好,调试火葬场,看日志看的头疼。这部分也比较简单,我就直接放代码了。
Lab3A:实现一个不需要快照的KV服务,其实结合Lab2中的test内容和Lab3的实验说明,我们就能大致知道Lab3A的实验要求,Client将内容发送给服务端,服务端调用raft中的start函数,添加log,之后就是发送日志到其他服务器,最后提交到applych中,接收到消息后需要我们在server端中处理,最后将消息返回给客户端。
Lab3B:因为在Lab2中我们已经实现了大部分功能了,在这个实验中只需要在适当的实际存储快照,以及读取快照就行。
三、common结构体
const (
OK = "OK"
ErrNoKey = "ErrNoKey"
ErrWrongLeader = "ErrWrongLeader"
ErrTimeout = "ErrTimeout"
Timeout = 1000 * time.Millisecond // 200ms
)
type Err string
// Put or Append
type PutAppendArgs struct {
Key string
Value string
Op string // "Put" or "Append"
ClientId int // 客户端id
SeqId int // 指令编号 自增 防止重复执行
// You'll have to add definitions here.
// Field names must start with capital letters,
// otherwise RPC will break.
}
type PutAppendReply struct {
Err Err
LeaderId int
}
type GetArgs struct {
Key string
ClientId int // 客户端id
SeqId int // 指令编号 自增 防止重复执行
// You'll have to add definitions here.
}
type GetReply struct {
Err Err
Value string
LeaderId int
}
四、客户端
我们增加了三个成员变量,一个是用来保存leaderId,两外两个参数是用来唯一表示该条指令的。
type Clerk struct {
servers []*labrpc.ClientEnd
// You will have to modify this struct.
mu sync.Mutex
leaderId int
clientId int // 客户端id
seqId int // 指令编号 自增 防止重复执行
}
项目中提供了nrand函数,可能是用来给clientId赋值的,但在这里我感觉可能会有重复的情况,就想用地址给他赋值,好像只找到了这个(不知道能不能行),C++后遗症。
func MakeClerk(servers []*labrpc.ClientEnd) *Clerk {
ck := new(Clerk)
ck.servers = servers
ck.leaderId = 0
ck.seqId = 0
ck.clientId = *((*int)(unsafe.Pointer(ck)))
// You'll have to add code here.
return ck
}
Get函数就是循环发送请求,知道收到想要的reply结果,唯一要注意的就是reply := GetReply{}要在for函数里面定义,不然会重复使用reply,有一条warning信息:labgob warning: Decoding into a non-default variable/field Err may not work
func (ck *Clerk) Get(key string) string {
ck.mu.Lock()
defer ck.mu.Unlock()
args := GetArgs{}
args.Key = key
args.ClientId = ck.clientId
args.SeqId = ck.seqId
ck.seqId++
id := ck.leaderId
k := 0
for {
i := id % len(ck.servers)
reply := GetReply{}
ok := ck.servers[i].Call("KVServer.Get", &args, &reply)
k++
if ok {
if reply.Err == OK || reply.Err == ErrNoKey {
ck.leaderId = reply.LeaderId
if reply.Err == OK {
return reply.Value
} else {
return ""
}
}
}
id++
time.Sleep(10 * time.Millisecond)
}
return ""
}
Put函数类似 就不再重复了
五、server函数
type Op struct {
// Your definitions here.
// Field names must start with capital letters,
// otherwise RPC will break.
Type string
Key string
Value string
ClientId int
SeqId int
Term int
Index int
}
添加的三个map,Kvdata用来存储k/v数据,CommandSeq确保同一个客户端指令是顺序执行,且不重复,commandReqnoti用于通知指令已经提交,准备返回结果。
type KVServer struct {
mu sync.Mutex
me int
rf *raft.Raft
applyCh chan raft.ApplyMsg
dead int32 // set by Kill()
maxraftstate int // snapshot if log grows this big
Kvdata map[string]string
CommandSeq map[int]int
commandReqnoti map[int]chan Op
// Your definitions here.
}
5.1 PutAppend
PutAppend函数功能就是通过调用raft中的start函数添加log信息,之后等待信息的提交,最后处理提交的信息,通知以便返回结果。
func (kv *KVServer) PutAppend(args *PutAppendArgs, reply *PutAppendReply) {
if kv.killed() {
reply.Err = ErrWrongLeader
return
}
op := Op{}
op.Type = args.Op
op.Key = args.Key
op.ClientId = args.ClientId
op.SeqId = args.SeqId
op.Value = args.Value
var isleader bool
op.Index, op.Term, isleader = kv.rf.Start(op)
if !isleader {
reply.Err = ErrWrongLeader
return
}
//fmt.Println("leaderId ", kv.me)
fmt.Println(op.ClientId, " ", op.Type, " ", op.Key, " ", op.Value)
// index 可以唯一确定 命令(leader)
var ch chan Op
//fmt.Println(kv.me, " ", 129, "lock")
kv.mu.Lock()
if _, ok := kv.commandReqnoti[op.Index]; !ok {
ch = make(chan Op, 1)
kv.commandReqnoti[op.Index] = ch
}
kv.mu.Unlock()
//fmt.Println(kv.me, " ", 129, "unlock")
select {
case <-time.After(time.Duration(1000) * time.Millisecond):
reply.Err = ErrTimeout
//fmt.Println(kv.me, " ", 141, "lock")
kv.mu.Lock()
delete(kv.commandReqnoti, op.Index)
kv.mu.Unlock()
//fmt.Println("put timeout key ", args.Key, " leader ", kv.me)
//fmt.Println(kv.me, " ", 141, "unlock")
return
case opmsg := <-ch:
if opmsg.Term != op.Term { //leader 已经更换 原来的command失效
reply.Err = ErrWrongLeader
//fmt.Println(kv.me, " ", 152, "lock")
kv.mu.Lock()
delete(kv.commandReqnoti, op.Index)
kv.mu.Unlock()
//fmt.Println(kv.me, " ", 152, "unlock")
return
}
//fmt.Println(kv.me, " ", 159, "lock")
kv.mu.Lock()
reply.Err = OK
reply.LeaderId = kv.me
delete(kv.commandReqnoti, op.Index)
kv.mu.Unlock()
//fmt.Println(kv.me, " ", 159, "unlock")
return
}
}
5.2 reqNotify
为什么要单独将applyCh处理再通知呢?
因为不止leader会提交applyCh信息,follower也会提交,而我们只需要通知leader,同时这部分还会有snapshot信息,需要单独处理。
func (kv *KVServer) reqNotify() {
for kv.killed() == false {
//fmt.Println("kv length; ", len(kv.applyCh), " ", kv.me)
select {
case msg := <-kv.applyCh:
if msg.SnapshotValid == true {
if msg.Snapshot == nil || len(msg.Snapshot) < 1 { // bootstrap without any state?
return
}
//fmt.Println(kv.me, " revover")
r := bytes.NewBuffer(msg.Snapshot)
d := gob.NewDecoder(r)
kv.mu.Lock()
d.Decode(&kv.CommandSeq)
d.Decode(&kv.Kvdata)
kv.mu.Unlock()
} else {
op := msg.Command.(Op)
op.Term = msg.SnapshotTerm //这边需要重新赋值 是要和start返回的term相比较 如果不同则说明发送了一次新的选举 可能已经不是leader了
op.Index = msg.CommandIndex
//fmt.Println("reqnotify leader ", kv.me)
kv.mu.Lock()
//fmt.Println("reqnotify leader+1 ", kv.me)
opSeqId, ok := kv.CommandSeq[op.ClientId]
if !ok || op.SeqId > opSeqId { // 确保同一个客户端的命令顺序执行 且不会重复执行
if op.Type == "Put" {
kv.Kvdata[op.Key] = op.Value
fmt.Println(op.SeqId, " put ", op.Key, kv.kvdata[op.Key])
} else if op.Type == "Append" {
v := kv.Kvdata[op.Key]
kv.Kvdata[op.Key] = v + op.Value
fmt.Println(op.SeqId, " append ", op.Key, kv.kvdata[op.Key])
}
kv.CommandSeq[op.ClientId] = op.SeqId
}
_, ok = kv.commandReqnoti[op.Index] // 只会再leader里面创建 但是follower提交信息后ye'huiy
if ok {
kv.commandReqnoti[op.Index] <- op
} else {
}
//fmt.Println(kv.me, " server ", kv.rf.GetPersister().RaftStateSize())
w := new(bytes.Buffer)
e := gob.NewEncoder(w)
e.Encode(kv.CommandSeq)
e.Encode(kv.Kvdata)
snapshot := w.Bytes()
if kv.maxraftstate != -1 && kv.maxraftstate <= kv.rf.GetPersister().RaftStateSize() {
kv.rf.Snapshot(msg.CommandIndex, snapshot)
}
kv.mu.Unlock()
}
}
}
}
5.3 StartKVServer
func StartKVServer(servers []*labrpc.ClientEnd, me int, persister *raft.Persister, maxraftstate int) *KVServer {
// call labgob.Register on structures you want
// Go's RPC library to marshall/unmarshall.
labgob.Register(Op{})
kv := new(KVServer)
kv.me = me
kv.maxraftstate = maxraftstate
// You may need initialization code here.
kv.Kvdata = make(map[string]string, 10)
kv.CommandSeq = make(map[int]int, 10)
kv.commandReqnoti = make(map[int]chan Op, 10)
kv.applyCh = make(chan raft.ApplyMsg, 100)
kv.rf = raft.Make(servers, me, persister, kv.applyCh)
// You may need initialization code here.
go kv.reqNotify()
return kv
}
六、优化
目前改代码还不能通过fast那几个测试,这个代码是200ms/ops差了好多,后面我通过修改锁的位置,将其提高到100ms/ops,但是离33ms/ops还挺远的,优化后面有时间再来做吧。