Skip to main content

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 {}