5. 基于RAFT 实现容错KV服务(3A)

第一步阅读文档

https://pdos.csail.mit.edu/6.824/labs/lab-kvraft.html
阅读第8章
https://www.infoq.cn/article/raft-paper
根据文档弄清楚交互流程

image.png

image.png

第二步 理解代码框架

CLIENT 端

type Clerk struct {

这里定义CLERK 属性

func MakeClerk(servers []*labrpc.ClientEnd) *Clerk {

新建一个CLERK,同时会告知一组SERVER

func (ck *Clerk) Get(key string) string {

发送GET请求的代码

func (ck *Clerk) PutAppend(key string, value string, op string) {

发送PUT 或 APPEND请求的代码

SERVER 端

type Op struct {

Op是要发向RAFT的COMMAND

type KVServer struct {
    mu      sync.Mutex
    me      int
    rf      *raft.Raft
    applyCh chan raft.ApplyMsg

KV SERVER 自己会有把锁,有一个RAFT PEER, 自己的序号,还有一个和RAFT通信的APPLY CHANNEL

func (kv *KVServer) Get(args *GetArgs, reply *GetReply) {

func (kv *KVServer) PutAppend(args *PutAppendArgs, reply *PutAppendReply) {

上面2个是和CLERK 通信的 RPC HANDLER

func (kv *KVServer) Kill() {

KILL 掉一个KV SERVER的命令

func StartKVServer(servers []*labrpc.ClientEnd, me int, persister *raft.Persister, maxraftstate int) *KVServer {

初始化一个KV SERVER,测试函数会在START SERVER的时候调用,注意不要阻塞,应该快速返回。


image.png

第三步 实现Client Get && PutAppend

因为要发GET RPC 给SERVER,所以看下 ARGS 和 REPLY


image.png

因为如果找错了LEADER,需要换一个重试,而是不是对的LEADER 应该就是WRONG LEADER这个域
在发请求的时候,需要用FOR,找到正确的LEADER为止。


image.png
image.png

根据文档,还要实现幂等性,这里的逻辑之后实现幂等性的时候还要加

第四步 from Hint

You will probably have to modify your Clerk to remember which server turned out to be the leader for the last RPC, and send the next RPC to that server first. This will avoid wasting time searching for the leader on every RPC, which may help you pass some of the tests quickly enough.


image.png

image.png

在PUT APPEND里同样的代码

client 告一段落,下面开始写SERVER端

第五步 实现Op

op就是发送给RAFT的COMMAND


image.png

第六步 实现 Get RPC HANDLER

一开始很NAVIE的实现,就是提交给RAFT后,去直接听applyCh。但是想到可能会有其他的CLERK也往LEADER发信息,那么这个APPLY CH 可能返回的不一定是你要的那个MSG。而这个MSG从CHANNEL里读掉就没了。那个需要的CLERK可能就拿不到了。
带着问题我去读了STUDENT GUIDE
里面说的和我想的一模一样

You might start off by having your service, whenever it receives a client request, send that request to the leader, wait for Raft to apply something, do the operation the client asked for, and then return to the client. While this would be fine in a single-client system, it does not work for concurrent clients.
解决方法文章下面也有暗示。

这个坎还是要思考一下的

可以通过Raft的Start函数返回请求在log中的index,对于每个index创建一个channel来接收执行完成的消息,接收到了之后,知道是完成操作了,然后回复给client

需要单独开一个goroutine来监视apply channel,一旦底层的Raft commit一个,就立马执行一个。

让我们来整理下思路。
当发送一个GET请求,它会去调用START方法,然后拿到INDEX。去监听这个INDEX上的CHANNEL。


image.png

后台有一个GO ROUTINE,会循环处理APPLY CHANEL上的消息。来了一个消息,就打到对应的INDEX的CHANNEL上。


image.png

然后一直到那个GET请求的CHANNEL上的消息来了,返回给客户端。

根据

you can tell whether or not the client’s operation succeeded based on whether the operation that came up for that index is in fact the one you put there. If it isn’t, a failure has happened and an error can be returned to the client.

我们可以知道如果这2个INDEX不一致的话,就说明有错。基于上述思路来写代码。

1.构建map[int] chan Op, 初始化

  1. 后台开一个GOROUTINE, 去监听applyCh, 发现来了一个,做处理,找到applyMsg 的CHANNEL ,NOTIFY BY OP
  2. 在GET HANDLER里 发送这个命令,拿到INDEX,随后去监听INDEX直到消息回来。然后去KV.DB里读,还需要CHECK OP是不是一致,不一致的话,就代表有错。

实现

image.png
image.png
image.png
image.png

PASS 3A1

image.png

第七步 对共享变量加锁

image.png

image.png
image.png

PASS 3A2

image.png

第八步 实现幂等

目标

Add code to cope with duplicate Clerk requests, including situations where the Clerk sends a request to a kvserver leader in one term, times out waiting for a reply, and re-sends the request to a new leader in another term. The request should always execute just once. Your code should pass the go test -run 3A tests.

参见STUDENT GUIDE Duplicate detection

One simple and fairly efficient one is to give each client a unique identifier, and then have them tag each request with a monotonically increasing sequence number. If a client re-sends a request, it re-uses the same sequence number. Your server keeps track of the latest sequence number it has seen for each client, and simply ignores any operation that it has already seen.

image.png

RPC参数这边我的思考是 读请求本身就具备幂等性。所以我加在PUTAPPEND ARG上


image.png

image.png

SERVER端 增加幂等判断
这里有一个思考的点,幂等判断是加在发送PUTAPPEND的地方还是加在专门监听APPLY MSG的地方。

如果不仔细想,可能会觉得加在PUTAPPEND的地方,这样可以减少往RAFT 发LOG的开销。不过这样就会有一个问题。因为你加在这边了,你是不能再PUTAPPEND的时候就去更新clientid to seqnum 的这个MAP,因为可能你这个消息发出去,不一定保证会被COMMIT.贸然更新就会出错,我们要知道何时被COMMIT还是要回到监听APPLY MSG的地方。所以把幂等逻辑放在了第二处。

image.png
image.png

pass until Partition

image.png

第九步 实现TIMEOUT 逻辑

到这步之前,可以测过所有非PARTITION的。
PARTITION这个,因为RAFT LEADER会拿不到足够的票数无法COMMIT,所以CLERK 会被BLOCK住。根据要求,这边需要加个超时逻辑


image.png

对应更新,GET 和 PUTAPPEND的代码。

之后可以跑过全部3A TEST了

zyx@zyx-virtual-machine:~/Desktop/mit6824/mit6.824/src/kvraft$ go test -run 3A
Test: one client (3A) ...
  ... Passed --  15.5  5  6919  148
Test: many clients (3A) ...
  ... Passed --  16.7  5 18013  741
Test: unreliable net, many clients (3A) ...
  ... Passed --  16.9  5  2246  604
Test: concurrent append to same key, unreliable (3A) ...
  ... Passed --   2.3  3   183   52
Test: progress in majority (3A) ...
  ... Passed --   0.9  5    85    2
Test: no progress in minority (3A) ...
  ... Passed --   1.2  5   112    3
Test: completion after heal (3A) ...
  ... Passed --   1.0  5    47    3
Test: partitions, one client (3A) ...
  ... Passed --  22.6  5 12458  116
Test: partitions, many clients (3A) ...
  ... Passed --  23.6  5 24778  519
Test: restarts, one client (3A) ...
  ... Passed --  20.5  5 23348  155
Test: restarts, many clients (3A) ...
  ... Passed --  21.6  5 75660  742
Test: unreliable net, restarts, many clients (3A) ...
  ... Passed --  24.3  5  3749  617
Test: restarts, partitions, many clients (3A) ...
  ... Passed --  29.1  5 69641  606
Test: unreliable net, restarts, partitions, many clients (3A) ...
  ... Passed --  29.1  5  2817  421
Test: unreliable net, restarts, partitions, many clients, linearizability checks (3A) ...
  ... Passed --  25.5  7  8061  751
PASS
ok      kvraft  251.633s

第九步 代码优化

因为_,isLeader := kv.rf.GetState()的判断被包含在index,_,isLeader := kv.rf.Start(originOp)
所以上面的代码可以去掉。

image.png

实现KILL,方法和之前在RAFT 实现的时候一样,加个KILL CH


image.png

go test -race 测下来没有问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容