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

news2024/9/25 9:32:49

大家好,我是 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 哥在这里求个关注和点赞,感谢感谢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2163170.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实验室ICPR 2024论文分享┆FPMT: 基于增强型半监督模型的交通事件检测(含详细视频解读)

目录 论文分享简介 1. 会议介绍 2. 研究背景及主要贡献 3. 方法 4. 实验 5. 结论 6. 论文介绍视频 论文分享简介 本推文详细介绍了一篇实验室的最新论文成果《FPMT: Enhanced Semi-Supervised Model for Traffic Incident Detection》&#xff0c;该论文已被第27届国际…

尚硅谷———-乐(智)尚代驾~~--------Day5----司机认证篇~

前言&#xff1a; Hello亲爱的uu们&#xff0c;在读过了一个愉快的周末后&#xff08;摸鱼了一会&#xff09;&#xff0c;我又回来更新啦&#xff0c;感谢uu们的阅读&#xff0c;话不多说~ 司机认证 当司机点击开始接单的时候&#xff0c;会先判断该司机有没有通过认证&…

关于PCA的一份介绍

在这篇文章中&#xff0c;我将介绍机器学习中的一种无监督学习算法——PCA&#xff0c;因为它主要有两种用途&#xff0c;即降维与特征提取&#xff0c;所以我将将围绕这两种用途来介绍它&#xff0c;包括基本概念&#xff0c;应用与代码实践。 一、 PCA 1.1 概念 PCA&#…

dev containers plugins for vscode构建虚拟开发环境

0. 需求说明 自用笔记本构建一套开发环境&#xff0c;用docker 虚拟插件 dev containers,实现开发环境的构建&#xff0c;我想构建一套LLMs的环境&#xff0c;由于环境配置太多&#xff0c;不想污染本地环境&#xff0c;所以选择隔离技术 1. 环境准备 vscodedocker 2. 步骤…

任意长度并行前缀和 扫描算法 《PMPP》笔记

下面的算法针对于任意长度输入 对于大数据集&#xff0c;首先将输入分为几段&#xff0c;每一段放进共享内存并用一个线程块处理&#xff0c;比如一个线程块使用1024个线程的话&#xff0c;每个块最多能处理2048个元素。 在前面代码中&#xff0c;一个块最后的执行结果保存到了…

桥接模式和NET模式的区别

桥接模式和NET模式的区别 NAT模式&#xff1a; NAT&#xff1a;网络地址转换&#xff08;模式&#xff09;&#xff1a;借助宿主机来上网&#xff0c;没桥接那么麻烦&#xff0c;只用配置DNS即可。 缺点&#xff1a;扎根于宿主机&#xff0c;不能和局域网内其它真实的主机进行…

用Python实现运筹学——Day 2: 线性规划的基本概念

一、学习内容 线性规划的定义&#xff1a; 线性规划&#xff08;Linear Programming, LP&#xff09;是一种用于求解约束条件下线性目标函数最优解的方法。线性规划问题通常涉及最大化或最小化一个线性目标函数&#xff0c;目标函数的变量受一组线性不等式或等式的约束。 目标…

C语言 | Leetcode C语言题解之第435题无重叠区间

题目&#xff1a; 题解&#xff1a; int cmp(int** a, int** b) {return (*a)[1] - (*b)[1]; }int eraseOverlapIntervals(int** intervals, int intervalsSize, int* intervalsColSize) {if (intervalsSize 0) {return 0;}qsort(intervals, intervalsSize, sizeof(int*), cm…

【React】Ant Design 5.x版本drawer抽屉黑边问题

环境 antd: ^5.14.1react: ^18 问题情况 <Drawer open{open} closable{false} mask{false} width{680}getContainer{props.getContainer || undefined}><p>Some contents...</p><p>Some contents...</p><p>Some contents...</p> …

[网络] 网络层--IP协议

目录 一、IP协议 1.1 基本概念 1.2 IP协议报头 1.3 如何将报头和有效载荷分离和分用 1.4 分片与组装 1.5 如何减少分片&#xff1f; 1.6 分片和封装的具体过程 二、网段划分 2.1 再次理解IP地址 2.2 了解DHCP 2.3 网络划分方案 2.4 为什么要进行网络划分 2.5 特殊的…

Java基础——字节流和字符流

字节流和字符流的用法几乎完全一样&#xff0c;区别在于字节流和字符流所操作的数据单元不同&#xff0c;字节流操作的单元是数据单元是8位的字节&#xff0c;字符流操作的是数据单元为16位的字符。 为什么要有字符流&#xff1f; Java中字符是采用Unicode标准&#xff0c;Un…

【Go语言】Go语言结构体全面解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

基于 Canvas 的可缩放拖动网格示例(Vue3以及TypeScript )

文章目录 1. 基本知识2. Vue33. TypeScript 1. 基本知识 基本知识讲解&#xff1a; Canvas API&#xff1a; 一种用于在网页上绘制图形的 HTML 元素&#xff0c;使用 JavaScript 的 Canvas API 来进行绘制 使用 getContext(2d) 方法获取 2D 绘图上下文&#xff0c;允许开发者绘…

MySQL数据库备份详解

文章目录 引言● 数据库备份的重要性 MySQL数据库备份的基础知识● 备份类型1、完全备份2、增量备份3、差异备份 ● 备份工具与方法1、逻辑备份工具——mysqldump2、物理备份工具——Xtrabackup3、第三方解决方案 MySQL数据库备份的实施步骤1、环境准备2、选择合适的备份工具与…

【Linux基础IO】深入解析Linux基础IO缓冲区机制:提升文件操作效率的关键

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;Linux “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;暂无 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀Linux基础IO &#x1f4d2;1. 什么是缓…

14.第二阶段x86游戏实战2-C++语言开发环境搭建-VisualStudio2017

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

基于jsonpath_ng的JSON数据查改增删

jsonpath_ng支持JSON数据的读写操作。 安装 pip install jsonpath-ng测试数据 from jsonpath_ng import parse import jsonjson_data { "store": {"book": [ { "category": "reference","author": "Nigel Rees&qu…

数据集-目标检测系列-鲨鱼检测数据集 shark >> DataBall

数据集-目标检测系列-鲨鱼检测数据集 shark >> DataBall 数据集-目标检测系列-鲨鱼检测数据集 shark 数据量&#xff1a;6k 想要进一步了解&#xff0c;请联系。 DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;百种数据集&#xff0c;持续增加中。 示例&…

【自动驾驶】基于车辆几何模型的横向控制算法 | Stanley 算法详解与编程实现

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

【Python】探索 Elpy:Emacs 中的 Python 开发环境

可以短时间不开心&#xff0c;但别长时间不清醒。 对于使用 Emacs 编辑器的 Python 开发者来说&#xff0c;Elpy 是一个强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;它通过整合多个 Emacs Lisp 和 Python 包&#xff0c;提供了一套完整的 Python 编程支持。本文…