dinghy

package module
v0.0.0-...-e318c52 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 9, 2018 License: MIT Imports: 12 Imported by: 1

README

dinghy GoDoc Build Status

Dinghy implements leader election using part of the raft protocol. It might be useful if you have several workers but only want one of them at a time doing things.

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/upsight/dinghy"
)

func main() {
	addr := flag.String("addr", "localhost:8899", "The address to listen on.")
	nodesList := flag.String("nodes", "localhost:8898,localhost:8897", "Comma separated list of host:port")
	flag.Parse()

	nodes := strings.Split(*nodesList, ",")
	nodes = append(nodes, *addr)

	onLeader := func() error {
		fmt.Println("leader")
		return nil
	}
	onFollower := func() error {
		fmt.Println("me follower")
		return nil
	}

	din, err := dinghy.New(
		*addr,
		nodes,
		onLeader,
		onFollower,
		&dinghy.LogLogger{Logger: log.New(os.Stderr, "logger: ", log.Lshortfile)},
		dinghy.DefaultElectionTickRange,
		dinghy.DefaultHeartbeatTickRange,
	)
	if err != nil {
		log.Fatal(err)
	}
	for _, route := range din.Routes() {
		http.HandleFunc(route.Path, route.Handler)
	}
	go func() {
		if err := din.Start(); err != nil {
			log.Fatal(err)
		}
	}()
	log.Fatal(http.ListenAndServe(*addr, nil))
}

dinghy

Documentation

Overview

Package dinghy is a leader election mechanism that follows the raft protocol https://raft.github.io/ using http as the transport, up until the use of replicated log. For a more complete raft implementation use https://godoc.org/github.com/coreos/etcd/raft or https://github.com/hashicorp/raft

Leader Election

The process begins with all nodes in FOLLOWER state and waiting for an election timeout. This timeout is recommended to be randomized between 150ms and 300ms. In order to reduce the amount of traffic this will be increased to ElectionTickRange.

After the election timeout, the FOLLOWER becomes a CANDIDATE and begins an election term. It starts by voting for itself and then sends out a RequestVote to all other nodes.

If the receiving nodes haven't voted yet, they will then vote for the candidate with a successful request. The current node will reset it's election timeout and when a candidate has a majority vote it will become a LEADER.

At this point the LEADER will begin sending an AppendEntries request to all other nodes at a rate specified by the heartbeat timeout. The heartbeat timeout should be shorter than the election timeout, preferably by a factor of 10. Followers respond to the AppendEntries, and this term will continue until a follower stops receiving a heartbeat and becomes a candidate.

There is a case where two nodes can be come candidates at the same time, which is referred to a split vote. In this case two nodes will start an election for the same term and each reaches a single follower node before the other. In this case each candidate will have two votes with no more available for the term and no majority. A new election will happen and finally a candidate will become a LEADER. This scenario can happen with an even number of nodes.

Index

Constants

View Source
const (
	// RouteAppendEntries for append entries requests.
	RouteAppendEntries = "/appendentries"
	// RouteID for id requests.
	RouteID = "/id"
	// RouteRequestVote for request vote requests.
	RouteRequestVote = "/requestvote"
	// RouteStatus will render the current nodes full state.
	RouteStatus = "/status"
	// RouteStepDown to force a node to step down.
	RouteStepDown = "/stepdown"
)
View Source
const (
	// UnknownLeaderID is set when a new election is in progress.
	UnknownLeaderID = 0
	// NoVote is set to represent the node has not voted.
	NoVote = 0
	// StateCandidate represents the raft candidate state
	StateCandidate = iota
	// StateFollower represents the raft follower state
	StateFollower
	// StateLeader represents the raft leader state
	StateLeader
)

Variables

View Source
var (
	// DefaultOnLeader is a no op function to execute when a node becomes a leader.
	DefaultOnLeader = func() error { return nil }
	// DefaultOnFollower is a no op function to execute when a node becomes a follower.
	DefaultOnFollower = func() error { return nil }
	// DefaultRoutePrefix is what is prefixed for the dinghy routes. (/dinghy)
	DefaultRoutePrefix = "/dinghy"
	// ErrTooFewVotes happens on a RequestVote when the candidate receives less than the
	// majority of votes.
	ErrTooFewVotes = errors.New("too few votes")
	// ErrNewElectionTerm if during RequestVote there is a higher term found.
	ErrNewElectionTerm = errors.New("newer election term")
	// 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")
)
View Source
var (
	// DefaultElectionTickRange will set the range of numbers for the election timeout. For example
	// a value of 1500 will first hash the input Addr to a number from 0 to 1500 and then
	// add that 1500 to give a result between 1500 and 3000
	DefaultElectionTickRange = 4000
	// DefaultHeartbeatTickRange will set the range of numbers for the heartbeat timeout.
	DefaultHeartbeatTickRange = 2000
)

Functions

This section is empty.

Types

type AppendEntriesRequest

type AppendEntriesRequest struct {
	Term     int `json:"term"`
	LeaderID int `json:"leader_id"`
}

AppendEntriesRequest represents AppendEntries requests. Replication logging is ignored.

type ApplyFunc

type ApplyFunc func() error

ApplyFunc is for on leader and on follower events.

type Dinghy

type Dinghy struct {

	// Addr is a host:port for the current node.
	Addr string
	// Nodes is a list of all nodes for consensus.
	Nodes []string
	// OnLeader is an optional function to execute when becoming a leader.
	OnLeader func() error
	// OnFollower is an optional function to execute when becoming a follower.
	OnFollower func() error
	// State for holding the raft state.
	State *State
	// contains filtered or unexported fields
}

Dinghy manages the raft FSM and executes OnLeader and OnFollower events.

func New

func New(addr string, nodes []string, onLeader, onFollower ApplyFunc, l Logger, eMS, hMS int) (*Dinghy, error)

New initializes a new dinghy. Start is required to be run to begin leader election.

func (*Dinghy) AppendEntriesHandler

func (d *Dinghy) AppendEntriesHandler() func(w http.ResponseWriter, r *http.Request)

AppendEntriesHandler (POST) handles append entry requests

func (*Dinghy) AppendEntriesRequest

func (d *Dinghy) AppendEntriesRequest() (int, error)

AppendEntriesRequest will broadcast an AppendEntries request to peers. In the raft protocol this deals with appending and processing the replication log, however for leader election this is unused. It returns the current term with any errors.

func (*Dinghy) BroadcastRequest

func (d *Dinghy) BroadcastRequest(peers []string, method, route string, body []byte, timeoutMS int) []*http.Response

BroadcastRequest will send a request to all other nodes in the system.

func (*Dinghy) IDHandler

func (d *Dinghy) IDHandler() func(w http.ResponseWriter, r *http.Request)

IDHandler (GET) returns the nodes id.

func (*Dinghy) RequestVoteHandler

func (d *Dinghy) RequestVoteHandler() func(w http.ResponseWriter, r *http.Request)

RequestVoteHandler (POST) handles requests for votes

func (*Dinghy) RequestVoteRequest

func (d *Dinghy) RequestVoteRequest() (int, error)

RequestVoteRequest will broadcast a request for votes in order to update dinghy to either a follower or leader. If this candidate becomes leader error will return nil. The latest known term is always returned (this could be a newer term from another peer).

func (*Dinghy) Routes

func (d *Dinghy) Routes() []*Route

Routes create the routes required for leader election.

func (*Dinghy) Start

func (d *Dinghy) Start() error

Start begins the leader election process.

func (*Dinghy) StatusHandler

func (d *Dinghy) StatusHandler() func(w http.ResponseWriter, r *http.Request)

StatusHandler (GET) returns the nodes full state.

func (*Dinghy) StepDownHandler

func (d *Dinghy) StepDownHandler() func(w http.ResponseWriter, r *http.Request)

StepDownHandler (POST) will force the node to step down to a follower state.

func (*Dinghy) Stop

func (d *Dinghy) Stop() error

Stop will stop any running event loop.

type DiscardLogger

type DiscardLogger struct {
}

DiscardLogger is a noop logger.

func (*DiscardLogger) Errorf

func (d *DiscardLogger) Errorf(format string, v ...interface{})

Errorf noop

func (*DiscardLogger) Errorln

func (d *DiscardLogger) Errorln(v ...interface{})

Errorln noop

func (*DiscardLogger) Printf

func (d *DiscardLogger) Printf(format string, v ...interface{})

Printf noop

func (*DiscardLogger) Println

func (d *DiscardLogger) Println(v ...interface{})

Println noop

type LogLogger

type LogLogger struct {
	Logger *log.Logger
}

LogLogger uses the std lib logger.

func (*LogLogger) Errorf

func (d *LogLogger) Errorf(format string, v ...interface{})

Errorf std lib

func (*LogLogger) Errorln

func (d *LogLogger) Errorln(v ...interface{})

Errorln std lib

func (*LogLogger) Printf

func (d *LogLogger) Printf(format string, v ...interface{})

Printf std lib

func (*LogLogger) Println

func (d *LogLogger) Println(v ...interface{})

Println std lib

type Logger

type Logger interface {
	Printf(format string, v ...interface{})
	Println(v ...interface{})
	Errorf(format string, v ...interface{})
	Errorln(v ...interface{})
}

Logger is for logging to a writer. This is not the raft replication log.

type Route

type Route struct {
	Path    string
	Handler func(http.ResponseWriter, *http.Request)
}

Route holds path and handler information.

type State

type State struct {
	// contains filtered or unexported fields
}

State encapsulates the current nodes raft state.

func NewState

func NewState(id int, electionTimeoutMS, heartbeatTimeoutMS int) *State

NewState initializes a new raft state.

func (*State) AppendEntriesEvent

func (s *State) AppendEntriesEvent(event ...*AppendEntriesRequest) chan *AppendEntriesRequest

AppendEntriesEvent returns a channel for any succesful append entries events.

func (*State) ElectionTick

func (s *State) ElectionTick() <-chan time.Time

ElectionTick returns a channel with a new random election tick.

func (*State) HeartbeatReset

func (s *State) HeartbeatReset(reset ...bool) <-chan struct{}

HeartbeatReset will signal a reset. This works with a listener for HeartbeatTick.

func (*State) HeartbeatTick

func (s *State) HeartbeatTick() <-chan time.Time

HeartbeatTick returns a channel with a heartbeat timeout set to heartbeatTimeoutMS.

func (*State) HeartbeatTickRandom

func (s *State) HeartbeatTickRandom() <-chan time.Time

HeartbeatTickRandom returns a channel with a random heartbeat timeout. 500ms is added to the minimum heartbeatTimeoutMS to compensate for possible network latency.

func (*State) ID

func (s *State) ID() int

ID returns the nodes id.

func (*State) LeaderID

func (s *State) LeaderID(id ...int) int

LeaderID will return the states current leader id or if an argument is passed in will set the current LeaderID.

func (*State) State

func (s *State) State(state ...int) int

State will return the states current state or if an argument is passed in will set the state

func (*State) StateChanged

func (s *State) StateChanged() chan int

StateChanged returns a channel for any state changes that occur.

func (*State) StateString

func (s *State) StateString(state int) string

StateString returns the current state as a string.

func (*State) StepDown

func (s *State) StepDown(term int)

StepDown will step down the state by resetting to the given term and emitting a state change.

func (*State) String

func (s *State) String() string

String will return the current states fields for debugging.

func (*State) Term

func (s *State) Term(term ...int) int

Term will return the states current term or if an argument is passed in will set the term

func (*State) VotedFor

func (s *State) VotedFor(votedFor ...int) int

VotedFor will return the states current vote or if an argument is passed in will set the vote

type Status

type Status struct {
	ID       int    `json:"id"`
	LeaderID int    `json:"leader_id"`
	State    string `json:"state"`
	Term     int    `json:"term"`
	VotedFor int    `json:"voted_for"`
}

Status is used to show the current states status.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL