蚂蚁Raft一致性算法库SOFAJRaft深入分析

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 大家好,我是 V 哥。SOFAJRaft 是蚂蚁金服开源的一款基于 Raft 共识算法的 Java 实现,特别适合高负载、低延迟的分布式系统。它支持 Multi-Raft-Group,能同时处理多个 Raft 集群,具备扩展性和强一致性保障。项目源自百度的 braft,并在性能和功能上进行了优化。本文将深入探讨 SOFAJRaft 的核心源码实现,包括 Leader 选举、日志复制、一致性维护、日志管理和快照机制等。通过关键代码展示其在节点初始化、日志复制、一致性维护等方面的设计思路。希望帮助大家更好地理解 Raft 算法,求关注和点赞,感谢!

大家好,我是 V 哥,SOFAJRaft 是蚂蚁金服开源的一个基于 Raft 共识算法的 Java 实现,它特别适合高负载、低延迟的分布式系统场景。SOFAJRaft 支持 Multi-Raft-Group,能够同时处理多个 Raft 集群,具有扩展性和强一致性保障。这个项目是从百度的 braft 移植而来的,并且在性能和功能上做了多项优化。今天的文章,V 哥来聊一聊SOFAJRaft的核心源码实现。

打开全球最大的基友网站 Github,搜索 sofa-jraft,可以找到SOFAJRaft库的源码实现:

SOFAJRaft 是一个基于 RAFT 一致性算法的生产级高性能 Java 实现,支持 MULTI-RAFT-GROUP,适用于高负载低延迟的场景。 使用 SOFAJRaft 你可以专注于自己的业务领域,由 SOFAJRaft 负责处理所有与 RAFT 相关的技术难题,并且 SOFAJRaft 非常易于使用,你可以通过几个示例在很短的时间内掌握它。

V哥要介绍的不是基础应用,而是通过SOFAJRaft库的实现原理,帮助兄弟们来理解Raft算法

SOFAJRaft 核心概念

SOFAJRaft 的核心是 Raft 算法,它主要的组件包括:

  • Leader 选举:用于在集群中选出唯一的 Leader。
  • 日志复制:Leader 将客户端的请求日志复制到所有的 Follower。
  • 日志一致性:通过多数派机制确保集群中的日志是一致的。
  • 日志应用:日志经过多数派确认后应用到状态机中。

核心源码分析

1. Raft 节点启动与初始化

SOFAJRaft 中的 Raft 节点通过 NodeImpl 类进行管理,它是 Raft 节点的核心实现。

public class NodeImpl implements Node, Lifecycle<NodeOptions>, Replicator.ReplicatorStateListener, StateMachineCaller.RaftStateMachineListener {
   
    // Raft 节点状态
    private volatile State state;
    private final RaftGroupId groupId; // Raft group ID
    private final PeerId serverId;     // 当前节点 ID
    private final 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;
        // 启动选举定时器等逻辑
    }
}

在这里,NodeImpl 类的 init 方法用于初始化 Raft 节点,它会设置 Raft 节点的配置并启动选举定时器等机制。

2. Leader 选举

Raft 的 Leader 选举是通过定时器和心跳机制来实现的。当 Follower 没有在一段时间内收到 Leader 的心跳时,它会进入选举状态。

public class ElectionTimer extends Timer {
   
    private final NodeImpl node;

    public ElectionTimer(NodeImpl node) {
   
        this.node = node;
    }

    @Override
    public void run() {
   
        // 处理选举超时
        this.node.handleElectionTimeout();
    }
}

当定时器超时时,会触发 handleElectionTimeout 方法进行选举。

private void handleElectionTimeout() {
   
    if (this.state != State.FOLLOWER) {
   
        return;
    }
    // 进入候选者状态
    becomeCandidate();
    // 发送投票请求
    sendVoteRequests();
}

这里的逻辑非常清晰了,当节点是 Follower 并且发生选举超时时,它会转换为候选者并开始发送投票请求给其他节点。

3. 日志复制

在 Raft 中,Leader 负责将客户端的请求日志复制到 Follower。

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) {
   
        // 将日志复制到 Follower 节点
        for (PeerId peer : node.getReplicatorList()) {
   
            Replicator replicator = node.getReplicator(peer);
            replicator.sendAppendEntries(logEntry);
        }
    }
}

在这里,Leader 通过 Replicator 将日志复制到所有 Follower 节点,sendAppendEntries 方法会发送 AppendEntries 请求。

4. 日志一致性

Raft 算法通过多数派机制来确保日志的一致性,来看一下源码:

public class AppendEntriesResponseHandler {
   
    private final NodeImpl node;

    public void handleResponse(AppendEntriesResponse response) {
   
        if (response.success) {
   
            // 更新提交的日志索引
            node.getLogManager().commitIndex(response.index);
        } else {
   
            // 如果失败,可能需要重新发送日志或处理冲突
            node.handleLogReplicationFailure(response);
        }
    }
}

当节点收到 AppendEntriesResponse 时,如果复制成功,它会更新日志的提交索引,确保日志的一致性。

5. 状态机应用

一旦日志被提交,Raft 将这些日志应用到状态机中,以实现最终的系统状态更新。

public class StateMachineCaller {
   
    private final StateMachine stateMachine;

    public void onApply(final List<LogEntry> entries) {
   
        // 将提交的日志应用到状态机
        for (LogEntry entry : entries) {
   
            stateMachine.apply(entry);
        }
    }
}

状态机将处理客户端请求并更新系统状态,这里 apply 方法会被调用来执行具体的业务逻辑。

我们继续深入探讨 SOFAJRaft 的其他核心部分,包括**日志管理(Log Management)**、**快照(Snapshot)机制**和**故障处理**,这些部分在分布式系统中都非常重要,尤其在长时间运行和高负载场景下。

6. 日志管理(Log Management)

日志管理是 Raft 协议中重要的一部分,它保证了每个节点在不同时间点所保存的日志能够保持一致。SOFAJRaft 使用 LogManager 来管理日志的存储和持久化。实现的代码是这样滴:

public class LogManager {
   
    private final List<LogEntry> logEntries; // 日志条目列表
    private long commitIndex;  // 当前提交的日志索引
    private long lastApplied;  // 最后应用的日志索引

    public LogManager() {
   
        this.logEntries = new ArrayList<>();
    }

    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 stateMachine) {
   
        List<LogEntry> unappliedEntries = getUnappliedEntries();
        for (LogEntry entry : unappliedEntries) {
   
            stateMachine.apply(entry); // 应用日志到状态机
            lastApplied++;
        }
    }
}

在日志管理中,LogManager 负责维护 Raft 节点的所有日志条目,并根据多数派的确认来更新提交的日志索引。当提交的日志多于 commitIndex 时,这些日志可以应用到状态机中。applyLogsToStateMachine 方法则负责将日志条目应用到状态机。

7. 快照机制(Snapshot)

在长时间运行的集群中,如果仅仅依赖日志复制,日志可能会积累得非常庞大,影响性能和磁盘空间的使用。那要肿么办呢?因此,Raft 设计了快照(Snapshot)机制来定期将当前状态持久化,并丢弃已经持久化的日志。

public class SnapshotManager {
   
    private final StateMachine stateMachine;
    private final LogManager logManager;
    private long lastSnapshotIndex;

    public SnapshotManager(StateMachine stateMachine, LogManager logManager) {
   
        this.stateMachine = stateMachine;
        this.logManager = logManager;
    }

    public void takeSnapshot() {
   
        // 生成新的快照
        Snapshot snapshot = stateMachine.saveSnapshot();
        this.lastSnapshotIndex = logManager.getLastAppliedIndex();
        // 持久化快照到磁盘
        persistSnapshot(snapshot);
        // 清理旧的日志条目
        logManager.truncatePrefix(lastSnapshotIndex);
    }

    private void persistSnapshot(Snapshot snapshot) {
   
        // 将快照写入磁盘的实现逻辑
        // 如将 snapshot 对象序列化并写入文件系统
    }
}

SnapshotManager 中,takeSnapshot 方法会触发状态机生成当前的快照,并持久化到磁盘。当快照创建完成后,旧的日志条目可以被截断以释放存储空间。这极大地减少了日志的冗余,提高了系统的性能。

8. 故障处理与恢复

SOFAJRaft 具有健全的故障处理机制,能够处理节点的崩溃和网络分区等情况。Raft 协议通过日志复制和 Leader 选举机制来保证系统的容错性。

Follower 的故障恢复

当 Follower 恢复之后,会向 Leader 请求缺失的日志,Leader 会通过 InstallSnapshot 或者 AppendEntries 来将最新的日志发送给 Follower。

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 request) {
   
        // 收到 Leader 的快照安装请求
        Snapshot snapshot = request.getSnapshot();
        node.getStateMachine().loadSnapshot(snapshot);
        logManager.reset(snapshot.getLastIndex());
    }

    public void handleAppendEntries(AppendEntriesRequest request) {
   
        // 收到 Leader 的日志复制请求
        List<LogEntry> entries = request.getEntries();
        logManager.appendEntries(entries);
    }
}

handleInstallSnapshot 用于处理 Leader 发送的快照请求,当日志缺失过多时,Leader 会将整个快照发给 Follower,避免重复发送大量的日志。handleAppendEntries 则用于正常情况下的日志复制和恢复。

Leader 的故障恢复

Leader 故障后,集群会通过新的 Leader 选举恢复正常工作。Leader 选举过程在前面的部分已经详细介绍,当一个新的 Leader 被选出后,它会尝试将自己的日志与 Follower 同步。

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() {
   
        // 向所有 Follower 发送最新的日志条目
        for (PeerId peer : node.getReplicatorList()) {
   
            Replicator replicator = node.getReplicator(peer);
            replicator.sendAppendEntries(logManager.getUncommittedEntries());
        }
    }
}

新的 Leader 会调用 catchUpFollowers 来确保所有的 Follower 都与它保持一致,利用 Raft 的日志复制机制恢复一致性。

9. Multi-Raft-Group 的支持

SOFAJRaft 的一大特色是对 Multi-Raft-Group 的支持,也就是说,它能够管理多个独立的 Raft 集群。这使得它在一些需要分片或者不同业务隔离的场景中能够很好地应用。

public class MultiRaftGroupManager {
   
    private final Map<String, NodeImpl> raftGroups = new ConcurrentHashMap<>();

    public NodeImpl createRaftGroup(String groupId, PeerId serverId, NodeOptions options) {
   
        NodeImpl node = new NodeImpl(groupId, serverId);
        node.init(options);
        raftGroups.put(groupId, node);
        return node;
    }

    public NodeImpl getRaftGroup(String groupId) {
   
        return raftGroups.get(groupId);
    }
}

MultiRaftGroupManager 负责管理多个 Raft 集群,通过 createRaftGroup 方法可以创建新的 Raft 集群,每个集群都有自己的 NodeImpl 实例。这种架构设计让系统可以同时运行多个 Raft 实例,从而大幅提升扩展性。

总结

SOFAJRaft 基于 Raft 算法实现了一个高性能、支持 Multi-Raft-Group 的分布式一致性系统。它通过 NodeImpl 负责 Raft 节点的管理,通过 Leader 选举、日志复制、多数派机制等实现分布式系统中的强一致性。

关键代码展示了从节点初始化到日志复制和一致性维护的核心流程,这些是 Raft 算法的重要组成部分。

SOFAJRaft 的设计通过日志管理、快照机制、故障处理以及 Multi-Raft-Group 的支持,提供了一个健壮且高效的分布式一致性解决方案。通过对关键代码的分析,我们可以看到它在处理日志复制、一致性维护和快照生成上的精妙实现,能够有效应对高负载、长时间运行的分布式系统场景。

好了,整理的学习笔记就到这里,分享给大家,希望可以帮助你更加深入的理解 Raft 算法,V 哥在这里求个关注和点赞,感谢感谢。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
6月前
|
消息中间件 算法 分布式数据库
Raft算法:分布式一致性领域的璀璨明珠
【4月更文挑战第21天】Raft算法是分布式一致性领域的明星,通过领导者选举、日志复制和安全性解决一致性问题。它将复杂问题简化,角色包括领导者、跟随者和候选者。领导者负责日志复制,确保多数节点同步。实现细节涉及超时机制、日志压缩和网络分区处理。广泛应用于分布式数据库、存储系统和消息队列,如Etcd、TiKV。其简洁高效的特点使其在分布式系统中备受青睐。
|
6月前
|
算法 分布式数据库
Paxos算法:分布式一致性的基石
【4月更文挑战第21天】Paxos算法是分布式一致性基础,由Leslie Lamport提出,包含准备和提交阶段,保证安全性和活性。通过提案编号、接受者和学习者实现,广泛应用于分布式数据库、锁和配置管理。其简单、高效、容错性强,影响了后续如Raft等算法,是理解分布式系统一致性关键。
|
存储 算法 数据可视化
分布式理论和一致性算法
分布式系统是一个硬件或软件组成分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统
133 0
分布式理论和一致性算法
|
3月前
|
存储 算法 NoSQL
(七)漫谈分布式之一致性算法下篇:一文从根上儿理解大名鼎鼎的Raft共识算法!
Raft通过一致性检查,能在一定程度上保证集群的一致性,但无法保证所有情况下的一致性,毕竟分布式系统各种故障层出不穷,如何在有可能发生各类故障的分布式系统保证集群一致性,这才是Raft等一致性算法要真正解决的问题。
112 11
|
6月前
|
算法 安全
金石原创 |【分布式技术专题】「分布式技术架构」一文带你厘清分布式事务协议及分布式一致性协议的算法原理和核心流程机制(Paxos篇)
金石原创 |【分布式技术专题】「分布式技术架构」一文带你厘清分布式事务协议及分布式一致性协议的算法原理和核心流程机制(Paxos篇)
355 1
金石原创 |【分布式技术专题】「分布式技术架构」一文带你厘清分布式事务协议及分布式一致性协议的算法原理和核心流程机制(Paxos篇)
|
6月前
|
算法 程序员 分布式数据库
分布式一致性必备:一文读懂Raft算法
Raft算法是一种用于分布式系统中复制日志一致性管理的算法。它通过选举领导者来协调日志复制,确保所有节点数据一致。算法包括心跳机制、选举过程、日志复制和一致性保证。当领导者失效时,节点会重新选举,保证高可用性。Raft易于理解和实现,提供强一致性,常用于分布式数据库和协调服务。作者小米分享了相关知识,鼓励对分布式系统感兴趣的读者进一步探索。
1312 0
|
6月前
|
存储 算法 NoSQL
分布式一致性与共识算法(一)
分布式一致性与共识算法(一)
109 0
|
存储 算法 Java
分布式系统的一致性与共识(1)-综述
分布式系统中的许多事情可能出错,最简单方法是让整个服务失效,并向用户显示错误消息。若无法接受,就得找到容错方法:即使某些内部组件出现故障,服务也能正常运行。
176 0
|
存储 算法 数据可视化
分布式一致性如何实现?- Raft 算法
Raft 是一种管理复制日志的一致性算法,它比 Paxos 更容易理解和实现。Raft 为了更加容易理解和实现,做了算法拆解,Raft 将一致性算法抽象为几个关键模块,例如:领导人选举、日志复制、安全等。
554 2
|
存储 缓存 算法
【组件可编排】分布式一致性系列:Paxos 算法——教会协议
本文主要以简化类比的方式阐述了 Paxos 算法中的单法令教会会议(The Single-decree SYNOD)的算法,文中的纰漏,欢迎指正。
204 0
【组件可编排】分布式一致性系列:Paxos 算法——教会协议