女主宣言
今天小编为大家分享一篇关于Golang实现Raft的文章,本篇文章为系列中的第三篇,对Raft中的命令和日志复制进行介绍并使用go进行实现。希望能对大家有所帮助。
PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!
本篇文章为Raft系列文章中的第三篇,命令和日志复制。在该篇中,我们将增强Raft的相关功能实现,实际的处理客户端提交的命令并在Raft集群中进行复制。
1
客户端交互
在第一篇文章中我们简要讨论了客户端交互,如果不清晰建议可以再回顾一下。这里我们先不关注客户端如何找到领导者,将重点讨论当找到一个领导者时会发生什么。
首先,客户端将命令提交给领导者。在Raft集群中,命令通常只提交给单个节点。
领导者将命令复制到其跟随者。
最后,如果大多数集群节点都承认在其日志中有该命令,该命令将被提交,并向所有客户端通知新的提交。
注意提交和提交命令之间的不对称性 - 在检查我们即将讨论的实现决策时,这一点很重要。命令被提交到单个Raft节点,但是多个节点(特别是所有已连接/活动的节点啊)会在一段时间后将其提交并通知其客户端。
回顾此图:
状态机代表使用Raft进行复制的任意服务。
然后我们在Raft ConsensusModule模块的上下文中讨论客户端,我们通常指的是此服务,因为这是将提交报告到的地方。换句话说,从Consensus模块到服务状态机的黑色箭头就是该通知。
2
实现:提交管道
在我们的实现中,当一个 ConsensusModule 被创建时,它接受一个提交管道 - 一个用来向调用者发送提交命令的通道:commitChan chan<-CommitEntry。定义如下:
// CommitEntry is the data reported by Raft to the commit channel. Each commit
// entry notifies the client that consensus was reached on a command and it can
// be applied to the client's state machine.
type CommitEntry struct {
// Command is the client command being committed.
Command interface{}
// Index is the log index at which the client command is committed.
Index int
// Term is the Raft term at which the client command is committed.
Term int
}
使用通道是一种设计选择,但不是唯一方式。也可以改用回调。创建ConsensusModule时,调用者将注册一个回调函数,只要有要提交的命令,就会调用该回调函数。
在实现通道上发送条目的功能之前。我们需要先讨论Raft服务器如何复制命令并确定命令是否已提交。
3
Raft日志
在文章中多次提到Raft日志,但还没有详细介绍。日志只是应该应用于状态机的线性命令序列;如果有需要,日志应该足以从某个开始状态“重放”状态机。在正常运行期间,所有Raft节点的日志都是相同的;当领导者收到新命令时,将其存放在自己的日志中,然后复制到跟随者。跟随者将命令放在日志中,并确认给领导者,领导者将保留已安全复制到群集中大多数服务器的最新日志索引的计数。
每个框都是一个日志条目;框顶部的数字是将其添加到日志中的任期。底部是此日志包含的键值命令。每个日志条目都有一个线性索引。框的颜色是任期的另一种表示形式。
如果将此日志应用于空键值存储,则最终结果将具有值x = 4,y = 7。
在我们的实现中,日志条目由以下形式表示:
type LogEntry struct {
Command interface{}
Term int
}
每个ConsensusModule的日志都只是lo