Raft 结构建议

一个 Raft 实例需要应对外部事件的到达（Start() 调用、AppendEntries 与 RequestVote RPC、以及 RPC 回复），并执行周期性任务（选举与心跳）。组织 Raft 代码来管理这些活动的方式很多；本文档简述几种思路。

每个 Raft 实例有一批状态（log、current index 等），需要在并发 goroutine 产生的事件下更新。Go 文档指出，goroutine 可以直接用共享数据结构和锁来更新，也可以通过 channel 传递消息。经验表明，对 Raft 而言最直接的是使用共享数据和锁。

一个 Raft 实例有两种由时间驱动的活动：leader 必须发送心跳，其他节点在超过一定时间未收到 leader 消息后必须发起选举。最好用各自独立的长驻 goroutine 驱动这两种活动，而不是把多种活动塞进一个 goroutine。

选举超时的管理常是头疼来源。或许最简单的做法是在 Raft 结构体里维护一个变量，记录该节点上次收到 leader 消息的时间，并让选举超时 goroutine 定期检查自那时起是否已超过超时时间。用带小常数的 time.Sleep() 驱动定期检查最简单。不要用 time.Ticker 和 time.Timer；它们容易用错。

你需要一个独立的长驻 goroutine，按顺序在 applyCh 上发送已提交的日志条目。它必须独立，因为向 applyCh 发送可能阻塞；且必须是单个 goroutine，否则很难保证按日志顺序发送。推进 commitIndex 的代码需要唤醒 apply goroutine；用条件变量（Go 的 sync.Cond）通常最简单。

每个 RPC 最好在各自的 goroutine 中发送（并处理回复），原因有二：让不可达的节点不会拖慢收集多数回复；让心跳和选举定时器随时都能继续走。在同一 goroutine 里处理 RPC 回复最简单，而不是通过 channel 传递回复信息。

要记住网络会延迟 RPC 和 RPC 回复，而且在并发发送 RPC 时，网络可能重排请求和回复。Figure 2 在指出 RPC handler 需要小心的地方（例如应忽略旧 term 的 RPC）方面写得不错。Figure 2 对 RPC 回复处理的说明并不总是显式。Leader 在处理回复时必须小心：必须检查自发送 RPC 以来 term 是否已变，并必须考虑对同一 follower 的并发 RPC 的回复可能已改变 leader 的状态（例如 nextIndex）。
