Distributed consensus with Raft
$ dotnet add package KommanderKommander is an open-source, distributed consensus library implemented in C# for the .NET platform. It leverages the Raft algorithm to provide a robust and reliable mechanism for leader election, log replication, and data consistency across clusters. Kommander is designed to be flexible and resilient, supporting multiple discovery mechanisms and communication protocols to suit various distributed system architectures.
[!WARNING] BETA SOFTWARE Kommander is under heavy development and some features may be unstable!
Raft is a consensus protocol that helps a cluster of nodes maintain a replicated state machine by synchronizing a replicated log. This log ensures that each node's state remains consistent across the cluster. Kommander implements the core Raft algorithm, providing a minimalistic design that focuses solely on the essential components of the protocol. By separating storage, messaging serialization, and network transport from the consensus logic, Kommander offers flexibility, determinism, and improved performance.
To install Kommander into your C#/.NET project, you can use the .NET CLI or the NuGet Package Manager.
dotnet add package Kommander
Search for Kommander and install it from the NuGet package manager UI, or use the Package Manager Console:
Install-Package Kommander
Or, using the NuGet Package Manager in Visual Studio, search for Kommander and install it.
Below is a basic example demonstrating how to set up a simple Kommander node, join a cluster, and start the consensus process.
// Identify the node configuration, including the host, port, and the maximum number of partitions.
RaftConfiguration config = new()
{
// Node will announce itself as localhost:8001
NodeId = "node1",
Host = "localhost",
Port = 8001,
// Partitions allow nodes to be leaders/followers for different sets of data
MaxPartitions = 3
};
// Create a Raft node with the specified configuration.
IRaft node = new RaftManager(
new ActorSystem(),
config,
// Node will use a static discovery mechanism to find other nodes in the cluster.
new StaticDiscovery([new("localhost:8002"), new("localhost:8003")]),
// Node will use a RocksDb Write-Ahead Log (WAL) per partition for log persistence.
new RocksDbWAL(path: "./data", version: "v1"),
// Node will use gRPC endpoints for communication with other nodes.
new GrpcCommunication()
// Node will use a new HybridLogicalClock for timestamping log entries.
new HybridLogicalClock(),
logger
);
// Subscribe to the OnReplicationReceived event to receive log entries from other nodes
// if the node is a follower
node.OnReplicationReceived += (RaftLog log) =>
{
Console.WriteLine("Replication received: {0} {1} {2}", log.Id, log.LogType, Encoding.UTF8.GetString(log.LogData));
return Task.FromResult(true);
};
// Start the node and join the cluster.
await node.JoinCluster();
// Check if the node is the leader of partition 0 and replicate a log entry.
if (await node.AmILeader(0))
{
(bool success, RaftOperationStatus status, long commitLogId) = await node.ReplicateLogs(0, "Kommander is awesome!");
if (success)
Console.WriteLine("Replicated log with id: {0}", commitLogId);
else
Console.WriteLine("Replication failed {0}", status);
}
Kommander supports advanced configurations including:
For detailed configuration options, please refer to the Documentation.
Partitions: A Raft cluster can have multiple partitions (sometimes called regions or tablets) within its dataset. Each partition elects its own leader and followers, allowing each node in the cluster to act as both a leader and a follower across different partitions. Having multiple partitions can improve the cluster's throughput by reducing contention and enabling more operations to run concurrently. However, it also increases network traffic and can lead to bottlenecks depending on the available hardware. Proper tuning of this parameter is essential to maintain a healthy cluster.
Quorum: A quorum is the minimum number of nodes required to reach a consensus in a Raft cluster. For a cluster with N nodes, a quorum is defined as (N/2) + 1. This ensures that a majority of nodes must agree on a decision before it is considered committed. Quorums are essential for maintaining consistency and fault tolerance in distributed systems.
Elections: The Raft algorithm selects a leader for each partition. The remaining nodes become followers. If the leader node fails or becomes unreachable, a new election process is triggered to select a replacement.
Leader: Each partition designates a leader. The leader is responsible for handling requests assigned to its partition and replicating them to followers until a quorum is reached. It maintains a local copy of the logs.
Followers: Followers receive replication logs from the leader and store local copies on their respective nodes. They continuously monitor the leader, and if it fails, they may nominate themselves as candidates in a new election for leadership.
Logs: Logs store any information that developers need to persist and replicate within certain partition in the cluster.
Checkpoint: Developers can determine when stored logs are secure and create a log checkpoint, which speeds up future recovery processes and simplifies the addition of new nodes to the cluster.
Write-Ahead Log (WAL): Logs are immediately written to disk in the most durable and consistent manner possible before being replicated to other nodes or notifying the application of a newly arrived log. This ensures data recovery in the event of node failures or crashes.
Heartbeat: The leader continuously sends heartbeat signals to followers to confirm its availability and health.
Communication: Nodes communicate with each other via RPCs to handle leader elections, send heartbeats, and replicate logs. Kommander supports communication using either gRPC or Rest/Json, both of which offer distinct advantages and are widely familiar to developers.
Log Id: Kommmander maintains a per partition 64-bit cluster-wide counter, known as the log id, which increments each time a new proposal is added. This revision functions as a global logical clock, ensuring a sequential order for all updates to the partition log. Each new log id represents an incremental change.
Replication: The leader replicates logs to followers, ensuring that all nodes remain consistent and up-to-date. Once a quorum of nodes acknowledges the log entry, it is considered committed and can be applied to the state machine.
Proposals: Clients send requests to the leader to propose changes to the cluster. If proposed changes are accepted by a quorum of nodes, the commit process is initiated.
Kommander relies on the following consensus algorithm to ensure that changes are applied consistently across all nodes. The commit phase is a critical part of this process. Here’s how it works:
We welcome contributions to Kommander! If you’d like to contribute, please follow these steps:
For more details, see our CONTRIBUTING.md.
Kommander is licensed under the MIT License. See the LICENSE file for details.