2. 一步一步带你实现raft(2A)

目标:

Implement leader election and heartbeats (AppendEntries RPCs with no log entries). The goal for Part 2A is for a single leader to be elected, for the leader to remain the leader if there are no failures, and for a new leader to take over if the old leader fails or if packets to/from the old leader are lost. Run go test -run 2A to test your 2A code.

第一步

Add any state you need to the Raft struct in raft.go. You'll also need to define a struct to hold information about each log entry. Your code should follow Figure 2 in the paper as closely as possible.

image.png

同时为了表明一台RAFT SERVER的状态,我们需要一个枚举类

定义LOG的时候,注意大写,因为这个会在RPC传输的时候序列化。相关博客

image.png

第二步 实现GET STATE

有了上面的定义,很好实现


image.png

第三步 实现RequestVoteArgs,RequestVoteReply

image.png

image.png

下面代码框架里并没有给AppendEntries的REQUEST 和 REPLY,因为LEADER选举,需要发心跳,所以我们不妨也先实现。

第四步 实现RequestVoteArgs,RequestVoteReply

image.png

image.png

第五步(来自HINT)

Modify Make() to create a background goroutine that will kick off leader election periodically by sending out RequestVote RPCs when it hasn't heard from another peer for a while. This way a peer will learn who is the leader, if there is already a leader, or become the leader itself.

  1. 初始化成员变量


    image.png
  2. 因为Make() must return quickly, so it should start goroutines, 所以开一个goroutine

  3. kick off leader election periodically by sending out RequestVote RPCs when it hasn't heard from another peer for a while, 这里涉及到超时机制,我们需要用到SELECT 和CHANNEL

  4. 定义channel, 当收到了rpc,需要打破SELECT等待


    image.png
  5. 实现select , channel(根据当前SERVER不同的身份,来监听不同的CHANNEL)


    image.png

上面的代码完成了下图的状态转换的红框部分


image.png

第6步,实现beCandidate()

image.png

第7步, 实现startElection

重读下段论文

为了开始选举,一个追随者会自增它的当前任期并且转换状态为候选人。然后,它会给自己投票并且给集群中的其他服务器发送 RequestVote RPC。一个候选人会一直处于该状态,直到下列三种情形之一发生:

  • 它赢得了选举;
  • 另一台服务器赢得了选举;
  • 一段时间后没有任何一台服务器赢得了选举

这些情形会在下面的章节中分别讨论。

一个候选人如果在一个任期内收到了来自集群中大多数服务器的投票就会赢得选举。在一个任期内,一台服务器最多能给一个候选人投票,按照先到先服务原则(first-come-first-served)(注意:在 5.4 节 针对投票添加了一个额外的限制)。大多数原则使得在一个任期内最多有一个候选人能赢得选举(表 -3 中提到的选举安全原则)。一旦有一个候选人赢得了选举,它就会成为领导人。然后它会像其他服务器发送心跳信息来建立自己的领导地位并且组织新的选举。

当一个候选人等待别人的选票时,它有可能会收到来自其他服务器发来的声明其为领导人的 AppendEntries RPC。如果这个领导人的任期(包含在它的 RPC 中)比当前候选人的当前任期要大,则候选人认为该领导人合法,并且转换自己的状态为追随者。如果在这个 RPC 中的任期小于候选人的当前任期,则候选人会拒绝此次 RPC, 继续保持候选人状态。

第三种情形是一个候选人既没有赢得选举也没有输掉选举:如果许多追随者在同一时刻都成为了候选人,选票会被分散,可能没有候选人能获得大多数的选票。当这种情形发生时,每一个候选人都会超时,并且通过自增任期号和发起另一轮 RequestVote RPC 来开始新的选举。然而,如果没有其它手段来分配选票的话,这种情形可能会无限的重复下去。

Raft 使用随机的选举超时时间来确保第三种情形很少发生,并且能够快速解决。为了防止在一开始是选票就被瓜分,选举超时时间是在一个固定的间隔内随机选出来的(例如,150~300ms)。这种机制使得在大多数情况下只有一个服务器会率先超时,它会在其它服务器超时之前赢得选举并且向其它服务器发送心跳信息。同样的机制被用于选票一开始被瓜分的情况下。每一个候选人在开始一次选举的时候会重置一个随机的选举超时时间,在超时进行下一次选举之前一直等待。这能够减小在新的选举中一开始选票就被瓜分的可能性。9.3 节 展示了这种方法能够快速的选出一个领导人。

实现的思路

开始选举,无非就是向其他节点发送REQUEST VOTE RPC。那么就要构造REQUEST VOTE ARG, 同时把 REQUEST VOTE REPLY 的指针传进去,好拿到结果。


image.png
allserver.png
image.png

这里因为是开协程去做,所以更新票数的时候要用atomic

image.png
image.png

第8步, 实现beFollower,beLeader

image.png

第9步(来自HINT)

Implement the RequestVote() RPC handler so that servers will vote for one another.

allserver.png

image.png

image.png

现在状态转换又实现了3个步骤


image.png

第9步 (来自HINT)

To implement heartbeats, define an AppendEntries RPC struct (though you may not need all the arguments yet), and have the leader send them out periodically. Write an AppendEntries RPC handler method that resets the election timeout so that other servers don't step forward as leaders when one has already been elected.

image.png
image.png
image.png

因为只是做一个心跳,所以LOG不用真的传真的写


image.png

image.png
image.png

调试阶段

BUG 1

image.png

image.png

image.png

i 应该变成 idx

BUG 2

image.png

做边界检查,代码改动如下

image.png

WARNING 1

虽然过了测试,但是打了个WARNING给我。我修改了TESTCODE,加了点输出,发现即使网络没有坏,LEADER也换了。为什么呢?


image.png

image.png

那一定就是FOLLOWER或者CANDIDATE等待超时了,才会去更新CURRENT TERM。那么超时是受HEARTBEAT来维持的。检查HEARTBEAT代码,发现问题。

再最后发送REPLY了之后,要去SEND APPLY ENTRY RPC 的CHANNEL

加上代码


image.png

PASS 2A

image.png

Add lock

上面的实现是没有带锁的,所以在run go test -race的时候会收到warning

阅读下边这个链接里的文章。

  • If you are puzzled about locking, you may find this advice helpful.

按照里面的思路,

我的策略如下:

  1. 在所有用到共享数据的地方加锁。
  2. 不要在阻塞等待的地方持有锁。
  3. 一些方法 是为了简化实现的,在外层加锁。比如beCandidate,因为Golang中的sync.Mutex是不可重入的
  4. Similarly, reply-handling code after the Call() must re-check all relevant assumptions after re-acquiring the lock

红色为加锁,蓝色为阻塞


image.png

image.png

image.png

image.png

image.png

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

推荐阅读更多精彩内容