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

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个操作

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

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,这就没问题了

index := -1

term := -1

isLeader := true

if rf.state != StateLeader {

   isLeader = false

   return index, term, isLeader

}

这样就有个初判断了

通过labrpc传递的数据不对

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?好奇怪

// 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

}

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

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的时候逻辑变为下面的:

// 通知结果

ch, ok := kv.result[index]

if ok {

   select {

   case <-ch:

   default:

   }

}else {

   // 没人读就有了数据?

   ch = make(chan Op,1)

   kv.result[index] = ch

}

ch <- op

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

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

func (kv *RaftKV)AppendLog(entry Op) bool {

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

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

TestUnreliable

☁  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

测试出现阻塞

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

}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏性能与架构

nginx 负载均衡策略

1. 轮询 轮询方式是nginx负载均衡的默认策略,根据每个server的权重值来轮流发送请求,例如: upstream backend { server...

3587
来自专栏ChaMd5安全团队

挖洞经验之代理不当日进内网

大家好,我是STCX,应M姐姐之邀写一篇文章为咱们公众号做点贡献,那我就扯一下之前挖一个漏洞的经验。 ---- 正向代理和反向代理是forward/revers...

3266
来自专栏牛客网

知识总结:I/O模型基础I/O基础

I/O基础 1、java1.4之前,java对I/O支持不完善,存在以下问题: 没有数据缓冲区,I/O性能存在问题。 没有C或者C++的channel概念,只...

3769
来自专栏移动端周边技术扩展

python pytesseract

1685
来自专栏IMWeb前端团队

针对Vue的后台权限功能实现思路(持续更新)

权限是一块设计挺繁琐的功能,尤其是设计到前端SPA应用,前后端的耦合性太强,先屡屡思路,再实现,如果您有好的建议,也可评论留言。 基本的表结构如下 用户表。...

2505
来自专栏码洞

一种简单的Failover机制

在应用结构上有这样一个业务场景,机房里部署了多个物理数据库的Proxy无状态节点,业务端通过Proxy节点间接和存储DB交互。Proxy支持了分库分表的特性,管...

1312
来自专栏祥子的故事

Tensorflow | win10中安装tensorflow-0.12.1 (0.12.1以后的版本安装均适用)

8597
来自专栏小鹏的专栏

tf API 研读6:Running Graphs

会话管理 (Session management) 操作 描述 class tf.Session 运行TF操作的类, 一个Session对象将操作节...

2136
来自专栏逆向技术

脱壳第三讲,UPX压缩壳,以及补充壳知识

           脱壳第三讲,UPX压缩壳,以及补充壳知识 一丶什么是压缩壳.以及壳的原理 在理解什么是压缩壳的时候,我们先了解一下什么是壳 1.什么是壳 ...

2688
来自专栏Danny的专栏

MySQL安装图解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

2303

扫码关注云+社区

领取腾讯云代金券