mirror of https://github.com/status-im/consul.git
1888 lines
51 KiB
Go
1888 lines
51 KiB
Go
|
package raft
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"log"
|
||
|
"os"
|
||
|
"strconv"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/armon/go-metrics"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
minCheckInterval = 10 * time.Millisecond
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
keyCurrentTerm = []byte("CurrentTerm")
|
||
|
keyLastVoteTerm = []byte("LastVoteTerm")
|
||
|
keyLastVoteCand = []byte("LastVoteCand")
|
||
|
|
||
|
// ErrLeader is returned when an operation can't be completed on a
|
||
|
// leader node.
|
||
|
ErrLeader = errors.New("node is the leader")
|
||
|
|
||
|
// ErrNotLeader is returned when an operation can't be completed on a
|
||
|
// follower or candidate node.
|
||
|
ErrNotLeader = errors.New("node is not the leader")
|
||
|
|
||
|
// ErrLeadershipLost is returned when a leader fails to commit a log entry
|
||
|
// because it's been deposed in the process.
|
||
|
ErrLeadershipLost = errors.New("leadership lost while committing log")
|
||
|
|
||
|
// ErrRaftShutdown is returned when operations are requested against an
|
||
|
// inactive Raft.
|
||
|
ErrRaftShutdown = errors.New("raft is already shutdown")
|
||
|
|
||
|
// ErrEnqueueTimeout is returned when a command fails due to a timeout.
|
||
|
ErrEnqueueTimeout = errors.New("timed out enqueuing operation")
|
||
|
|
||
|
// ErrKnownPeer is returned when trying to add a peer to the configuration
|
||
|
// that already exists.
|
||
|
ErrKnownPeer = errors.New("peer already known")
|
||
|
|
||
|
// ErrUnknownPeer is returned when trying to remove a peer from the
|
||
|
// configuration that doesn't exist.
|
||
|
ErrUnknownPeer = errors.New("peer is unknown")
|
||
|
|
||
|
// ErrNothingNewToSnapshot is returned when trying to create a snapshot
|
||
|
// but there's nothing new commited to the FSM since we started.
|
||
|
ErrNothingNewToSnapshot = errors.New("Nothing new to snapshot")
|
||
|
)
|
||
|
|
||
|
// commitTuple is used to send an index that was committed,
|
||
|
// with an optional associated future that should be invoked.
|
||
|
type commitTuple struct {
|
||
|
log *Log
|
||
|
future *logFuture
|
||
|
}
|
||
|
|
||
|
// leaderState is state that is used while we are a leader.
|
||
|
type leaderState struct {
|
||
|
commitCh chan struct{}
|
||
|
inflight *inflight
|
||
|
replState map[string]*followerReplication
|
||
|
notify map[*verifyFuture]struct{}
|
||
|
stepDown chan struct{}
|
||
|
}
|
||
|
|
||
|
// Raft implements a Raft node.
|
||
|
type Raft struct {
|
||
|
raftState
|
||
|
|
||
|
// applyCh is used to async send logs to the main thread to
|
||
|
// be committed and applied to the FSM.
|
||
|
applyCh chan *logFuture
|
||
|
|
||
|
// Configuration provided at Raft initialization
|
||
|
conf *Config
|
||
|
|
||
|
// FSM is the client state machine to apply commands to
|
||
|
fsm FSM
|
||
|
|
||
|
// fsmCommitCh is used to trigger async application of logs to the fsm
|
||
|
fsmCommitCh chan commitTuple
|
||
|
|
||
|
// fsmRestoreCh is used to trigger a restore from snapshot
|
||
|
fsmRestoreCh chan *restoreFuture
|
||
|
|
||
|
// fsmSnapshotCh is used to trigger a new snapshot being taken
|
||
|
fsmSnapshotCh chan *reqSnapshotFuture
|
||
|
|
||
|
// lastContact is the last time we had contact from the
|
||
|
// leader node. This can be used to gauge staleness.
|
||
|
lastContact time.Time
|
||
|
lastContactLock sync.RWMutex
|
||
|
|
||
|
// Leader is the current cluster leader
|
||
|
leader string
|
||
|
leaderLock sync.RWMutex
|
||
|
|
||
|
// leaderCh is used to notify of leadership changes
|
||
|
leaderCh chan bool
|
||
|
|
||
|
// leaderState used only while state is leader
|
||
|
leaderState leaderState
|
||
|
|
||
|
// Stores our local addr
|
||
|
localAddr string
|
||
|
|
||
|
// Used for our logging
|
||
|
logger *log.Logger
|
||
|
|
||
|
// LogStore provides durable storage for logs
|
||
|
logs LogStore
|
||
|
|
||
|
// Track our known peers
|
||
|
peerCh chan *peerFuture
|
||
|
peers []string
|
||
|
peerStore PeerStore
|
||
|
|
||
|
// RPC chan comes from the transport layer
|
||
|
rpcCh <-chan RPC
|
||
|
|
||
|
// Shutdown channel to exit, protected to prevent concurrent exits
|
||
|
shutdown bool
|
||
|
shutdownCh chan struct{}
|
||
|
shutdownLock sync.Mutex
|
||
|
|
||
|
// snapshots is used to store and retrieve snapshots
|
||
|
snapshots SnapshotStore
|
||
|
|
||
|
// snapshotCh is used for user triggered snapshots
|
||
|
snapshotCh chan *snapshotFuture
|
||
|
|
||
|
// stable is a StableStore implementation for durable state
|
||
|
// It provides stable storage for many fields in raftState
|
||
|
stable StableStore
|
||
|
|
||
|
// The transport layer we use
|
||
|
trans Transport
|
||
|
|
||
|
// verifyCh is used to async send verify futures to the main thread
|
||
|
// to verify we are still the leader
|
||
|
verifyCh chan *verifyFuture
|
||
|
}
|
||
|
|
||
|
// NewRaft is used to construct a new Raft node. It takes a configuration, as well
|
||
|
// as implementations of various interfaces that are required. If we have any old state,
|
||
|
// such as snapshots, logs, peers, etc, all those will be restored when creating the
|
||
|
// Raft node.
|
||
|
func NewRaft(conf *Config, fsm FSM, logs LogStore, stable StableStore, snaps SnapshotStore,
|
||
|
peerStore PeerStore, trans Transport) (*Raft, error) {
|
||
|
// Validate the configuration
|
||
|
if err := ValidateConfig(conf); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Ensure we have a LogOutput
|
||
|
var logger *log.Logger
|
||
|
if conf.Logger != nil {
|
||
|
logger = conf.Logger
|
||
|
} else {
|
||
|
if conf.LogOutput == nil {
|
||
|
conf.LogOutput = os.Stderr
|
||
|
}
|
||
|
logger = log.New(conf.LogOutput, "", log.LstdFlags)
|
||
|
}
|
||
|
|
||
|
// Try to restore the current term
|
||
|
currentTerm, err := stable.GetUint64(keyCurrentTerm)
|
||
|
if err != nil && err.Error() != "not found" {
|
||
|
return nil, fmt.Errorf("failed to load current term: %v", err)
|
||
|
}
|
||
|
|
||
|
// Read the last log value
|
||
|
lastIdx, err := logs.LastIndex()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("failed to find last log: %v", err)
|
||
|
}
|
||
|
|
||
|
// Get the log
|
||
|
var lastLog Log
|
||
|
if lastIdx > 0 {
|
||
|
if err := logs.GetLog(lastIdx, &lastLog); err != nil {
|
||
|
return nil, fmt.Errorf("failed to get last log: %v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Construct the list of peers that excludes us
|
||
|
localAddr := trans.LocalAddr()
|
||
|
peers, err := peerStore.Peers()
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("failed to get list of peers: %v", err)
|
||
|
}
|
||
|
peers = ExcludePeer(peers, localAddr)
|
||
|
|
||
|
// Create Raft struct
|
||
|
r := &Raft{
|
||
|
applyCh: make(chan *logFuture),
|
||
|
conf: conf,
|
||
|
fsm: fsm,
|
||
|
fsmCommitCh: make(chan commitTuple, 128),
|
||
|
fsmRestoreCh: make(chan *restoreFuture),
|
||
|
fsmSnapshotCh: make(chan *reqSnapshotFuture),
|
||
|
leaderCh: make(chan bool),
|
||
|
localAddr: localAddr,
|
||
|
logger: logger,
|
||
|
logs: logs,
|
||
|
peerCh: make(chan *peerFuture),
|
||
|
peers: peers,
|
||
|
peerStore: peerStore,
|
||
|
rpcCh: trans.Consumer(),
|
||
|
snapshots: snaps,
|
||
|
snapshotCh: make(chan *snapshotFuture),
|
||
|
shutdownCh: make(chan struct{}),
|
||
|
stable: stable,
|
||
|
trans: trans,
|
||
|
verifyCh: make(chan *verifyFuture, 64),
|
||
|
}
|
||
|
|
||
|
// Initialize as a follower
|
||
|
r.setState(Follower)
|
||
|
|
||
|
// Start as leader if specified. This should only be used
|
||
|
// for testing purposes.
|
||
|
if conf.StartAsLeader {
|
||
|
r.setState(Leader)
|
||
|
r.setLeader(r.localAddr)
|
||
|
}
|
||
|
|
||
|
// Restore the current term and the last log
|
||
|
r.setCurrentTerm(currentTerm)
|
||
|
r.setLastLogIndex(lastLog.Index)
|
||
|
r.setLastLogTerm(lastLog.Term)
|
||
|
|
||
|
// Attempt to restore a snapshot if there are any
|
||
|
if err := r.restoreSnapshot(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Setup a heartbeat fast-path to avoid head-of-line
|
||
|
// blocking where possible. It MUST be safe for this
|
||
|
// to be called concurrently with a blocking RPC.
|
||
|
trans.SetHeartbeatHandler(r.processHeartbeat)
|
||
|
|
||
|
// Start the background work
|
||
|
r.goFunc(r.run)
|
||
|
r.goFunc(r.runFSM)
|
||
|
r.goFunc(r.runSnapshots)
|
||
|
return r, nil
|
||
|
}
|
||
|
|
||
|
// Leader is used to return the current leader of the cluster.
|
||
|
// It may return empty string if there is no current leader
|
||
|
// or the leader is unknown.
|
||
|
func (r *Raft) Leader() string {
|
||
|
r.leaderLock.RLock()
|
||
|
leader := r.leader
|
||
|
r.leaderLock.RUnlock()
|
||
|
return leader
|
||
|
}
|
||
|
|
||
|
// setLeader is used to modify the current leader of the cluster
|
||
|
func (r *Raft) setLeader(leader string) {
|
||
|
r.leaderLock.Lock()
|
||
|
r.leader = leader
|
||
|
r.leaderLock.Unlock()
|
||
|
}
|
||
|
|
||
|
// Apply is used to apply a command to the FSM in a highly consistent
|
||
|
// manner. This returns a future that can be used to wait on the application.
|
||
|
// An optional timeout can be provided to limit the amount of time we wait
|
||
|
// for the command to be started. This must be run on the leader or it
|
||
|
// will fail.
|
||
|
func (r *Raft) Apply(cmd []byte, timeout time.Duration) ApplyFuture {
|
||
|
metrics.IncrCounter([]string{"raft", "apply"}, 1)
|
||
|
var timer <-chan time.Time
|
||
|
if timeout > 0 {
|
||
|
timer = time.After(timeout)
|
||
|
}
|
||
|
|
||
|
// Create a log future, no index or term yet
|
||
|
logFuture := &logFuture{
|
||
|
log: Log{
|
||
|
Type: LogCommand,
|
||
|
Data: cmd,
|
||
|
},
|
||
|
}
|
||
|
logFuture.init()
|
||
|
|
||
|
select {
|
||
|
case <-timer:
|
||
|
return errorFuture{ErrEnqueueTimeout}
|
||
|
case <-r.shutdownCh:
|
||
|
return errorFuture{ErrRaftShutdown}
|
||
|
case r.applyCh <- logFuture:
|
||
|
return logFuture
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Barrier is used to issue a command that blocks until all preceeding
|
||
|
// operations have been applied to the FSM. It can be used to ensure the
|
||
|
// FSM reflects all queued writes. An optional timeout can be provided to
|
||
|
// limit the amount of time we wait for the command to be started. This
|
||
|
// must be run on the leader or it will fail.
|
||
|
func (r *Raft) Barrier(timeout time.Duration) Future {
|
||
|
metrics.IncrCounter([]string{"raft", "barrier"}, 1)
|
||
|
var timer <-chan time.Time
|
||
|
if timeout > 0 {
|
||
|
timer = time.After(timeout)
|
||
|
}
|
||
|
|
||
|
// Create a log future, no index or term yet
|
||
|
logFuture := &logFuture{
|
||
|
log: Log{
|
||
|
Type: LogBarrier,
|
||
|
},
|
||
|
}
|
||
|
logFuture.init()
|
||
|
|
||
|
select {
|
||
|
case <-timer:
|
||
|
return errorFuture{ErrEnqueueTimeout}
|
||
|
case <-r.shutdownCh:
|
||
|
return errorFuture{ErrRaftShutdown}
|
||
|
case r.applyCh <- logFuture:
|
||
|
return logFuture
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// VerifyLeader is used to ensure the current node is still
|
||
|
// the leader. This can be done to prevent stale reads when a
|
||
|
// new leader has potentially been elected.
|
||
|
func (r *Raft) VerifyLeader() Future {
|
||
|
metrics.IncrCounter([]string{"raft", "verify_leader"}, 1)
|
||
|
verifyFuture := &verifyFuture{}
|
||
|
verifyFuture.init()
|
||
|
select {
|
||
|
case <-r.shutdownCh:
|
||
|
return errorFuture{ErrRaftShutdown}
|
||
|
case r.verifyCh <- verifyFuture:
|
||
|
return verifyFuture
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AddPeer is used to add a new peer into the cluster. This must be
|
||
|
// run on the leader or it will fail.
|
||
|
func (r *Raft) AddPeer(peer string) Future {
|
||
|
logFuture := &logFuture{
|
||
|
log: Log{
|
||
|
Type: LogAddPeer,
|
||
|
peer: peer,
|
||
|
},
|
||
|
}
|
||
|
logFuture.init()
|
||
|
select {
|
||
|
case r.applyCh <- logFuture:
|
||
|
return logFuture
|
||
|
case <-r.shutdownCh:
|
||
|
return errorFuture{ErrRaftShutdown}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// RemovePeer is used to remove a peer from the cluster. If the
|
||
|
// current leader is being removed, it will cause a new election
|
||
|
// to occur. This must be run on the leader or it will fail.
|
||
|
func (r *Raft) RemovePeer(peer string) Future {
|
||
|
logFuture := &logFuture{
|
||
|
log: Log{
|
||
|
Type: LogRemovePeer,
|
||
|
peer: peer,
|
||
|
},
|
||
|
}
|
||
|
logFuture.init()
|
||
|
select {
|
||
|
case r.applyCh <- logFuture:
|
||
|
return logFuture
|
||
|
case <-r.shutdownCh:
|
||
|
return errorFuture{ErrRaftShutdown}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SetPeers is used to forcibly replace the set of internal peers and
|
||
|
// the peerstore with the ones specified. This can be considered unsafe.
|
||
|
func (r *Raft) SetPeers(p []string) Future {
|
||
|
peerFuture := &peerFuture{
|
||
|
peers: p,
|
||
|
}
|
||
|
peerFuture.init()
|
||
|
|
||
|
select {
|
||
|
case r.peerCh <- peerFuture:
|
||
|
return peerFuture
|
||
|
case <-r.shutdownCh:
|
||
|
return errorFuture{ErrRaftShutdown}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Shutdown is used to stop the Raft background routines.
|
||
|
// This is not a graceful operation. Provides a future that
|
||
|
// can be used to block until all background routines have exited.
|
||
|
func (r *Raft) Shutdown() Future {
|
||
|
r.shutdownLock.Lock()
|
||
|
defer r.shutdownLock.Unlock()
|
||
|
|
||
|
if !r.shutdown {
|
||
|
close(r.shutdownCh)
|
||
|
r.shutdown = true
|
||
|
r.setState(Shutdown)
|
||
|
}
|
||
|
|
||
|
return &shutdownFuture{r}
|
||
|
}
|
||
|
|
||
|
// Snapshot is used to manually force Raft to take a snapshot.
|
||
|
// Returns a future that can be used to block until complete.
|
||
|
func (r *Raft) Snapshot() Future {
|
||
|
snapFuture := &snapshotFuture{}
|
||
|
snapFuture.init()
|
||
|
select {
|
||
|
case r.snapshotCh <- snapFuture:
|
||
|
return snapFuture
|
||
|
case <-r.shutdownCh:
|
||
|
return errorFuture{ErrRaftShutdown}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// State is used to return the current raft state.
|
||
|
func (r *Raft) State() RaftState {
|
||
|
return r.getState()
|
||
|
}
|
||
|
|
||
|
// LeaderCh is used to get a channel which delivers signals on
|
||
|
// acquiring or losing leadership. It sends true if we become
|
||
|
// the leader, and false if we lose it. The channel is not buffered,
|
||
|
// and does not block on writes.
|
||
|
func (r *Raft) LeaderCh() <-chan bool {
|
||
|
return r.leaderCh
|
||
|
}
|
||
|
|
||
|
func (r *Raft) String() string {
|
||
|
return fmt.Sprintf("Node at %s [%v]", r.localAddr, r.getState())
|
||
|
}
|
||
|
|
||
|
// LastContact returns the time of last contact by a leader.
|
||
|
// This only makes sense if we are currently a follower.
|
||
|
func (r *Raft) LastContact() time.Time {
|
||
|
r.lastContactLock.RLock()
|
||
|
last := r.lastContact
|
||
|
r.lastContactLock.RUnlock()
|
||
|
return last
|
||
|
}
|
||
|
|
||
|
// Stats is used to return a map of various internal stats. This should only
|
||
|
// be used for informative purposes or debugging.
|
||
|
func (r *Raft) Stats() map[string]string {
|
||
|
toString := func(v uint64) string {
|
||
|
return strconv.FormatUint(v, 10)
|
||
|
}
|
||
|
s := map[string]string{
|
||
|
"state": r.getState().String(),
|
||
|
"term": toString(r.getCurrentTerm()),
|
||
|
"last_log_index": toString(r.getLastLogIndex()),
|
||
|
"last_log_term": toString(r.getLastLogTerm()),
|
||
|
"commit_index": toString(r.getCommitIndex()),
|
||
|
"applied_index": toString(r.getLastApplied()),
|
||
|
"fsm_pending": toString(uint64(len(r.fsmCommitCh))),
|
||
|
"last_snapshot_index": toString(r.getLastSnapshotIndex()),
|
||
|
"last_snapshot_term": toString(r.getLastSnapshotTerm()),
|
||
|
"num_peers": toString(uint64(len(r.peers))),
|
||
|
}
|
||
|
last := r.LastContact()
|
||
|
if last.IsZero() {
|
||
|
s["last_contact"] = "never"
|
||
|
} else if r.getState() == Leader {
|
||
|
s["last_contact"] = "0"
|
||
|
} else {
|
||
|
s["last_contact"] = fmt.Sprintf("%v", time.Now().Sub(last))
|
||
|
}
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
// LastIndex returns the last index in stable storage,
|
||
|
// either from the last log or from the last snapshot.
|
||
|
func (r *Raft) LastIndex() uint64 {
|
||
|
return r.getLastIndex()
|
||
|
}
|
||
|
|
||
|
// AppliedIndex returns the last index applied to the FSM.
|
||
|
// This is generally lagging behind the last index, especially
|
||
|
// for indexes that are persisted but have not yet been considered
|
||
|
// committed by the leader.
|
||
|
func (r *Raft) AppliedIndex() uint64 {
|
||
|
return r.getLastApplied()
|
||
|
}
|
||
|
|
||
|
// runFSM is a long running goroutine responsible for applying logs
|
||
|
// to the FSM. This is done async of other logs since we don't want
|
||
|
// the FSM to block our internal operations.
|
||
|
func (r *Raft) runFSM() {
|
||
|
var lastIndex, lastTerm uint64
|
||
|
for {
|
||
|
select {
|
||
|
case req := <-r.fsmRestoreCh:
|
||
|
// Open the snapshot
|
||
|
meta, source, err := r.snapshots.Open(req.ID)
|
||
|
if err != nil {
|
||
|
req.respond(fmt.Errorf("failed to open snapshot %v: %v", req.ID, err))
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Attempt to restore
|
||
|
start := time.Now()
|
||
|
if err := r.fsm.Restore(source); err != nil {
|
||
|
req.respond(fmt.Errorf("failed to restore snapshot %v: %v", req.ID, err))
|
||
|
source.Close()
|
||
|
continue
|
||
|
}
|
||
|
source.Close()
|
||
|
metrics.MeasureSince([]string{"raft", "fsm", "restore"}, start)
|
||
|
|
||
|
// Update the last index and term
|
||
|
lastIndex = meta.Index
|
||
|
lastTerm = meta.Term
|
||
|
req.respond(nil)
|
||
|
|
||
|
case req := <-r.fsmSnapshotCh:
|
||
|
// Is there something to snapshot?
|
||
|
if lastIndex == 0 {
|
||
|
req.respond(ErrNothingNewToSnapshot)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Get our peers
|
||
|
peers, err := r.peerStore.Peers()
|
||
|
if err != nil {
|
||
|
req.respond(err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Start a snapshot
|
||
|
start := time.Now()
|
||
|
snap, err := r.fsm.Snapshot()
|
||
|
metrics.MeasureSince([]string{"raft", "fsm", "snapshot"}, start)
|
||
|
|
||
|
// Respond to the request
|
||
|
req.index = lastIndex
|
||
|
req.term = lastTerm
|
||
|
req.peers = peers
|
||
|
req.snapshot = snap
|
||
|
req.respond(err)
|
||
|
|
||
|
case commitTuple := <-r.fsmCommitCh:
|
||
|
// Apply the log if a command
|
||
|
var resp interface{}
|
||
|
if commitTuple.log.Type == LogCommand {
|
||
|
start := time.Now()
|
||
|
resp = r.fsm.Apply(commitTuple.log)
|
||
|
metrics.MeasureSince([]string{"raft", "fsm", "apply"}, start)
|
||
|
}
|
||
|
|
||
|
// Update the indexes
|
||
|
lastIndex = commitTuple.log.Index
|
||
|
lastTerm = commitTuple.log.Term
|
||
|
|
||
|
// Invoke the future if given
|
||
|
if commitTuple.future != nil {
|
||
|
commitTuple.future.response = resp
|
||
|
commitTuple.future.respond(nil)
|
||
|
}
|
||
|
case <-r.shutdownCh:
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// run is a long running goroutine that runs the Raft FSM.
|
||
|
func (r *Raft) run() {
|
||
|
for {
|
||
|
// Check if we are doing a shutdown
|
||
|
select {
|
||
|
case <-r.shutdownCh:
|
||
|
// Clear the leader to prevent forwarding
|
||
|
r.setLeader("")
|
||
|
return
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
// Enter into a sub-FSM
|
||
|
switch r.getState() {
|
||
|
case Follower:
|
||
|
r.runFollower()
|
||
|
case Candidate:
|
||
|
r.runCandidate()
|
||
|
case Leader:
|
||
|
r.runLeader()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// runFollower runs the FSM for a follower.
|
||
|
func (r *Raft) runFollower() {
|
||
|
didWarn := false
|
||
|
r.logger.Printf("[INFO] raft: %v entering Follower state", r)
|
||
|
metrics.IncrCounter([]string{"raft", "state", "follower"}, 1)
|
||
|
heartbeatTimer := randomTimeout(r.conf.HeartbeatTimeout)
|
||
|
for {
|
||
|
select {
|
||
|
case rpc := <-r.rpcCh:
|
||
|
r.processRPC(rpc)
|
||
|
|
||
|
case a := <-r.applyCh:
|
||
|
// Reject any operations since we are not the leader
|
||
|
a.respond(ErrNotLeader)
|
||
|
|
||
|
case v := <-r.verifyCh:
|
||
|
// Reject any operations since we are not the leader
|
||
|
v.respond(ErrNotLeader)
|
||
|
|
||
|
case p := <-r.peerCh:
|
||
|
// Set the peers
|
||
|
r.peers = ExcludePeer(p.peers, r.localAddr)
|
||
|
p.respond(r.peerStore.SetPeers(p.peers))
|
||
|
|
||
|
case <-heartbeatTimer:
|
||
|
// Restart the heartbeat timer
|
||
|
heartbeatTimer = randomTimeout(r.conf.HeartbeatTimeout)
|
||
|
|
||
|
// Check if we have had a successful contact
|
||
|
lastContact := r.LastContact()
|
||
|
if time.Now().Sub(lastContact) < r.conf.HeartbeatTimeout {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Heartbeat failed! Transition to the candidate state
|
||
|
r.setLeader("")
|
||
|
if len(r.peers) == 0 && !r.conf.EnableSingleNode {
|
||
|
if !didWarn {
|
||
|
r.logger.Printf("[WARN] raft: EnableSingleNode disabled, and no known peers. Aborting election.")
|
||
|
didWarn = true
|
||
|
}
|
||
|
} else {
|
||
|
r.logger.Printf("[WARN] raft: Heartbeat timeout reached, starting election")
|
||
|
|
||
|
metrics.IncrCounter([]string{"raft", "transition", "heartbeat_timout"}, 1)
|
||
|
r.setState(Candidate)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
case <-r.shutdownCh:
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// runCandidate runs the FSM for a candidate.
|
||
|
func (r *Raft) runCandidate() {
|
||
|
r.logger.Printf("[INFO] raft: %v entering Candidate state", r)
|
||
|
metrics.IncrCounter([]string{"raft", "state", "candidate"}, 1)
|
||
|
|
||
|
// Start vote for us, and set a timeout
|
||
|
voteCh := r.electSelf()
|
||
|
electionTimer := randomTimeout(r.conf.ElectionTimeout)
|
||
|
|
||
|
// Tally the votes, need a simple majority
|
||
|
grantedVotes := 0
|
||
|
votesNeeded := r.quorumSize()
|
||
|
r.logger.Printf("[DEBUG] raft: Votes needed: %d", votesNeeded)
|
||
|
|
||
|
for r.getState() == Candidate {
|
||
|
select {
|
||
|
case rpc := <-r.rpcCh:
|
||
|
r.processRPC(rpc)
|
||
|
|
||
|
case vote := <-voteCh:
|
||
|
// Check if the term is greater than ours, bail
|
||
|
if vote.Term > r.getCurrentTerm() {
|
||
|
r.logger.Printf("[DEBUG] raft: Newer term discovered, fallback to follower")
|
||
|
r.setState(Follower)
|
||
|
r.setCurrentTerm(vote.Term)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Check if the vote is granted
|
||
|
if vote.Granted {
|
||
|
grantedVotes++
|
||
|
r.logger.Printf("[DEBUG] raft: Vote granted from %s. Tally: %d", vote.voter, grantedVotes)
|
||
|
}
|
||
|
|
||
|
// Check if we've become the leader
|
||
|
if grantedVotes >= votesNeeded {
|
||
|
r.logger.Printf("[INFO] raft: Election won. Tally: %d", grantedVotes)
|
||
|
r.setState(Leader)
|
||
|
r.setLeader(r.localAddr)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
case a := <-r.applyCh:
|
||
|
// Reject any operations since we are not the leader
|
||
|
a.respond(ErrNotLeader)
|
||
|
|
||
|
case v := <-r.verifyCh:
|
||
|
// Reject any operations since we are not the leader
|
||
|
v.respond(ErrNotLeader)
|
||
|
|
||
|
case p := <-r.peerCh:
|
||
|
// Set the peers
|
||
|
r.peers = ExcludePeer(p.peers, r.localAddr)
|
||
|
p.respond(r.peerStore.SetPeers(p.peers))
|
||
|
// Become a follower again
|
||
|
r.setState(Follower)
|
||
|
return
|
||
|
|
||
|
case <-electionTimer:
|
||
|
// Election failed! Restart the election. We simply return,
|
||
|
// which will kick us back into runCandidate
|
||
|
r.logger.Printf("[WARN] raft: Election timeout reached, restarting election")
|
||
|
return
|
||
|
|
||
|
case <-r.shutdownCh:
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// runLeader runs the FSM for a leader. Do the setup here and drop into
|
||
|
// the leaderLoop for the hot loop.
|
||
|
func (r *Raft) runLeader() {
|
||
|
r.logger.Printf("[INFO] raft: %v entering Leader state", r)
|
||
|
metrics.IncrCounter([]string{"raft", "state", "leader"}, 1)
|
||
|
|
||
|
// Notify that we are the leader
|
||
|
asyncNotifyBool(r.leaderCh, true)
|
||
|
|
||
|
// Push to the notify channel if given
|
||
|
if notify := r.conf.NotifyCh; notify != nil {
|
||
|
select {
|
||
|
case notify <- true:
|
||
|
case <-r.shutdownCh:
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Setup leader state
|
||
|
r.leaderState.commitCh = make(chan struct{}, 1)
|
||
|
r.leaderState.inflight = newInflight(r.leaderState.commitCh)
|
||
|
r.leaderState.replState = make(map[string]*followerReplication)
|
||
|
r.leaderState.notify = make(map[*verifyFuture]struct{})
|
||
|
r.leaderState.stepDown = make(chan struct{}, 1)
|
||
|
|
||
|
// Cleanup state on step down
|
||
|
defer func() {
|
||
|
// Since we were the leader previously, we update our
|
||
|
// last contact time when we step down, so that we are not
|
||
|
// reporting a last contact time from before we were the
|
||
|
// leader. Otherwise, to a client it would seem our data
|
||
|
// is extremely stale.
|
||
|
r.setLastContact()
|
||
|
|
||
|
// Stop replication
|
||
|
for _, p := range r.leaderState.replState {
|
||
|
close(p.stopCh)
|
||
|
}
|
||
|
|
||
|
// Cancel inflight requests
|
||
|
r.leaderState.inflight.Cancel(ErrLeadershipLost)
|
||
|
|
||
|
// Respond to any pending verify requests
|
||
|
for future := range r.leaderState.notify {
|
||
|
future.respond(ErrLeadershipLost)
|
||
|
}
|
||
|
|
||
|
// Clear all the state
|
||
|
r.leaderState.commitCh = nil
|
||
|
r.leaderState.inflight = nil
|
||
|
r.leaderState.replState = nil
|
||
|
r.leaderState.notify = nil
|
||
|
r.leaderState.stepDown = nil
|
||
|
|
||
|
// If we are stepping down for some reason, no known leader.
|
||
|
// We may have stepped down due to an RPC call, which would
|
||
|
// provide the leader, so we cannot always blank this out.
|
||
|
r.leaderLock.Lock()
|
||
|
if r.leader == r.localAddr {
|
||
|
r.leader = ""
|
||
|
}
|
||
|
r.leaderLock.Unlock()
|
||
|
|
||
|
// Notify that we are not the leader
|
||
|
asyncNotifyBool(r.leaderCh, false)
|
||
|
|
||
|
// Push to the notify channel if given
|
||
|
if notify := r.conf.NotifyCh; notify != nil {
|
||
|
select {
|
||
|
case notify <- false:
|
||
|
case <-r.shutdownCh:
|
||
|
// On shutdown, make a best effort but do not block
|
||
|
select {
|
||
|
case notify <- false:
|
||
|
default:
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
// Start a replication routine for each peer
|
||
|
for _, peer := range r.peers {
|
||
|
r.startReplication(peer)
|
||
|
}
|
||
|
|
||
|
// Dispatch a no-op log first. Instead of LogNoop,
|
||
|
// we use a LogAddPeer with our peerset. This acts like
|
||
|
// a no-op as well, but when doing an initial bootstrap, ensures
|
||
|
// that all nodes share a common peerset.
|
||
|
peerSet := append([]string{r.localAddr}, r.peers...)
|
||
|
noop := &logFuture{
|
||
|
log: Log{
|
||
|
Type: LogAddPeer,
|
||
|
Data: encodePeers(peerSet, r.trans),
|
||
|
},
|
||
|
}
|
||
|
r.dispatchLogs([]*logFuture{noop})
|
||
|
|
||
|
// Disable EnableSingleNode after we've been elected leader.
|
||
|
// This is to prevent a split brain in the future, if we are removed
|
||
|
// from the cluster and then elect ourself as leader.
|
||
|
if r.conf.DisableBootstrapAfterElect && r.conf.EnableSingleNode {
|
||
|
r.logger.Printf("[INFO] raft: Disabling EnableSingleNode (bootstrap)")
|
||
|
r.conf.EnableSingleNode = false
|
||
|
}
|
||
|
|
||
|
// Sit in the leader loop until we step down
|
||
|
r.leaderLoop()
|
||
|
}
|
||
|
|
||
|
// startReplication is a helper to setup state and start async replication to a peer.
|
||
|
func (r *Raft) startReplication(peer string) {
|
||
|
lastIdx := r.getLastIndex()
|
||
|
s := &followerReplication{
|
||
|
peer: peer,
|
||
|
inflight: r.leaderState.inflight,
|
||
|
stopCh: make(chan uint64, 1),
|
||
|
triggerCh: make(chan struct{}, 1),
|
||
|
currentTerm: r.getCurrentTerm(),
|
||
|
matchIndex: 0,
|
||
|
nextIndex: lastIdx + 1,
|
||
|
lastContact: time.Now(),
|
||
|
notifyCh: make(chan struct{}, 1),
|
||
|
stepDown: r.leaderState.stepDown,
|
||
|
}
|
||
|
r.leaderState.replState[peer] = s
|
||
|
r.goFunc(func() { r.replicate(s) })
|
||
|
asyncNotifyCh(s.triggerCh)
|
||
|
}
|
||
|
|
||
|
// leaderLoop is the hot loop for a leader. It is invoked
|
||
|
// after all the various leader setup is done.
|
||
|
func (r *Raft) leaderLoop() {
|
||
|
// stepDown is used to track if there is an inflight log that
|
||
|
// would cause us to lose leadership (specifically a RemovePeer of
|
||
|
// ourselves). If this is the case, we must not allow any logs to
|
||
|
// be processed in parallel, otherwise we are basing commit on
|
||
|
// only a single peer (ourself) and replicating to an undefined set
|
||
|
// of peers.
|
||
|
stepDown := false
|
||
|
|
||
|
lease := time.After(r.conf.LeaderLeaseTimeout)
|
||
|
for r.getState() == Leader {
|
||
|
select {
|
||
|
case rpc := <-r.rpcCh:
|
||
|
r.processRPC(rpc)
|
||
|
|
||
|
case <-r.leaderState.stepDown:
|
||
|
r.setState(Follower)
|
||
|
|
||
|
case <-r.leaderState.commitCh:
|
||
|
// Get the committed messages
|
||
|
committed := r.leaderState.inflight.Committed()
|
||
|
for e := committed.Front(); e != nil; e = e.Next() {
|
||
|
// Measure the commit time
|
||
|
commitLog := e.Value.(*logFuture)
|
||
|
metrics.MeasureSince([]string{"raft", "commitTime"}, commitLog.dispatch)
|
||
|
|
||
|
// Increment the commit index
|
||
|
idx := commitLog.log.Index
|
||
|
r.setCommitIndex(idx)
|
||
|
r.processLogs(idx, commitLog)
|
||
|
}
|
||
|
|
||
|
case v := <-r.verifyCh:
|
||
|
if v.quorumSize == 0 {
|
||
|
// Just dispatched, start the verification
|
||
|
r.verifyLeader(v)
|
||
|
|
||
|
} else if v.votes < v.quorumSize {
|
||
|
// Early return, means there must be a new leader
|
||
|
r.logger.Printf("[WARN] raft: New leader elected, stepping down")
|
||
|
r.setState(Follower)
|
||
|
delete(r.leaderState.notify, v)
|
||
|
v.respond(ErrNotLeader)
|
||
|
|
||
|
} else {
|
||
|
// Quorum of members agree, we are still leader
|
||
|
delete(r.leaderState.notify, v)
|
||
|
v.respond(nil)
|
||
|
}
|
||
|
|
||
|
case p := <-r.peerCh:
|
||
|
p.respond(ErrLeader)
|
||
|
|
||
|
case newLog := <-r.applyCh:
|
||
|
// Group commit, gather all the ready commits
|
||
|
ready := []*logFuture{newLog}
|
||
|
for i := 0; i < r.conf.MaxAppendEntries; i++ {
|
||
|
select {
|
||
|
case newLog := <-r.applyCh:
|
||
|
ready = append(ready, newLog)
|
||
|
default:
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Handle any peer set changes
|
||
|
n := len(ready)
|
||
|
for i := 0; i < n; i++ {
|
||
|
// Fail all future transactions once stepDown is on
|
||
|
if stepDown {
|
||
|
ready[i].respond(ErrNotLeader)
|
||
|
ready[i], ready[n-1] = ready[n-1], nil
|
||
|
n--
|
||
|
i--
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Special case AddPeer and RemovePeer
|
||
|
log := ready[i]
|
||
|
if log.log.Type != LogAddPeer && log.log.Type != LogRemovePeer {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Check if this log should be ignored. The logs can be
|
||
|
// reordered here since we have not yet assigned an index
|
||
|
// and are not violating any promises.
|
||
|
if !r.preparePeerChange(log) {
|
||
|
ready[i], ready[n-1] = ready[n-1], nil
|
||
|
n--
|
||
|
i--
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Apply peer set changes early and check if we will step
|
||
|
// down after the commit of this log. If so, we must not
|
||
|
// allow any future entries to make progress to avoid undefined
|
||
|
// behavior.
|
||
|
if ok := r.processLog(&log.log, nil, true); ok {
|
||
|
stepDown = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Nothing to do if all logs are invalid
|
||
|
if n == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Dispatch the logs
|
||
|
ready = ready[:n]
|
||
|
r.dispatchLogs(ready)
|
||
|
|
||
|
case <-lease:
|
||
|
// Check if we've exceeded the lease, potentially stepping down
|
||
|
maxDiff := r.checkLeaderLease()
|
||
|
|
||
|
// Next check interval should adjust for the last node we've
|
||
|
// contacted, without going negative
|
||
|
checkInterval := r.conf.LeaderLeaseTimeout - maxDiff
|
||
|
if checkInterval < minCheckInterval {
|
||
|
checkInterval = minCheckInterval
|
||
|
}
|
||
|
|
||
|
// Renew the lease timer
|
||
|
lease = time.After(checkInterval)
|
||
|
|
||
|
case <-r.shutdownCh:
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// verifyLeader must be called from the main thread for safety.
|
||
|
// Causes the followers to attempt an immediate heartbeat.
|
||
|
func (r *Raft) verifyLeader(v *verifyFuture) {
|
||
|
// Current leader always votes for self
|
||
|
v.votes = 1
|
||
|
|
||
|
// Set the quorum size, hot-path for single node
|
||
|
v.quorumSize = r.quorumSize()
|
||
|
if v.quorumSize == 1 {
|
||
|
v.respond(nil)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Track this request
|
||
|
v.notifyCh = r.verifyCh
|
||
|
r.leaderState.notify[v] = struct{}{}
|
||
|
|
||
|
// Trigger immediate heartbeats
|
||
|
for _, repl := range r.leaderState.replState {
|
||
|
repl.notifyLock.Lock()
|
||
|
repl.notify = append(repl.notify, v)
|
||
|
repl.notifyLock.Unlock()
|
||
|
asyncNotifyCh(repl.notifyCh)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// checkLeaderLease is used to check if we can contact a quorum of nodes
|
||
|
// within the last leader lease interval. If not, we need to step down,
|
||
|
// as we may have lost connectivity. Returns the maximum duration without
|
||
|
// contact.
|
||
|
func (r *Raft) checkLeaderLease() time.Duration {
|
||
|
// Track contacted nodes, we can always contact ourself
|
||
|
contacted := 1
|
||
|
|
||
|
// Check each follower
|
||
|
var maxDiff time.Duration
|
||
|
now := time.Now()
|
||
|
for peer, f := range r.leaderState.replState {
|
||
|
diff := now.Sub(f.LastContact())
|
||
|
if diff <= r.conf.LeaderLeaseTimeout {
|
||
|
contacted++
|
||
|
if diff > maxDiff {
|
||
|
maxDiff = diff
|
||
|
}
|
||
|
} else {
|
||
|
// Log at least once at high value, then debug. Otherwise it gets very verbose.
|
||
|
if diff <= 3*r.conf.LeaderLeaseTimeout {
|
||
|
r.logger.Printf("[WARN] raft: Failed to contact %v in %v", peer, diff)
|
||
|
} else {
|
||
|
r.logger.Printf("[DEBUG] raft: Failed to contact %v in %v", peer, diff)
|
||
|
}
|
||
|
}
|
||
|
metrics.AddSample([]string{"raft", "leader", "lastContact"}, float32(diff/time.Millisecond))
|
||
|
}
|
||
|
|
||
|
// Verify we can contact a quorum
|
||
|
quorum := r.quorumSize()
|
||
|
if contacted < quorum {
|
||
|
r.logger.Printf("[WARN] raft: Failed to contact quorum of nodes, stepping down")
|
||
|
r.setState(Follower)
|
||
|
metrics.IncrCounter([]string{"raft", "transition", "leader_lease_timeout"}, 1)
|
||
|
}
|
||
|
return maxDiff
|
||
|
}
|
||
|
|
||
|
// quorumSize is used to return the quorum size
|
||
|
func (r *Raft) quorumSize() int {
|
||
|
return ((len(r.peers) + 1) / 2) + 1
|
||
|
}
|
||
|
|
||
|
// preparePeerChange checks if a LogAddPeer or LogRemovePeer should be performed,
|
||
|
// and properly formats the data field on the log before dispatching it.
|
||
|
func (r *Raft) preparePeerChange(l *logFuture) bool {
|
||
|
// Check if this is a known peer
|
||
|
p := l.log.peer
|
||
|
knownPeer := PeerContained(r.peers, p) || r.localAddr == p
|
||
|
|
||
|
// Ignore known peers on add
|
||
|
if l.log.Type == LogAddPeer && knownPeer {
|
||
|
l.respond(ErrKnownPeer)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Ignore unknown peers on remove
|
||
|
if l.log.Type == LogRemovePeer && !knownPeer {
|
||
|
l.respond(ErrUnknownPeer)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Construct the peer set
|
||
|
var peerSet []string
|
||
|
if l.log.Type == LogAddPeer {
|
||
|
peerSet = append([]string{p, r.localAddr}, r.peers...)
|
||
|
} else {
|
||
|
peerSet = ExcludePeer(append([]string{r.localAddr}, r.peers...), p)
|
||
|
}
|
||
|
|
||
|
// Setup the log
|
||
|
l.log.Data = encodePeers(peerSet, r.trans)
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
// dispatchLog is called to push a log to disk, mark it
|
||
|
// as inflight and begin replication of it.
|
||
|
func (r *Raft) dispatchLogs(applyLogs []*logFuture) {
|
||
|
now := time.Now()
|
||
|
defer metrics.MeasureSince([]string{"raft", "leader", "dispatchLog"}, now)
|
||
|
|
||
|
term := r.getCurrentTerm()
|
||
|
lastIndex := r.getLastIndex()
|
||
|
logs := make([]*Log, len(applyLogs))
|
||
|
|
||
|
for idx, applyLog := range applyLogs {
|
||
|
applyLog.dispatch = now
|
||
|
applyLog.log.Index = lastIndex + uint64(idx) + 1
|
||
|
applyLog.log.Term = term
|
||
|
applyLog.policy = newMajorityQuorum(len(r.peers) + 1)
|
||
|
logs[idx] = &applyLog.log
|
||
|
}
|
||
|
|
||
|
// Write the log entry locally
|
||
|
if err := r.logs.StoreLogs(logs); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to commit logs: %v", err)
|
||
|
for _, applyLog := range applyLogs {
|
||
|
applyLog.respond(err)
|
||
|
}
|
||
|
r.setState(Follower)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Add this to the inflight logs, commit
|
||
|
r.leaderState.inflight.StartAll(applyLogs)
|
||
|
|
||
|
// Update the last log since it's on disk now
|
||
|
r.setLastLogIndex(lastIndex + uint64(len(applyLogs)))
|
||
|
r.setLastLogTerm(term)
|
||
|
|
||
|
// Notify the replicators of the new log
|
||
|
for _, f := range r.leaderState.replState {
|
||
|
asyncNotifyCh(f.triggerCh)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// processLogs is used to process all the logs from the lastApplied
|
||
|
// up to the given index.
|
||
|
func (r *Raft) processLogs(index uint64, future *logFuture) {
|
||
|
// Reject logs we've applied already
|
||
|
lastApplied := r.getLastApplied()
|
||
|
if index <= lastApplied {
|
||
|
r.logger.Printf("[WARN] raft: Skipping application of old log: %d", index)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Apply all the preceding logs
|
||
|
for idx := r.getLastApplied() + 1; idx <= index; idx++ {
|
||
|
// Get the log, either from the future or from our log store
|
||
|
if future != nil && future.log.Index == idx {
|
||
|
r.processLog(&future.log, future, false)
|
||
|
|
||
|
} else {
|
||
|
l := new(Log)
|
||
|
if err := r.logs.GetLog(idx, l); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to get log at %d: %v", idx, err)
|
||
|
panic(err)
|
||
|
}
|
||
|
r.processLog(l, nil, false)
|
||
|
}
|
||
|
|
||
|
// Update the lastApplied index and term
|
||
|
r.setLastApplied(idx)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// processLog is invoked to process the application of a single committed log.
|
||
|
// Returns if this log entry would cause us to stepDown after it commits.
|
||
|
func (r *Raft) processLog(l *Log, future *logFuture, precommit bool) (stepDown bool) {
|
||
|
switch l.Type {
|
||
|
case LogBarrier:
|
||
|
// Barrier is handled by the FSM
|
||
|
fallthrough
|
||
|
|
||
|
case LogCommand:
|
||
|
// Forward to the fsm handler
|
||
|
select {
|
||
|
case r.fsmCommitCh <- commitTuple{l, future}:
|
||
|
case <-r.shutdownCh:
|
||
|
if future != nil {
|
||
|
future.respond(ErrRaftShutdown)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return so that the future is only responded to
|
||
|
// by the FSM handler when the application is done
|
||
|
return
|
||
|
|
||
|
case LogAddPeer:
|
||
|
fallthrough
|
||
|
case LogRemovePeer:
|
||
|
peers := decodePeers(l.Data, r.trans)
|
||
|
r.logger.Printf("[DEBUG] raft: Node %v updated peer set (%v): %v", r.localAddr, l.Type, peers)
|
||
|
|
||
|
// If the peer set does not include us, remove all other peers
|
||
|
removeSelf := !PeerContained(peers, r.localAddr) && l.Type == LogRemovePeer
|
||
|
if removeSelf {
|
||
|
// Mark that this operation will cause us to step down as
|
||
|
// leader. This prevents the future logs from being Applied
|
||
|
// from this leader.
|
||
|
stepDown = true
|
||
|
|
||
|
// We only modify the peers after the commit, otherwise we
|
||
|
// would be using a quorum size of 1 for the RemovePeer operation.
|
||
|
// This is used with the stepDown guard to prevent any other logs.
|
||
|
if !precommit {
|
||
|
r.peers = nil
|
||
|
r.peerStore.SetPeers([]string{r.localAddr})
|
||
|
}
|
||
|
} else {
|
||
|
r.peers = ExcludePeer(peers, r.localAddr)
|
||
|
r.peerStore.SetPeers(peers)
|
||
|
}
|
||
|
|
||
|
// Handle replication if we are the leader
|
||
|
if r.getState() == Leader {
|
||
|
for _, p := range r.peers {
|
||
|
if _, ok := r.leaderState.replState[p]; !ok {
|
||
|
r.logger.Printf("[INFO] raft: Added peer %v, starting replication", p)
|
||
|
r.startReplication(p)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Stop replication for old nodes
|
||
|
if r.getState() == Leader && !precommit {
|
||
|
var toDelete []string
|
||
|
for _, repl := range r.leaderState.replState {
|
||
|
if !PeerContained(r.peers, repl.peer) {
|
||
|
r.logger.Printf("[INFO] raft: Removed peer %v, stopping replication (Index: %d)", repl.peer, l.Index)
|
||
|
|
||
|
// Replicate up to this index and stop
|
||
|
repl.stopCh <- l.Index
|
||
|
close(repl.stopCh)
|
||
|
toDelete = append(toDelete, repl.peer)
|
||
|
}
|
||
|
}
|
||
|
for _, name := range toDelete {
|
||
|
delete(r.leaderState.replState, name)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Handle removing ourself
|
||
|
if removeSelf && !precommit {
|
||
|
if r.conf.ShutdownOnRemove {
|
||
|
r.logger.Printf("[INFO] raft: Removed ourself, shutting down")
|
||
|
r.Shutdown()
|
||
|
} else {
|
||
|
r.logger.Printf("[INFO] raft: Removed ourself, transitioning to follower")
|
||
|
r.setState(Follower)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
case LogNoop:
|
||
|
// Ignore the no-op
|
||
|
default:
|
||
|
r.logger.Printf("[ERR] raft: Got unrecognized log type: %#v", l)
|
||
|
}
|
||
|
|
||
|
// Invoke the future if given
|
||
|
if future != nil && !precommit {
|
||
|
future.respond(nil)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// processRPC is called to handle an incoming RPC request.
|
||
|
func (r *Raft) processRPC(rpc RPC) {
|
||
|
switch cmd := rpc.Command.(type) {
|
||
|
case *AppendEntriesRequest:
|
||
|
r.appendEntries(rpc, cmd)
|
||
|
case *RequestVoteRequest:
|
||
|
r.requestVote(rpc, cmd)
|
||
|
case *InstallSnapshotRequest:
|
||
|
r.installSnapshot(rpc, cmd)
|
||
|
default:
|
||
|
r.logger.Printf("[ERR] raft: Got unexpected command: %#v", rpc.Command)
|
||
|
rpc.Respond(nil, fmt.Errorf("unexpected command"))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// processHeartbeat is a special handler used just for heartbeat requests
|
||
|
// so that they can be fast-pathed if a transport supports it.
|
||
|
func (r *Raft) processHeartbeat(rpc RPC) {
|
||
|
defer metrics.MeasureSince([]string{"raft", "rpc", "processHeartbeat"}, time.Now())
|
||
|
|
||
|
// Check if we are shutdown, just ignore the RPC
|
||
|
select {
|
||
|
case <-r.shutdownCh:
|
||
|
return
|
||
|
default:
|
||
|
}
|
||
|
|
||
|
// Ensure we are only handling a heartbeat
|
||
|
switch cmd := rpc.Command.(type) {
|
||
|
case *AppendEntriesRequest:
|
||
|
r.appendEntries(rpc, cmd)
|
||
|
default:
|
||
|
r.logger.Printf("[ERR] raft: Expected heartbeat, got command: %#v", rpc.Command)
|
||
|
rpc.Respond(nil, fmt.Errorf("unexpected command"))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// appendEntries is invoked when we get an append entries RPC call.
|
||
|
func (r *Raft) appendEntries(rpc RPC, a *AppendEntriesRequest) {
|
||
|
defer metrics.MeasureSince([]string{"raft", "rpc", "appendEntries"}, time.Now())
|
||
|
// Setup a response
|
||
|
resp := &AppendEntriesResponse{
|
||
|
Term: r.getCurrentTerm(),
|
||
|
LastLog: r.getLastIndex(),
|
||
|
Success: false,
|
||
|
NoRetryBackoff: false,
|
||
|
}
|
||
|
var rpcErr error
|
||
|
defer func() {
|
||
|
rpc.Respond(resp, rpcErr)
|
||
|
}()
|
||
|
|
||
|
// Ignore an older term
|
||
|
if a.Term < r.getCurrentTerm() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Increase the term if we see a newer one, also transition to follower
|
||
|
// if we ever get an appendEntries call
|
||
|
if a.Term > r.getCurrentTerm() || r.getState() != Follower {
|
||
|
// Ensure transition to follower
|
||
|
r.setState(Follower)
|
||
|
r.setCurrentTerm(a.Term)
|
||
|
resp.Term = a.Term
|
||
|
}
|
||
|
|
||
|
// Save the current leader
|
||
|
r.setLeader(r.trans.DecodePeer(a.Leader))
|
||
|
|
||
|
// Verify the last log entry
|
||
|
if a.PrevLogEntry > 0 {
|
||
|
lastIdx, lastTerm := r.getLastEntry()
|
||
|
|
||
|
var prevLogTerm uint64
|
||
|
if a.PrevLogEntry == lastIdx {
|
||
|
prevLogTerm = lastTerm
|
||
|
|
||
|
} else {
|
||
|
var prevLog Log
|
||
|
if err := r.logs.GetLog(a.PrevLogEntry, &prevLog); err != nil {
|
||
|
r.logger.Printf("[WARN] raft: Failed to get previous log: %d %v (last: %d)",
|
||
|
a.PrevLogEntry, err, lastIdx)
|
||
|
resp.NoRetryBackoff = true
|
||
|
return
|
||
|
}
|
||
|
prevLogTerm = prevLog.Term
|
||
|
}
|
||
|
|
||
|
if a.PrevLogTerm != prevLogTerm {
|
||
|
r.logger.Printf("[WARN] raft: Previous log term mis-match: ours: %d remote: %d",
|
||
|
prevLogTerm, a.PrevLogTerm)
|
||
|
resp.NoRetryBackoff = true
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Process any new entries
|
||
|
if n := len(a.Entries); n > 0 {
|
||
|
start := time.Now()
|
||
|
first := a.Entries[0]
|
||
|
last := a.Entries[n-1]
|
||
|
|
||
|
// Delete any conflicting entries
|
||
|
lastLogIdx := r.getLastLogIndex()
|
||
|
if first.Index <= lastLogIdx {
|
||
|
r.logger.Printf("[WARN] raft: Clearing log suffix from %d to %d", first.Index, lastLogIdx)
|
||
|
if err := r.logs.DeleteRange(first.Index, lastLogIdx); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to clear log suffix: %v", err)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Append the entry
|
||
|
if err := r.logs.StoreLogs(a.Entries); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to append to logs: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Update the lastLog
|
||
|
r.setLastLogIndex(last.Index)
|
||
|
r.setLastLogTerm(last.Term)
|
||
|
metrics.MeasureSince([]string{"raft", "rpc", "appendEntries", "storeLogs"}, start)
|
||
|
}
|
||
|
|
||
|
// Update the commit index
|
||
|
if a.LeaderCommitIndex > 0 && a.LeaderCommitIndex > r.getCommitIndex() {
|
||
|
start := time.Now()
|
||
|
idx := min(a.LeaderCommitIndex, r.getLastIndex())
|
||
|
r.setCommitIndex(idx)
|
||
|
r.processLogs(idx, nil)
|
||
|
metrics.MeasureSince([]string{"raft", "rpc", "appendEntries", "processLogs"}, start)
|
||
|
}
|
||
|
|
||
|
// Everything went well, set success
|
||
|
resp.Success = true
|
||
|
r.setLastContact()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// requestVote is invoked when we get an request vote RPC call.
|
||
|
func (r *Raft) requestVote(rpc RPC, req *RequestVoteRequest) {
|
||
|
defer metrics.MeasureSince([]string{"raft", "rpc", "requestVote"}, time.Now())
|
||
|
// Setup a response
|
||
|
resp := &RequestVoteResponse{
|
||
|
Term: r.getCurrentTerm(),
|
||
|
Peers: encodePeers(r.peers, r.trans),
|
||
|
Granted: false,
|
||
|
}
|
||
|
var rpcErr error
|
||
|
defer func() {
|
||
|
rpc.Respond(resp, rpcErr)
|
||
|
}()
|
||
|
|
||
|
// Check if we have an existing leader [who's not the candidate]
|
||
|
candidate := r.trans.DecodePeer(req.Candidate)
|
||
|
if leader := r.Leader(); leader != "" && leader != candidate {
|
||
|
r.logger.Printf("[WARN] raft: Rejecting vote request from %v since we have a leader: %v",
|
||
|
candidate, leader)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Ignore an older term
|
||
|
if req.Term < r.getCurrentTerm() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Increase the term if we see a newer one
|
||
|
if req.Term > r.getCurrentTerm() {
|
||
|
// Ensure transition to follower
|
||
|
r.setState(Follower)
|
||
|
r.setCurrentTerm(req.Term)
|
||
|
resp.Term = req.Term
|
||
|
}
|
||
|
|
||
|
// Check if we have voted yet
|
||
|
lastVoteTerm, err := r.stable.GetUint64(keyLastVoteTerm)
|
||
|
if err != nil && err.Error() != "not found" {
|
||
|
r.logger.Printf("[ERR] raft: Failed to get last vote term: %v", err)
|
||
|
return
|
||
|
}
|
||
|
lastVoteCandBytes, err := r.stable.Get(keyLastVoteCand)
|
||
|
if err != nil && err.Error() != "not found" {
|
||
|
r.logger.Printf("[ERR] raft: Failed to get last vote candidate: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Check if we've voted in this election before
|
||
|
if lastVoteTerm == req.Term && lastVoteCandBytes != nil {
|
||
|
r.logger.Printf("[INFO] raft: Duplicate RequestVote for same term: %d", req.Term)
|
||
|
if bytes.Compare(lastVoteCandBytes, req.Candidate) == 0 {
|
||
|
r.logger.Printf("[WARN] raft: Duplicate RequestVote from candidate: %s", req.Candidate)
|
||
|
resp.Granted = true
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Reject if their term is older
|
||
|
lastIdx, lastTerm := r.getLastEntry()
|
||
|
if lastTerm > req.LastLogTerm {
|
||
|
r.logger.Printf("[WARN] raft: Rejecting vote request from %v since our last term is greater (%d, %d)",
|
||
|
candidate, lastTerm, req.LastLogTerm)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if lastIdx > req.LastLogIndex {
|
||
|
r.logger.Printf("[WARN] raft: Rejecting vote request from %v since our last index is greater (%d, %d)",
|
||
|
candidate, lastIdx, req.LastLogIndex)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Persist a vote for safety
|
||
|
if err := r.persistVote(req.Term, req.Candidate); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to persist vote: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
resp.Granted = true
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// installSnapshot is invoked when we get a InstallSnapshot RPC call.
|
||
|
// We must be in the follower state for this, since it means we are
|
||
|
// too far behind a leader for log replay.
|
||
|
func (r *Raft) installSnapshot(rpc RPC, req *InstallSnapshotRequest) {
|
||
|
defer metrics.MeasureSince([]string{"raft", "rpc", "installSnapshot"}, time.Now())
|
||
|
// Setup a response
|
||
|
resp := &InstallSnapshotResponse{
|
||
|
Term: r.getCurrentTerm(),
|
||
|
Success: false,
|
||
|
}
|
||
|
var rpcErr error
|
||
|
defer func() {
|
||
|
rpc.Respond(resp, rpcErr)
|
||
|
}()
|
||
|
|
||
|
// Ignore an older term
|
||
|
if req.Term < r.getCurrentTerm() {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Increase the term if we see a newer one
|
||
|
if req.Term > r.getCurrentTerm() {
|
||
|
// Ensure transition to follower
|
||
|
r.setState(Follower)
|
||
|
r.setCurrentTerm(req.Term)
|
||
|
resp.Term = req.Term
|
||
|
}
|
||
|
|
||
|
// Save the current leader
|
||
|
r.setLeader(r.trans.DecodePeer(req.Leader))
|
||
|
|
||
|
// Create a new snapshot
|
||
|
sink, err := r.snapshots.Create(req.LastLogIndex, req.LastLogTerm, req.Peers)
|
||
|
if err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to create snapshot to install: %v", err)
|
||
|
rpcErr = fmt.Errorf("failed to create snapshot: %v", err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Spill the remote snapshot to disk
|
||
|
n, err := io.Copy(sink, rpc.Reader)
|
||
|
if err != nil {
|
||
|
sink.Cancel()
|
||
|
r.logger.Printf("[ERR] raft: Failed to copy snapshot: %v", err)
|
||
|
rpcErr = err
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Check that we received it all
|
||
|
if n != req.Size {
|
||
|
sink.Cancel()
|
||
|
r.logger.Printf("[ERR] raft: Failed to receive whole snapshot: %d / %d", n, req.Size)
|
||
|
rpcErr = fmt.Errorf("short read")
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Finalize the snapshot
|
||
|
if err := sink.Close(); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to finalize snapshot: %v", err)
|
||
|
rpcErr = err
|
||
|
return
|
||
|
}
|
||
|
r.logger.Printf("[INFO] raft: Copied %d bytes to local snapshot", n)
|
||
|
|
||
|
// Restore snapshot
|
||
|
future := &restoreFuture{ID: sink.ID()}
|
||
|
future.init()
|
||
|
select {
|
||
|
case r.fsmRestoreCh <- future:
|
||
|
case <-r.shutdownCh:
|
||
|
future.respond(ErrRaftShutdown)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Wait for the restore to happen
|
||
|
if err := future.Error(); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to restore snapshot: %v", err)
|
||
|
rpcErr = err
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Update the lastApplied so we don't replay old logs
|
||
|
r.setLastApplied(req.LastLogIndex)
|
||
|
|
||
|
// Update the last stable snapshot info
|
||
|
r.setLastSnapshotIndex(req.LastLogIndex)
|
||
|
r.setLastSnapshotTerm(req.LastLogTerm)
|
||
|
|
||
|
// Restore the peer set
|
||
|
peers := decodePeers(req.Peers, r.trans)
|
||
|
r.peers = ExcludePeer(peers, r.localAddr)
|
||
|
r.peerStore.SetPeers(peers)
|
||
|
|
||
|
// Compact logs, continue even if this fails
|
||
|
if err := r.compactLogs(req.LastLogIndex); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to compact logs: %v", err)
|
||
|
}
|
||
|
|
||
|
r.logger.Printf("[INFO] raft: Installed remote snapshot")
|
||
|
resp.Success = true
|
||
|
r.setLastContact()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// setLastContact is used to set the last contact time to now
|
||
|
func (r *Raft) setLastContact() {
|
||
|
r.lastContactLock.Lock()
|
||
|
r.lastContact = time.Now()
|
||
|
r.lastContactLock.Unlock()
|
||
|
}
|
||
|
|
||
|
type voteResult struct {
|
||
|
RequestVoteResponse
|
||
|
voter string
|
||
|
}
|
||
|
|
||
|
// electSelf is used to send a RequestVote RPC to all peers,
|
||
|
// and vote for ourself. This has the side affecting of incrementing
|
||
|
// the current term. The response channel returned is used to wait
|
||
|
// for all the responses (including a vote for ourself).
|
||
|
func (r *Raft) electSelf() <-chan *voteResult {
|
||
|
// Create a response channel
|
||
|
respCh := make(chan *voteResult, len(r.peers)+1)
|
||
|
|
||
|
// Increment the term
|
||
|
r.setCurrentTerm(r.getCurrentTerm() + 1)
|
||
|
|
||
|
// Construct the request
|
||
|
lastIdx, lastTerm := r.getLastEntry()
|
||
|
req := &RequestVoteRequest{
|
||
|
Term: r.getCurrentTerm(),
|
||
|
Candidate: r.trans.EncodePeer(r.localAddr),
|
||
|
LastLogIndex: lastIdx,
|
||
|
LastLogTerm: lastTerm,
|
||
|
}
|
||
|
|
||
|
// Construct a function to ask for a vote
|
||
|
askPeer := func(peer string) {
|
||
|
r.goFunc(func() {
|
||
|
defer metrics.MeasureSince([]string{"raft", "candidate", "electSelf"}, time.Now())
|
||
|
resp := &voteResult{voter: peer}
|
||
|
err := r.trans.RequestVote(peer, req, &resp.RequestVoteResponse)
|
||
|
if err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to make RequestVote RPC to %v: %v", peer, err)
|
||
|
resp.Term = req.Term
|
||
|
resp.Granted = false
|
||
|
}
|
||
|
|
||
|
// If we are not a peer, we could have been removed but failed
|
||
|
// to receive the log message. OR it could mean an improperly configured
|
||
|
// cluster. Either way, we should warn
|
||
|
if err == nil {
|
||
|
peerSet := decodePeers(resp.Peers, r.trans)
|
||
|
if !PeerContained(peerSet, r.localAddr) {
|
||
|
r.logger.Printf("[WARN] raft: Remote peer %v does not have local node %v as a peer",
|
||
|
peer, r.localAddr)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
respCh <- resp
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// For each peer, request a vote
|
||
|
for _, peer := range r.peers {
|
||
|
askPeer(peer)
|
||
|
}
|
||
|
|
||
|
// Persist a vote for ourselves
|
||
|
if err := r.persistVote(req.Term, req.Candidate); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to persist vote : %v", err)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Include our own vote
|
||
|
respCh <- &voteResult{
|
||
|
RequestVoteResponse: RequestVoteResponse{
|
||
|
Term: req.Term,
|
||
|
Granted: true,
|
||
|
},
|
||
|
voter: r.localAddr,
|
||
|
}
|
||
|
return respCh
|
||
|
}
|
||
|
|
||
|
// persistVote is used to persist our vote for safety.
|
||
|
func (r *Raft) persistVote(term uint64, candidate []byte) error {
|
||
|
if err := r.stable.SetUint64(keyLastVoteTerm, term); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if err := r.stable.Set(keyLastVoteCand, candidate); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// setCurrentTerm is used to set the current term in a durable manner.
|
||
|
func (r *Raft) setCurrentTerm(t uint64) {
|
||
|
// Persist to disk first
|
||
|
if err := r.stable.SetUint64(keyCurrentTerm, t); err != nil {
|
||
|
panic(fmt.Errorf("failed to save current term: %v", err))
|
||
|
}
|
||
|
r.raftState.setCurrentTerm(t)
|
||
|
}
|
||
|
|
||
|
// setState is used to update the current state. Any state
|
||
|
// transition causes the known leader to be cleared. This means
|
||
|
// that leader should be set only after updating the state.
|
||
|
func (r *Raft) setState(state RaftState) {
|
||
|
r.setLeader("")
|
||
|
r.raftState.setState(state)
|
||
|
}
|
||
|
|
||
|
// runSnapshots is a long running goroutine used to manage taking
|
||
|
// new snapshots of the FSM. It runs in parallel to the FSM and
|
||
|
// main goroutines, so that snapshots do not block normal operation.
|
||
|
func (r *Raft) runSnapshots() {
|
||
|
for {
|
||
|
select {
|
||
|
case <-randomTimeout(r.conf.SnapshotInterval):
|
||
|
// Check if we should snapshot
|
||
|
if !r.shouldSnapshot() {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Trigger a snapshot
|
||
|
if err := r.takeSnapshot(); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to take snapshot: %v", err)
|
||
|
}
|
||
|
|
||
|
case future := <-r.snapshotCh:
|
||
|
// User-triggered, run immediately
|
||
|
err := r.takeSnapshot()
|
||
|
if err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to take snapshot: %v", err)
|
||
|
}
|
||
|
future.respond(err)
|
||
|
|
||
|
case <-r.shutdownCh:
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// shouldSnapshot checks if we meet the conditions to take
|
||
|
// a new snapshot.
|
||
|
func (r *Raft) shouldSnapshot() bool {
|
||
|
// Check the last snapshot index
|
||
|
lastSnap := r.getLastSnapshotIndex()
|
||
|
|
||
|
// Check the last log index
|
||
|
lastIdx, err := r.logs.LastIndex()
|
||
|
if err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to get last log index: %v", err)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Compare the delta to the threshold
|
||
|
delta := lastIdx - lastSnap
|
||
|
return delta >= r.conf.SnapshotThreshold
|
||
|
}
|
||
|
|
||
|
// takeSnapshot is used to take a new snapshot.
|
||
|
func (r *Raft) takeSnapshot() error {
|
||
|
defer metrics.MeasureSince([]string{"raft", "snapshot", "takeSnapshot"}, time.Now())
|
||
|
// Create a snapshot request
|
||
|
req := &reqSnapshotFuture{}
|
||
|
req.init()
|
||
|
|
||
|
// Wait for dispatch or shutdown
|
||
|
select {
|
||
|
case r.fsmSnapshotCh <- req:
|
||
|
case <-r.shutdownCh:
|
||
|
return ErrRaftShutdown
|
||
|
}
|
||
|
|
||
|
// Wait until we get a response
|
||
|
if err := req.Error(); err != nil {
|
||
|
if err != ErrNothingNewToSnapshot {
|
||
|
err = fmt.Errorf("failed to start snapshot: %v", err)
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
defer req.snapshot.Release()
|
||
|
|
||
|
// Log that we are starting the snapshot
|
||
|
r.logger.Printf("[INFO] raft: Starting snapshot up to %d", req.index)
|
||
|
|
||
|
// Encode the peerset
|
||
|
peerSet := encodePeers(req.peers, r.trans)
|
||
|
|
||
|
// Create a new snapshot
|
||
|
start := time.Now()
|
||
|
sink, err := r.snapshots.Create(req.index, req.term, peerSet)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to create snapshot: %v", err)
|
||
|
}
|
||
|
metrics.MeasureSince([]string{"raft", "snapshot", "create"}, start)
|
||
|
|
||
|
// Try to persist the snapshot
|
||
|
start = time.Now()
|
||
|
if err := req.snapshot.Persist(sink); err != nil {
|
||
|
sink.Cancel()
|
||
|
return fmt.Errorf("failed to persist snapshot: %v", err)
|
||
|
}
|
||
|
metrics.MeasureSince([]string{"raft", "snapshot", "persist"}, start)
|
||
|
|
||
|
// Close and check for error
|
||
|
if err := sink.Close(); err != nil {
|
||
|
return fmt.Errorf("failed to close snapshot: %v", err)
|
||
|
}
|
||
|
|
||
|
// Update the last stable snapshot info
|
||
|
r.setLastSnapshotIndex(req.index)
|
||
|
r.setLastSnapshotTerm(req.term)
|
||
|
|
||
|
// Compact the logs
|
||
|
if err := r.compactLogs(req.index); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Log completion
|
||
|
r.logger.Printf("[INFO] raft: Snapshot to %d complete", req.index)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// compactLogs takes the last inclusive index of a snapshot
|
||
|
// and trims the logs that are no longer needed.
|
||
|
func (r *Raft) compactLogs(snapIdx uint64) error {
|
||
|
defer metrics.MeasureSince([]string{"raft", "compactLogs"}, time.Now())
|
||
|
// Determine log ranges to compact
|
||
|
minLog, err := r.logs.FirstIndex()
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("failed to get first log index: %v", err)
|
||
|
}
|
||
|
|
||
|
// Check if we have enough logs to truncate
|
||
|
if r.getLastLogIndex() <= r.conf.TrailingLogs {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Truncate up to the end of the snapshot, or `TrailingLogs`
|
||
|
// back from the head, which ever is further back. This ensures
|
||
|
// at least `TrailingLogs` entries, but does not allow logs
|
||
|
// after the snapshot to be removed.
|
||
|
maxLog := min(snapIdx, r.getLastLogIndex()-r.conf.TrailingLogs)
|
||
|
|
||
|
// Log this
|
||
|
r.logger.Printf("[INFO] raft: Compacting logs from %d to %d", minLog, maxLog)
|
||
|
|
||
|
// Compact the logs
|
||
|
if err := r.logs.DeleteRange(minLog, maxLog); err != nil {
|
||
|
return fmt.Errorf("log compaction failed: %v", err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// restoreSnapshot attempts to restore the latest snapshots, and fails
|
||
|
// if none of them can be restored. This is called at initialization time,
|
||
|
// and is completely unsafe to call at any other time.
|
||
|
func (r *Raft) restoreSnapshot() error {
|
||
|
snapshots, err := r.snapshots.List()
|
||
|
if err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to list snapshots: %v", err)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Try to load in order of newest to oldest
|
||
|
for _, snapshot := range snapshots {
|
||
|
_, source, err := r.snapshots.Open(snapshot.ID)
|
||
|
if err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to open snapshot %v: %v", snapshot.ID, err)
|
||
|
continue
|
||
|
}
|
||
|
defer source.Close()
|
||
|
|
||
|
if err := r.fsm.Restore(source); err != nil {
|
||
|
r.logger.Printf("[ERR] raft: Failed to restore snapshot %v: %v", snapshot.ID, err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Log success
|
||
|
r.logger.Printf("[INFO] raft: Restored from snapshot %v", snapshot.ID)
|
||
|
|
||
|
// Update the lastApplied so we don't replay old logs
|
||
|
r.setLastApplied(snapshot.Index)
|
||
|
|
||
|
// Update the last stable snapshot info
|
||
|
r.setLastSnapshotIndex(snapshot.Index)
|
||
|
r.setLastSnapshotTerm(snapshot.Term)
|
||
|
|
||
|
// Success!
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// If we had snapshots and failed to load them, its an error
|
||
|
if len(snapshots) > 0 {
|
||
|
return fmt.Errorf("failed to load any existing snapshots")
|
||
|
}
|
||
|
return nil
|
||
|
}
|