MIT 6.824 Lab3 Fault-tolerant Key/Value Service

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还挺远的,优化后面有时间再来做吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值