almost_raft/lib.rs
1//! Consensus or agreeing on some value is a fundamental issue in a distributed system.
2//! While there are algorithms like Paxos exists since long back, the complexity of those
3//! make implementation complicated.
4//!
5//! So Raft was designed to solve the problem while keeping the algorithm understandable.
6//!
7//! Raft tackles the problem in two steps -
8//! * Leader Election - Elect a node as a leader on startup or when the existing one fails
9//! * Log Replication - Maintain the log consistency among nodes
10//!
11//! **This crate handles Leader election provided a list of nodes.**
12//!
13//! For more on Raft [https://raft.github.io](https://raft.github.io).
14//!
15//! ## Usage
16//! *almost-raft* uses a closed loop, the only way to communicate is to use mpsc channel and control
17//! messages.
18//!
19//! First step is to implement `trait Node`.
20//! For example - a simple node that uses mpsc channel to communicate with others
21//! ```ignore
22//! use tokio::sync::mpsc::Sender;
23//! use almost_raft::{Message, Node};
24//! #[derive(Debug, Clone)]
25//! struct NodeMPSC {
26//! id: String,
27//! sender: Sender<Message<NodeMPSC>>,
28//! }
29//!
30//! #[async_trait]
31//! impl Node for NodeMPSC {
32//! type NodeType = NodeMPSC;
33//! async fn send_message(&self, msg: Message<Self::NodeType>) {
34//! self.sender.send(msg).await;
35//! }
36//!
37//! fn node_id(&self) -> &String {
38//! &self.id
39//! }
40//! }
41//! ```
42//! To initiate [RaftElectionState](crate::election::RaftElectionState)
43//! ```ignore
44//! use tokio::sync::mpsc;
45//! use almost_raft::election::RaftElectionState;
46//! let (heartbeat_interval, message_timeout, timeout, max_node, min_node) =
47//! (1000, 20, 5000, 5, 3);
48//! let (tx, mut from_raft) = mpsc::channel(10);
49//! let self_id = uuid::Uuid::new_v4().to_string();
50//! let nodes = vec![]; // we'll add node later
51//! let (state, tx_to_raft) = RaftElectionState::init(
52//! self_id,
53//! timeout,
54//! heartbeat_interval,
55//! message_timeout,
56//! nodes,
57//! tx.clone(),
58//! max_node,
59//! min_node,
60//! );
61//! ```
62//!
63//! Now we can start the election process using the `state`. But this will not necessarily start the
64//! election, it'll wait as long as there isn't enough node (`min_node`).
65//!
66//! ```ignore
67//! use almost_raft::election::raft_election;
68//! tokio::spawn(raft_election(state));
69//! ```
70//!
71//! Let's add nodes
72//! ```ignore
73//! use tokio::sync::mpsc;
74//! use almost_raft::Message;
75//! let (tx,rx) = mpsc::channel(10);
76//! tx_to_raft
77//! .send(Message::ControlAddNode(NodeMPSC {
78//! id: uuid::Uuid::new_v4().to_string(),
79//! sender: tx,
80//! }))
81//! .await;
82//! ```
83//!
84//! Raft will notify through mpsc channel if there's any change in leadership. To receive the event
85//! ```ignore
86//! // let (tx, mut from_raft) = mpsc::channel(10);
87//! // tx was used to initialize RaftElectionState
88//! from_raft.recv().await;
89//! ```
90//!
91
92#![warn(missing_docs)]
93
94/// handles election process
95pub mod election;
96
97use async_trait::async_trait;
98use serde::{Deserialize, Serialize};
99use std::fmt::{Debug, Display};
100use std::hash::Hash;
101
102/// States of the node
103#[derive(Debug, PartialEq)]
104pub enum NodeState {
105 /// Initial or the normal state of the node
106 Follower,
107 /// Node is holding an election and calling for votes
108 Candidate,
109 /// Node won the election with majority votes and became leader
110 Leader,
111 /// Node is terminating
112 Terminating,
113}
114
115/// A Cluster node
116#[async_trait]
117pub trait ClusterNode {
118 /// concrete node type
119 type NodeType: Debug;
120 /// Type of node identifier, can be string or integer or any other type
121 type NodeIdType: Display + Clone + Debug + PartialEq + From<String> + Eq + Hash + Send + Sync;
122 /// send messages to the node
123 async fn send_message(&self, msg: Message<Self>)
124 where
125 Self: Sized;
126 /// unique node identifier
127 fn node_id(&self) -> &Self::NodeIdType;
128
129 // Provide implementation to get id provided by service discovery provider(e.g. Kubernetes).
130 // By default this function is an alias to [`Self::node_id`]
131 // #[deprecated]
132 // fn service_instance_id(&self) -> &String {
133 // //todo remove this method, raft shouldn't have anything related to discovery service
134 // self.node_id()
135 // }
136}
137
138/// Messages to communicate with Raft
139#[derive(Debug, Serialize, Deserialize)]
140pub enum Message<T: ClusterNode> {
141 /// Asking for vote from other nodes for term
142 RequestVote {
143 /// Sender node id
144 requester_node_id: T::NodeIdType,
145 /// raft election term
146 term: usize,
147 },
148 /// Message in response to `Message::RequestVote`
149 RequestVoteResponse {
150 /// raft election term for which the vote was requested for
151 term: usize,
152 /// Is voting for the `term`
153 vote: bool,
154 },
155 /// Heartbeat message
156 HeartBeat {
157 /// Current leader, i.e. message sender's node ID
158 leader_node_id: T::NodeIdType,
159 /// Term of the leader
160 term: usize,
161 },
162 /// Add a new node
163 ControlAddNode(T),
164 /// Remove an existing node, removing self will cause node termination
165 ControlRemoveNode(T),
166 /// A leader has been elected or change of existing one
167 ControlLeaderChanged(T::NodeIdType),
168}
169
170#[doc(hidden)]
171#[macro_export]
172macro_rules! log_error {
173 ($result:expr) => {
174 if let Err(e) = $result {
175 error!("{}", e.to_string());
176 }
177 };
178}
179
180#[cfg(test)]
181mod test {}