前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >6.824 Lab 3: Fault-tolerant Key/Value Service Part-AIntroduction实际设计中出现的问题

6.824 Lab 3: Fault-tolerant Key/Value Service Part-AIntroduction实际设计中出现的问题

作者头像
zhuanxu
发布2018-08-23 12:48:44
8710
发布2018-08-23 12:48:44
举报
文章被收录于专栏:进击的程序猿进击的程序猿

Introduction

该实验是mit 6.824课程的第3个实验,基于raft协议完成一个key-value系统

实验分为A和B两个部分,在Part A中:我们不考虑日志的大小,在Part B中会完成快照功能

完整的代码地址 课程地址 实验地址 已经有的实验地址: Lab 1: MapReduce6.824 Lab 1: MapReduce(2016) Lab 2: Raftraft 系列解读(2) 之 测试用例 Lab 3: KV Raft Part-A6.824 Lab 3: Fault-tolerant Key/Value Service Part-A

Part A: Key/value service without log compaction

支持3个操作

代码语言:javascript
复制
Put(key, value):改变key的值

Append(key, arg):给key的值新增value

Get(key):返回值

任务

当没有丢包和servers fail的情况下进行实现,需要提供客户端顺序一致性的api,调用Put,Append和Get3个api,在所有的server以相同的顺序执行,并且具有at-most-once的语义 一个建议的计划是:先完成server.go中的Op结构,然后完成server.go中的PutAppend()Get()操作,在操作中,应该先调用Start(),当日志commit的时候,回复客户端

提示

  1. 调用Start()后,kvraft servers 会等待raft log达成一致,通过applyCh获取一致的命令,我们需要考虑怎么安排代码,才能持续读取applyCh,而其他命令也能执行
  2. 我们需要处理case:leader调用了Start(),但是在log commit之前,丢失了leadership,这种情况下,代码应该将请求重新发送给新的leader。一种方式是,server需要检测出自己已经不是leader了,通过查看相同的start在index上返回一个不用的请求,另一种方式是通过调用GetState(),但是如果出现网络分区,可能不知道自己已经不是leader了,这种情况下client和server都处在网络分区中,因此可以无限的等待下去,直到网络恢复
  3. A kvraft server不应该完成Get()操作如果得不到majority,因为这样子可能会得不到最新的数据

任务:

需要处理重复请求,保证满足at-most-once的语义

提示:

  1. 需要对每个client请求编号
  2. 要保证快速的释放内存,因此可以在下一个请求带上下一个请求

实际设计中出现的问题

频繁变化leader

代码语言:javascript
复制
func (ck *Clerk) Get(key string) string {

   args := GetArgs{Key:key}

   for {

      for _,c := range ck.servers {

         time.Sleep(time.Millisecond*1000)

         reply := GetReply{}

         ok := c.Call("RaftKV.Get", &args, &reply)

         if ok && !reply.WrongLeader {

            return reply.Value

         }

      }

   }

   // You will have to modify this function.

   return ""

}

此处如果没有sleep的话,相当于客户端一直不断的在START,导致的一个问题是:server不断在处理START命令,导致正常的心跳都完成不了了,就出现了频繁的变化leader了,问题很严重,那应该怎么做呢?

后来做了优化,对于读操作不走 chan,这就没问题了

代码语言:javascript
复制
index := -1

term := -1

isLeader := true

if rf.state != StateLeader {

   isLeader = false

   return index, term, isLeader

}

这样就有个初判断了

通过labrpc传递的数据不对

代码语言:javascript
复制
func StartKVServer(servers []*labrpc.ClientEnd, me int, persister *raft.Persister, maxraftstate int) *RaftKV {

   // call gob.Register on structures you want

   // Go's RPC library to marshall/unmarshall.

   gob.Register(Op{})

如果没有 gob.Register(Op{}) 这就错误,为什么要加上这句话呢?

出现阻塞

分析:此处阻塞了为什么呢?因为在get上的时候,有一个没有收到apply?好奇怪

代码语言:javascript
复制
// TODO:优化超时的逻辑

select {

case op := <-ch:

   commited := op == entry

   kv.logger.Debug("index:%d commited:%v",index,commited)

   return commited

case <- time.After(AppendTimeOut):

   kv.logger.Info("index:%d %s timeout after %v",index, entry.Type,AppendTimeOut)

   return false

}

加上上面的超时逻辑后,就可以解决阻塞的问题,但是一旦超时

代码语言:javascript
复制
2016/10/26 14:37:45 I index:323 Append timeout after 1s

2016/10/26 14:37:45 0: client new get 0

2016/10/26 14:37:45 get wrong value, key 0, wanted:

就会出现问题,会重复执行 Append操作,因为其实已经apply了这个请求了

那怎么解决呢?我现在去除这个超时限制,在获取Apply的时候逻辑变为下面的:

代码语言:javascript
复制
// 通知结果

ch, ok := kv.result[index]

if ok {

   select {

   case <-ch:

   default:

   }

}else {

   // 没人读就有了数据?

   ch = make(chan Op,1)

   kv.result[index] = ch

}

ch <- op

此时就不会有超时的问题了,为什么呢?

很反人类的问题:因为当调用

代码语言:javascript
复制
func (kv *RaftKV)AppendLog(entry Op) bool {

   index, _, isLeader := kv.rf.Start(entry)

此时可能没等到执行下面的去读chan的时候,已经apply成功了,因此我们就需要事先往chan里面存入数据

TestUnreliable

代码语言:javascript
复制
☁  kvraft [master] ⚡ go test -run TestUnreliable

Test: unreliable ...

2016/10/26 14:59:42 get wrong value, key 3, wanted:

x 3 0 yx 3 1 y

, got

x 3 0 yx 3 0 yx 3 1 y

很容易看出问题:一个请求重复执行了,我们需要在客户端去重

对于每个客户端都给编号,然后每个请求都顺序增长

TestManyPartitionsManyClients

测试出现阻塞

代码语言:javascript
复制
select {

case op := <-ch:

   commited := op == entry

   kv.logger.Debug("index:%d commited:%v", index, commited)

   return commited

   // 此处超时其实也很好理解,因为刚开始是leader,但是在log得到commit之前,丢失了leadership,此时

   // 如果没有超时机制,则会一直阻塞下去

   // 或者由于此时的leader是一个分区里面的leader,则只可能一直阻塞下去了

   // 因此也需要超时

case <-time.After(AppendTimeOut):

   //kv.logger.Info("index:%d %s timeout after %v", index, entry.Type, AppendTimeOut)

   return false

}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016.10.26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Introduction
    • Part A: Key/value service without log compaction
    • 实际设计中出现的问题
      • 频繁变化leader
        • 通过labrpc传递的数据不对
          • 出现阻塞
            • TestUnreliable
              • TestManyPartitionsManyClients
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档