聊聊RAFT的一个实现(4)–NOPCommand

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 在我的文章聊聊RAFT的一个实现(3) 我曾经提到NOPCommand也是一种LogEntry,它会在集群中被分发。那么它有什么作用呢? 2. NOPCommand 我们依然使用raft paper 5.4 Safety 图8来说明这个问题 2.1 场景1 如图所示,集群中有S1 ~ S5,5个节点,在时间点(A),S1是leader, 它接收到外部的指令,生成logIndex 2的日志,并复制到S2。在时间点(B)S1奔溃了,S5在term 3通过S3、S4和自己的选票赢得选举,称为leader。它从客户端接收到一条不一样的日志条目放在logIndex 2。然后到时间点(C1), S5又崩溃了;S1重新启动,被选举为leader, 开始复制日志LogEntry{Term:2, Index:2} 到S3, 此时已经达到了提交点。LogEntry{Term:2, Index:2} 已经被复制到majority,可以向状态机中写入数据了。但是接下来时间点(D1) S1可能再次崩溃,S5重新启动,S5可以重新被选举成功(通过S3、S4以及它自己的选票)。然后复制日志LogEntry{Term:3, Index:2}到S2、S3、S4。 如果事情进展按上面的情况发生, 那么显然违反了raft论文所要求 Leader Completeness和State Machine Safety 领导人完全特性–如果某个日志条目在某个任期号中已经被提交,那么这个条目必然出现在更大任期号的所有领导人中(5.4 节) 状态机安全特性–如果一个领导人已经在给定的索引值位置的日志条目应用到状态机中,那么其他任何的服务器在这个索引位置不会提交一个不同的日志(5.4.3 节) 2.2 场景2 为了解决这个问题, raft引入了空命令NOPCommand Upon election: send initial empty AppendEntries RPCs (heartbeat) to each server; 一旦选举为leader, leader应该立马发送一个空命令给其它所有节点。 ...

December 4, 2018 · 2 min

聊聊Raft的一个实现(2)-日志提交

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 在我的上一篇文章聊聊Raft的一个实现(1),我简要的介绍了 goraft/raftd。这篇文章我将结合goraft的实现,来聊聊raft中的一些场景 2. 场景1-正常的执行1条WriteCommand命令 在上一篇文章,我们已经提到WriteCommand和NOPCommand、JoinCommand一样,对goraft而言都是LogEntry, 执行它时,这条命令会被分发到整个Cluster,让我们看看其中的详细过程 当前我们有3个node |节点|state|name|connectionString|term|lastLogIndex|commitIndex| |:—|:—|:—|:—|:—|:—| |node1|leader|2832bfa|localhost:4001|17|26|26| |node2|follower|3320b68|localhost:4002|17|26|26| |node3|follower|7bd5bdc|localhost:4003|17|26|26| 从上表可以看出整个集群处于完全一致的状态,我们开始执行WriteCommand step1 client:通过API提交WriteCommand命令 curl -XPOST http://localhost:4001/db/aaa -d 'bbb' step2 node1:收到指令后,生成LogEntry 写入logfile (磁盘文件) 2)添加到Log.entries (内存) step3 node1:等待Heartbeat(周期性由leader发往其它每个node1和node2), 把LogEntry带给其它node(这里的node1,node2状态相同,所以AppendEntriesRequest是一样的) AppendEntriesRequest { "Term": 17, "PrevLogIndex": 26, "PrevLogTerm": 17, "CommitIndex": 26, "LeaderName": "2832bfa", "Entries": [{ "Index": 27, "Term": 17, "CommandName": "write", "Command": "eyJrZXkiOiJhYWEiLCJ2YWx1ZSI6ImJiYiJ9Cg==" }] } 这里对PrevLogIndex做下简单的解释,PrevLogIndex表示的是leader所认为的follower与leader保持一致的最后一个日志index。PrevLogTerm是与PrevLogIndex对应的term。 Command做了base64编码解码后 { "key": "aaa", "value": "bbb" } 现在解释下上面的AppendEntriesRequest,node1(leader)告诉node2(follower) 如果LogIndex 26, 咱们是一致的, 那么Append LogIndex 27 ...

November 29, 2018 · 2 min

聊聊Raft的一个实现(1)--goraft

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 最近花了不少时间来学习raft相关的知识,写这篇文章的目的 1)为了对raft中让我困惑的问题进行总结 为了对其它人的阅读便利,提供必要的帮助 网上raft的实现不少,但能够独立运行的样本工程实在不多。我选择的是 goraft/raftd raft的paper是为数不多的连实现都写的比较清楚的文档,因此在阅读本文前,请确保你已经大致了解过raft的协议 2. 概述 goraft/raftd 依赖 goraft/raft, goraft/raft是分布式一致性算法raft的实现,而goraft/raftd 已经可以称之最简单的分布式key-value数据库(服务)了 聊到一个服务,我们最常用的手法, 看它的接口。 看它的数据如何存储,包括内部的数据结构,数据如何落地 2.1 接口 接口大致可以分为2组 2.1.1 HTTP之上RPC协议 这其实goraft的作者对raft协议所要求的几个RPC的实现,包括 AppendEntries RPC RequestVote RPC Snapshot RPC (非raft协议要求的) SnapshotRecovery RPC (非raft协议要求的) 2.1.2 普通的HTTP API POST /db/{key} 写入key-value curl -XPOST localhost:4001/db/foo -d 'bar' GET /db/{key} 读取key对应的value curl localhost:4001/db/foo POST /join 把某个节点加入集群 curl http://localhost:4001/join -d '{"name":"5f2ac6d","connectionString":"http://localhost:4001"}' 这里有3个有趣的事情 1)这个节点可以是虚假的节点 2)由于JoinCommand 本身也是一条LogEntry, 所以当这条消息被发给Leader时,Cluster中的所有成员都会知道有一个新节点的加入(加入 raft.Server.peers) 3) 1个节点掉线后,它不会从raft.Server.peers移除 为了便于观察raft.Server的内部状态 我又扩充了3个接口 传送门vearne/raftd ...

November 28, 2018 · 2 min