我们之前定义了raft通信的基本数据格式以及rpc模式也生成了数据内容,这篇文章就要开始具体的操作了。

前言

由于本部分涉及到很多的判断过程,我们想要一蹴而就是不可能的,所以我们应该借鉴面向接口的思想,先定义好接口,外部操作直接调用,具体的操作我们以后再进行判断。

日志操作

在raft中在,最重要的判断就是日志的操作了吧,无论是增加日志,还是判断日志内容,抑或取出一个日志,我们都是对日志的操作,所以我们为了方便,先定义一个队日志的基本操作。 我们先自己考虑下有什么对日志的操作,目前至少这几个是要有的 1. 日志内容对比:我们知道有prevLogTerm这些内容需要校验,这个是必须的 2. 日志内容添加:Leader发来新的日志,Follower需要添加进来 3. 取出日志最新任期,最新序号,以及已经执行的最新序号

大体就是以下几个内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type Logger interface {
	Check(prevLogIndex int64, prevLogTerm int64, index int64, term int64) error
	Append(e *Entry) error
	FresherThan(index int64, term int64) bool
	Get(index int64) *Entry
	GetEntryForRequest(index int64) (*Entry, int64, int64)
	Index() int64
	LastIndex() int64
	Term() int64
}

其中entry就是日志的内容了,我们可以想下entry至少有以下结构

1
2
3
4
5
6
type Entry struct {
	CmdID int64
	Index int64
	Term  int64
	Data  []byte
}

好,关于日志的定义我们暂时先定义到这。

peer内容

我们从论文中可以看到,leader是需要维护一个数据结构的,这个数组里面有nextIndex和matchIndex,用来给不同的follower发送日志内容,因此我们定义这么一个结构

1
2
3
4
5
type Peer struct {
	ID         string
	NextIndex  int64
	MatchIndex int64
}

通信

我们应该还记得,上一篇文章我们定义了通信的基本要求,在这个地方我们需要二次封装下 首先是client端,我们知道,client端是可以生成client之后,直接调用函数进行操作的,但是client的生成需要地址,参数等,所以我们定义一个接口

1
2
3
4
type GRPCClient interface {
	RequestVote(address string, request *pb.VoteRequest) (*pb.VoteRespond, error)
	AppendEntries(address string, request *pb.EntryRequest) (*pb.EntryResponse, error)
}

这个就是我们client需要实现的内容了 server的话,不仅需要实现proto定义的service,还有定义这么几个接口,包括启动服务,停止服务,处理网络参数等

1
2
3
4
5
6
7
type GRPCServer interface {
	Address() string
	Start(n *Node) error
	Stop()
	Server() *grpc.Server
	Listener() net.Listener
}

状态机

我们还差最后一个部分,就是把日志内容执行的部分,我们收到了leader的指令,肯定是要存储的,就是有了一个接口

1
2
3
type Applyer interface {
	Apply(cmd *CommandRequest) error
}

这个CommandRequest是什么呢?就是具体的指令,有什么内容我们可以先不考虑,定义成一个空的结构体

1
2
type CommandRequest struct {
}

结束

我们目前定义了后续开发所需要的各种接口,工具与原料都准备好了,就准备开始造起来吧!