- 实现
Storage
接口,etcd自带了MemoryStorage
:和etcd的wal和snap包做持久化,重启的时候从wal和snap中获取日志恢复MemoryStorage - 对
Ready
的处理
这个Ready结构体封装了一批更新,这些更新包括:
pb.HardState
: 包含当前节点见过的最大的term,以及在这个term给谁投过票,已经当前节点知道的commit indexMessages
: 需要广播给所有peers的消息CommittedEntries
:已经commit了,还没有apply到状态机的日志Snapshot
:需要持久化的快照
应用需要对Ready的处理包括:
- 将
HardState
,Entries
,Snapshot
持久化到storage。 - 将
Messages
(上文提到的msgs)非阻塞的广播给其他peers - 将
CommittedEntries
(已经commit还没有apply)应用到状态机。 - 如果发现
CommittedEntries
中有成员变更类型的entry,调用node的ApplyConfChange()
方法让node知道(这里和raft论文不一样,论文中只要节点收到了成员变更日志就应用) - 调用
Node.Advance()
告诉raft node,这批状态更新处理完了,状态已经演进了,可以给我下一批Ready让我处理。
https://www.youtube.com/watch?v=P9Ydif5_qvE
WAL、Snapshot、网络传输都需要由该库的使用者来完成。
内存kv存储系统
set/get都是通过http协议
核心数据结构为
其中proposeC
是与底层raft库交互的接口
raftNode
其核心数据结构为
- proposeC:这是应用将客户的更新请求传递至底层raft组件的管道;
- commitC:这是底层raft组件通知应用准备提交的指令的通道;
- node:是对底层的raft组件的抽象,所有与底层raft组件的交互都通过该结构暴露的API来实现;
- wal:WAL日志管理,etcd-raft将日志的管理交给应用层来处理;
- snapshotter:同wal,etcd-raft将快照的管理也交给应用层来处理;
- transport:应用同其他节点应用的网络传输接口,同wal,etcd-raft将集群节点之间的网络请求发送和接收也交给应用层来处理。
raftNode是应用与raft核心库连接的桥梁。该结构需要处理的内容包括
- 应用的更新传递给底层raft
- raft已提交的请求传输给应用以更新应用的状态机
- 处理raft组件产生的指令,如选举指令、数据复制指令、集群节点变更指令等;
- 处理WAL日志
- 将底层raft组件的指令通过网络传输至集群其他节点等
包含三个部分
- 初始化raft(在创建raft node的时候,需要指出集群其他节点ip、该节点在集群中的id以及角色等)
- 应用初始化
- 应用开启对外服务大门
初始化raftNode本质上的关键点为
- 创建waldir和一个snapdir,为以后存储WAL日志和snapshot数据;
- 该函数给调用者返回的是三个channel(commitC、errorC和snapshotterReady),这些channel后续作用是什么?不得而知
- 启动内部协程startRaft
其中startRaft比较关键,会启动底层一些与raft协议处理相关联的组件
readCommits逻辑
http服务函数
raftNode对propose的处理
来自serveChannels
函数
接收来自raft的返回
其中publishEntries
方法将返回推到应用层
在应用程序启动时,第一步便是进行日志重放,构建内存状态机。
因为日志又总是和snapshot搅和在一起的,因此,构建内存状态机必须是Snapshot + 日志一起。
而主要的运行逻辑为
randomizedElectionTimeout
参数的设置与作用
tickElection()
为谁调用
状态机启动的配置为
那么step
回调函数是怎么使用的呢?
如何将消息发送到其他raft节点
这个问题的关键是Ready
内容是如何构成的?
由node
组件调用newReady
方法。
由外围节点(即我们应用代码)消费Ready
中的数据并发送出去。
etcd-raft和应用之间是通过channel进行消息的通信,而消息的结构也是由raft库定义好。具体来说,应用通过raft库提供的Ready()接口获取到消息传输管道,并从该管道接收raft库发出的各种指令(Message),最后再通过Advance()通知raft库命令处理结果。应用处理指令的典型流程是:
- 将指令写入WAL日志
- 将指令写入raft组件内存中(为什么要做这个?)
- 将消息中指定的已经commit的日志进行提交,也即:应用到应用状态机中
- 调用Advance接口,应该是通知raft当前命令执行完成,可以继续提供下一条指令了