Deep Dive into SOFAJRaft: How Java Implements Multi‑Raft Consensus
This article examines the core implementation of SOFAJRaft, a high‑performance Java library based on the Raft consensus algorithm, covering node initialization, leader election, log replication, snapshot handling, fault recovery, and multi‑Raft‑Group support with detailed code examples.
Overview
SOFAJRaft is a production‑grade Java implementation of the Raft consensus algorithm. It targets high‑load, low‑latency distributed systems and supports multiple independent Raft groups (Multi‑Raft‑Group) for sharding or business isolation.
Raft Core Components
Leader election : selects a unique leader in the cluster.
Log replication : the leader copies client request logs to all followers.
Log consistency : a majority of nodes must acknowledge a log entry before it is considered committed.
State‑machine application : committed logs are applied to the state machine to update the system state.
Node Startup and Initialization
The Raft node is represented by the NodeImpl class, which implements Node, lifecycle management, replicator callbacks and state‑machine callbacks.
public class NodeImpl implements Node, Lifecycle<NodeOptions>, Replicator.ReplicatorStateListener, StateMachineCaller.RaftStateMachineListener {
private volatile State state;
private final RaftGroupId groupId;
private final PeerId serverId;
private NodeOptions options;
public NodeImpl(final String groupId, final PeerId serverId) {
this.groupId = new RaftGroupId(groupId);
this.serverId = serverId;
this.options = new NodeOptions();
}
@Override
public synchronized boolean init(final NodeOptions opts) {
this.options = opts; // configure node
// start election timer, heartbeat, etc.
return true;
}
}Leader Election
Each follower runs an ElectionTimer. When the timer expires, the follower becomes a candidate and sends vote requests to other peers.
public class ElectionTimer extends Timer {
private final NodeImpl node;
public ElectionTimer(NodeImpl node) { this.node = node; }
@Override
public void run() { node.handleElectionTimeout(); }
}
private void handleElectionTimeout() {
if (state != State.FOLLOWER) return;
becomeCandidate();
sendVoteRequests();
}Log Replication
The leader uses a Replicator for each follower to send AppendEntries RPCs.
public class LeaderState {
private final NodeImpl node;
private final LogManager logManager;
public LeaderState(NodeImpl node) {
this.node = node;
this.logManager = node.getLogManager();
}
public void replicateLog(final LogEntry logEntry) {
for (PeerId peer : node.getReplicatorList()) {
Replicator replicator = node.getReplicator(peer);
replicator.sendAppendEntries(logEntry);
}
}
}Log Consistency
Followers respond with AppendEntriesResponse. On success the leader updates the commit index; on failure it may retry or resolve conflicts.
public class AppendEntriesResponseHandler {
private final NodeImpl node;
public void handleResponse(AppendEntriesResponse response) {
if (response.success) {
node.getLogManager().commitIndex(response.index);
} else {
node.handleLogReplicationFailure(response);
}
}
}State‑Machine Application
When the commit index advances, the node applies the newly committed entries to the state machine.
public class StateMachineCaller {
private final StateMachine stateMachine;
public void onApply(final List<LogEntry> entries) {
for (LogEntry entry : entries) {
stateMachine.apply(entry);
}
}
}Log Management
LogManagerstores log entries, tracks the commit index and the last applied index, and provides utilities for appending and retrieving unapplied entries.
public class LogManager {
private final List<LogEntry> logEntries = new ArrayList<>();
private long commitIndex;
private long lastApplied;
public synchronized void appendEntry(LogEntry entry) { logEntries.add(entry); }
public synchronized void commitIndex(long newCommitIndex) { this.commitIndex = newCommitIndex; }
public synchronized List<LogEntry> getUnappliedEntries() {
return logEntries.subList((int)lastApplied + 1, (int)commitIndex + 1);
}
public void applyLogsToStateMachine(StateMachine sm) {
for (LogEntry e : getUnappliedEntries()) { sm.apply(e); lastApplied++; }
}
}Snapshot Mechanism
To bound log size, SnapshotManager periodically asks the state machine to generate a snapshot, persists it, and truncates log entries preceding the snapshot index.
public class SnapshotManager {
private final StateMachine stateMachine;
private final LogManager logManager;
private long lastSnapshotIndex;
public SnapshotManager(StateMachine sm, LogManager lm) {
this.stateMachine = sm; this.logManager = lm;
}
public void takeSnapshot() {
Snapshot snapshot = stateMachine.saveSnapshot();
lastSnapshotIndex = logManager.getLastAppliedIndex();
persistSnapshot(snapshot);
logManager.truncatePrefix(lastSnapshotIndex);
}
private void persistSnapshot(Snapshot snapshot) {
// serialize and write to durable storage
}
}Fault Handling and Recovery
Follower Recovery
When a follower restarts, it may receive either a full snapshot ( InstallSnapshot) or incremental log entries ( AppendEntries) from the leader.
public class FollowerRecovery {
private final NodeImpl node;
private final LogManager logManager;
public FollowerRecovery(NodeImpl node) {
this.node = node; this.logManager = node.getLogManager();
}
public void handleInstallSnapshot(InstallSnapshotRequest req) {
Snapshot snap = req.getSnapshot();
node.getStateMachine().loadSnapshot(snap);
logManager.reset(snap.getLastIndex());
}
public void handleAppendEntries(AppendEntriesRequest req) {
logManager.appendEntries(req.getEntries());
}
}Leader Recovery
After a leader crash, the newly elected leader invokes catchUpFollowers to bring all followers up‑to‑date.
public class LeaderRecovery {
private final NodeImpl node;
private final LogManager logManager;
public LeaderRecovery(NodeImpl node) { this.node = node; this.logManager = node.getLogManager(); }
public void catchUpFollowers() {
for (PeerId peer : node.getReplicatorList()) {
Replicator r = node.getReplicator(peer);
r.sendAppendEntries(logManager.getUncommittedEntries());
}
}
}Multi‑Raft‑Group Support
MultiRaftGroupManagermaintains a map of group identifiers to NodeImpl instances, allowing independent Raft clusters to run in the same process.
public class MultiRaftGroupManager {
private final Map<String, NodeImpl> raftGroups = new ConcurrentHashMap<>();
public NodeImpl createRaftGroup(String groupId, PeerId serverId, NodeOptions opts) {
NodeImpl node = new NodeImpl(groupId, serverId);
node.init(opts);
raftGroups.put(groupId, node);
return node;
}
public NodeImpl getRaftGroup(String groupId) { return raftGroups.get(groupId); }
}Key Takeaways
SOFAJRaft implements the full Raft protocol stack: node lifecycle, leader election, log replication, majority‑based commit, state‑machine application, snapshot compaction, fault recovery, and multi‑group management. The provided code snippets illustrate the concrete Java classes and methods that realize each step, making the library suitable for building high‑performance, strongly consistent distributed services.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architecture Stack
Dedicated to original, practical tech insights—from skill advancement to architecture, front‑end to back‑end, the full‑stack path, with Wei Ge guiding you.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
