Categorygithub.com/dmitry-kovalev/cluster
modulepackage
0.1.1
Repository: https://github.com/dmitry-kovalev/cluster.git
Documentation: pkg.go.dev

# README

cluster

Reworked version of Dinghy

Cluster 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/dmitry-kovalev/cluster"
)

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

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

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

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

You can test cluster with several nodes using docker-compose

$ export LOCAL_IP=192.168.1.138
$ docker compose up
[+] Running 4/4
 ⠿ Network cluster_default    Created                                                                                                                                                              3.8s
 ⠿ Container cluster_node3_1  Created                                                                                                                                                              0.1s
 ⠿ Container cluster_node1_1  Created                                                                                                                                                              0.1s
 ⠿ Container cluster_node2_1  Created                                                                                                                                                              0.1s
Attaching to node1_1, node2_1, node3_1
node2_1  | 14:19:40 [INFO] [cluster] Creating cluster node with nodes: [192.168.1.138:8899 192.168.1.138:8898 192.168.1.138:8897]
node2_1  | 14:19:40 [INFO] [cluster] Starting cluster node
node2_1  | 14:19:40 [INFO] [cluster] Current state is {"id":3626187060,"leader_id":0,"state":"follower","term":1,"voted_for":0}
node2_1  | 14:19:40 [INFO] [cluster] Entering follower state, leader id 0
node2_1  | 14:19:40 [INFO] Me follower
node3_1  | 14:19:41 [INFO] [cluster] Creating cluster node with nodes: [192.168.1.138:8899 192.168.1.138:8898 192.168.1.138:8897]
node3_1  | 14:19:41 [INFO] [cluster] Starting cluster node
node3_1  | 14:19:41 [INFO] [cluster] Current state is {"id":3776595137,"leader_id":0,"state":"follower","term":1,"voted_for":0}
node3_1  | 14:19:41 [INFO] [cluster] Entering follower state, leader id 0
node3_1  | 14:19:41 [INFO] Me follower
node2_1  | 14:19:42 [INFO] [cluster] Follower heartbeat timeout, transitioning to candidate
node2_1  | 14:19:42 [INFO] [cluster] Current state is {"id":3626187060,"leader_id":0,"state":"candidate","term":2,"voted_for":0}
node2_1  | 14:19:42 [INFO] [cluster] Entering candidate state
node2_1  | 14:19:42 [INFO] [cluster] Requesting vote
node2_1  | 14:19:42 [INFO] [cluster] Got RequestVote request, voting for candidate id 3626187060 {"id":3626187060,"leader_id":0,"state":"candidate","term":2,"voted_for":0}
node3_1  | 14:19:42 [INFO] [cluster] got RequestVote request from newer term, stepping down {Term:2 CandidateID:3626187060 NodeID:3626187060} {"id":3776595137,"leader_id":0,"state":"follower","term":1,"voted_for":0}
node3_1  | 14:19:42 [INFO] [cluster] Got RequestVote request, voting for candidate id 3626187060 {"id":3776595137,"leader_id":0,"state":"follower","term":2,"voted_for":0}
node2_1  | 14:19:42 [INFO] [cluster] Election won with 2 votes, becoming leader {"id":3626187060,"leader_id":0,"state":"candidate","term":2,"voted_for":3626187060}
node2_1  | 14:19:42 [INFO] [cluster] Candidate state changed to leader
node2_1  | 14:19:42 [INFO] [cluster] Current state is {"id":3626187060,"leader_id":3626187060,"state":"leader","term":2,"voted_for":3626187060}
node2_1  | 14:19:42 [INFO] [cluster] Entering leader state
node2_1  | 14:19:42 [INFO] I'm leader
node1_1  | 14:19:43 [INFO] [cluster] Creating cluster node with nodes: [192.168.1.138:8899 192.168.1.138:8898 192.168.1.138:8897]
node1_1  | 14:19:43 [INFO] [cluster] Starting cluster node
node1_1  | 14:19:43 [INFO] [cluster] Current state is {"id":3070019158,"leader_id":0,"state":"follower","term":1,"voted_for":0}
node1_1  | 14:19:43 [INFO] [cluster] Entering follower state, leader id 0
node1_1  | 14:19:43 [INFO] Me follower
node1_1  | 14:19:43 [INFO] [cluster] Got AppendEntries request from newer term, stepping down {Term:2 LeaderID:3626187060 NodeID:3626187060} {"id":3070019158,"leader_id":0,"state":"follower","term":1,"voted_for":0}

# Packages

No description provided by the author

# Functions

New initializes a new cluster.
NewState initializes a new raft state.

# Constants

NoVote is set to represent the node has not voted.
RouteAppendEntries for append entries requests.
RouteID for id requests.
RouteRequestVote for request vote requests.
RouteStatus will render the current nodes full state.
RouteStepDown to force a node to step down.
StateCandidate represents the raft candidate state.
StateFollower represents the raft follower state.
StateLeader represents the raft leader state.
UnknownLeaderID is set when a new election is in progress.

# Variables

DefaultElectionTickRange will set the range of numbers for the election timeout.
DefaultHeartbeatTickRange will set the range of numbers for the heartbeat timeout.
DefaultOnFollower is a no op function to execute when a node becomes a follower.
DefaultOnLeader is a no op function to execute when a node becomes a leader.
DefaultRoutePrefix is what is prefixed for the cluster routes.
ErrLeader is returned when an operation can't be completed on a leader node.
ErrNewElectionTerm if during RequestVote there is a higher term found.
ErrNotLeader is returned when an operation can't be completed on a follower or candidate node.
ErrTooFewVotes happens on a RequestVote when the candidate receives less than the majority of votes.

# Structs

AppendEntriesRequest represents AppendEntries requests.
Cluster manages the raft FSM and executes OnLeader and OnFollower events.
DiscardLogger is a noop logger.
LogLogger uses the std lib logger.
Route holds path and handler information.
State encapsulates the current nodes raft state.
Status is used to show the current states status.

# Interfaces

Logger is for logging to a writer.

# Type aliases

ApplyFunc is for on leader and on follower events.