读过文章之后,我们虽然不知道具体的程序要怎么动笔,但是让我们说出来怎么选举领导人,怎么复制日志,对大家来说问题不大了。

怎么写第一步程序呢?我虽然看着pontoon的源码,但是依然有些无从下手的准备。 经过分析论文之后,感觉全文使用最多的就是通信的数据结构了。RequestVote RPC 和AppendEntries RPC 是通信的根本,而且,也定义了这两个函数的输入数据和输出数据,简直就只把代码给我们了呀!

前言

由于都是远程调用通信,我们可以直接使用rpc进行通信,protobuf作为数据格式,grpc进行通信即可。在此处不对rpc进行赘述,可以参考rpc学习或者官方网站进行学习。

AppendEntries

文章中说,附加日志远程调用,是由领导人进行复制日志和心跳包使用的,其中需要包含以下内容

1
2
3
4
5
6
Term 整数,表示领导人的任期
leaderId 整数,表示当前领导人的ID,这样其他客户端也可以给它发送消息
prevLogIndex 整数,最新日志的上一条日志序号
prevLogTerm 整数,prevLogIndex的领导人的Term值
entries[] 数组,各个节点要存储的日志内容
leaderCommit 整数,提交日志的序号

为什么要用两个prevLog呢?论文中也说到了,是为了检查日志连续性的,我们以后的代码会涉及到对这两点的检验。 客户端收到leader发来的请求后,需要回复给leader,回复的内容如下

1
2
term 整数,当前的任期号
success  布尔,如果数据通过,就返回true

理解了这些,我们就可以放心的定义数据结构了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
message EntryRequest {
    int64 LeaderCommit = 1;
    int64 Term = 2;
    string LeaderID = 3;
    int64 PrevLogIndex = 4;
    int64 PrevLogTerm = 5;
    bytes Data = 6;
}

message EntryResponse {
    int64 Term = 1;
    bool Success = 2;
}

对于这个的操作服务也是很简单

1
2
3
4
service Mgr {
    rpc AppendEntries (EntryRequest) returns (EntryResponse) {
    }
}

RequestVote

除了提供这个服务,还有一个RequestVote服务,请求方需要提供这些数据内容

1
2
3
4
Term 整数,候选人的任期号
CandidateId 字符串,候选人的Id
lastLogIndex 整数,候选人最新的日志序号
lastLogTerm 整数,候选人最新日志的任期号

数据返回内容一样很简单

1
2
Term 整数,目前的任期号,候选人可以进行更新
VoteGranted 布尔,是否给候选人投票

转换成代码就是这样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
message VoteRequest {
    int64 Term = 1;
    string CandidateID = 2;
    int64 LastLogIndex = 3;
    int64 LastLogTerm = 4;
}
message VoteRespond {
    int64 Term = 1;
    bool voteGranted = 2;
}

rpc调用也很简单

1
2
3
4
service Mgr {
    rpc RequestVote (VoteRequest) returns (VoteRespond) {
    }
} 

写好后,我们就可以编译生成go文件了。 文件内容参见message.proto

编译

由于我使用的是window系统,在运行的时候很容易找不到对应的库文件,我们需要进行指定,我的命令是这样的

1
D:\develop\kit\protoc\bin\protoc.exe  --plugin=protoc-gen-go=%GOPATH%\bin\protoc-gen-go.exe --go_out=plugins=grpc:. message.proto

大家可以参考下。

结束

这块的内容还是比较简单的,因为论文几乎给了我们直接的数据结构,我们所需做的就是写成代码而已。我们会继续进行后续的文章。