发布时间:2026/7/24 5:43:31
Raft协议实现数据的分布式存储 Raft 是一种分布式共识协议。它本身不直接负责把数据存入磁盘而是保证多个节点按照相同顺序执行相同的操作从而让多个节点拥有一致的数据副本。可以把它理解为客户端命令 ↓ Raft 复制日志 ↓ 多个节点按相同顺序执行日志 ↓ 每个节点得到相同的 KV 数据例如PUT user:1001 {name:张三}Raft 要保证所有副本最终都按同样的顺序执行这条命令。一、Raft 实现分布式存储的总体结构一个基于 Raft 的 KV 存储系统通常分为四层客户端 │ ▼ 请求路由层 │ ▼ Raft 共识层 │ ▼ 状态机层 │ ▼ 本地存储引擎具体来说1. 客户端层客户端发送PUT(user:1001, 张三) GET(user:1001)2. Raft 层Raft 负责选举 Leader复制操作日志确认多数节点已经保存日志保证日志顺序一致处理节点故障和 Leader 故障3. 状态机层状态机负责真正执行命令PUT user:1001 张三执行后变成内存状态 user:1001 → 张三4. 本地存储层每个节点可以将 Raft 日志和状态机数据保存到本地磁盘例如WAL 日志SSTableB 树RocksDBLevelDB自定义文件Raft 保证的是“大家执行的命令相同”本地存储引擎负责“如何保存这些命令产生的数据”。二、Raft 集群中的三种角色Raft 节点有三种角色Follower 跟随者 Candidate 候选者 Leader 领导者1. FollowerFollower 不主动处理普通写请求主要负责接收 Leader 的心跳接收 Leader 的日志投票选举保存日志执行已经提交的日志2. Candidate当 Follower 长时间没有收到 Leader 的消息时会认为 Leader 可能失效转换为 Candidate开始发起选举。3. LeaderLeader 负责接收客户端请求追加日志向 Followers复制日志判断日志是否提交通知 Followers 执行日志正常情况下客户端只需要和 Leader 通信。三、写入数据的完整流程假设客户端执行PUT user:1001 {name:张三,age:25}集群结构客户端 │ ▼ Node A Leader / \ ▼ ▼ Node B Node C Follower Follower第一步客户端找到 Leader客户端可能首先连接到 Node B但 B 是 Follower。B 可以返回 Leader 地址将请求转发给 Leader直接拒绝并提示客户端重试最终请求到达 Node A。PUT user:1001 ... │ ▼ Node A Leader第二步Leader 将命令写入日志Leader 不会马上直接修改最终 KV 状态而是先将命令追加到本地日志日志 index term command ----------------------------------------------- 1 3 SET config:x 1 2 4 PUT user:1001 张三 3 5 PUT user:1001 {name:张三,age:25}日志中的几个重要字段Index日志条目的位置1、2、3、4……Term写入这条日志时 Leader 所处的任期。Command真正要执行的操作PUT user:1001 ... DELETE user:1001 INCR stock:1001第三步Leader 向 Followers 复制日志Leader 向 B、C 发送日志A ──日志 index3── B A ──日志 index3── CFollower 收到后会先写入自己的本地日志。Node B保存 index3 Node C保存 index3Follower 此时通常还不能立即执行这条命令因为这条日志还没有被 Leader 确认提交。第四步等待多数节点确认假设 B 成功保存C 暂时宕机A保存成功 B保存成功 C没有响应三个节点中有两个节点已经保存A B 2达到多数派因此该日志可以提交。commitIndex 3第五步Leader 执行状态机Leader 将已经提交的日志交给状态机执行PUT user:1001 {name:张三,age:25}状态机执行后KV 数据 user:1001 → {name:张三,age:25}然后 Leader 向客户端返回成功OK第六步通知 Followers 执行Leader 会在后续心跳或日志同步消息中告诉 FollowersleaderCommit 3B 看到leaderCommit3后也执行 index3Node B user:1001 → {name:张三,age:25}C 恢复后Leader 会先把缺少的日志补给 CC 再执行这些已经提交的命令。四、Raft 中的“提交”和“应用”不是一回事这是一个很重要的概念。日志复制成功表示日志已经保存在足够多的节点上。A、B 已保存 index10日志提交表示 Leader 确认它已经不会丢失commitIndex 10状态机应用表示节点真正执行了命令user:1001 → 张三流程是日志写入 ↓ 达到多数派 ↓ 日志提交 ↓ 状态机应用 ↓ KV 数据发生变化通常每个节点维护两个位置commitIndex已经提交到哪里 lastApplied已经执行到哪里要求lastApplied commitIndex节点会持续将lastApplied 1到commitIndex之间的日志交给状态机执行。五、KV 数据如何与 Raft 状态机结合Raft 只处理命令不直接理解 KV 业务。例如客户端发来PUT user:1 张三系统可以把它编码成Command { type: PUT, key: user:1, value: 张三 }Leader 将这个 Command 写入 Raft 日志LogEntry { index: 10, term: 7, command: PUT user:1 张三 }当日志提交后每个节点都调用相同的状态机stateMachine.apply(command)伪代码可以表示为function apply(command): if command.type PUT: kv[command.key] command.value if command.type DELETE: delete kv[command.key] if command.type INCR: kv[command.key] command.amount由于所有节点拥有相同的日志所有节点按照相同顺序执行状态机逻辑确定性一致所以最终得到的 KV 数据也一致Node Auser:1 → 张三 Node Buser:1 → 张三 Node Cuser:1 → 张三这叫做状态机复制 Replicated State Machine六、读取数据如何处理写请求通常必须发送给 Leader但读请求有多种处理方式。1. 从 Leader 读取最简单的方式GET → Leader这样可以保证读取到最新提交的数据。但 Leader 需要确认自己仍然是当前 Leader否则可能出现旧 Leader 读取旧数据的问题。2. ReadIndexLeader 通过一次心跳确认自己仍然获得多数派支持然后执行读取。适合需要线性一致性的读取。3. Leader LeaseLeader 在一个租约时间内认为自己仍然有效可以直接读取。优点是延迟低缺点是依赖时钟和网络延迟假设使用时需要谨慎。4. 从 Follower 读取可以直接从 Follower 读取但可能读到旧数据客户端写入成功 立即从 Follower 读取 Follower 还没同步完成 返回旧值这种方式称为Stale Read陈旧读取适合对实时一致性要求不高的场景。七、Raft 日志不能无限增长如果所有历史操作永久保存在日志中日志会越来越大PUT a 1 PUT a 2 PUT a 3 PUT b 4 DELETE c ...因此需要快照机制。1. 创建快照当日志达到一定大小时节点将当前状态机状态保存成快照Snapshot a → 3 b → 4然后删除快照之前的旧日志旧日志1 2 3 4 5 6 7 8 9 快照包含1 到 7 保留日志8 92. 新节点加入如果新节点落后太多Leader 不必发送几百万条日志而是直接发送快照Leader ──InstallSnapshot── 新节点新节点恢复快照后再同步快照之后的少量日志。3. 快照和日志的关系可以理解为快照 某个时间点的完整状态 日志 从这个时间点之后的增量操作恢复数据时加载快照 ↓ 重放快照之后的日志 ↓ 得到最新状态八、Raft 如何实现水平扩展一个 Raft 集群通常不应该把全部数据放进一个无限增长的 Raft 日志组否则所有写入都要经过同一个 Leader吞吐量会受限制。更常见的方式是多个 Raft Group例如按照 Key 分片Raft Group 1user:0 ~ user:999 Raft Group 2user:1000 ~ user:1999 Raft Group 3order:0 ~ order:999每个 Raft Group 有自己的 Leader 和副本Group 1A、B、C Group 2D、E、F Group 3G、H、I请求路由层根据 Key 找到对应的 Groupuser:1001 → Group 2 → Group 2 的 Leader这样多个 Group 可以并行处理请求要注意Raft 负责副本一致性 分片负责容量和吞吐扩展 路由层负责把请求送到正确的 Raft GroupRaft 本身并不自动解决数据分片九、一个完整流程图客户端 │ │ PUT user:1001 张三 ▼ 路由层 │ │ 根据 Key 找到 Raft Group ▼ Group Leader │ ├── 追加日志到本地 WAL │ ├── AppendEntries → Follower 1 │ ├── AppendEntries → Follower 2 │ ├── 获得多数派确认 │ ├── 更新 commitIndex │ ├── 应用到本地 KV 状态机 │ ├── 通知 Followers 提交 │ └── 返回客户端成功

相关新闻

2026/7/24 5:38:31

基于YOLOv10的蔬果新鲜度检测系统开发实践

1. 项目概述这个项目实现了一个基于YOLOv10目标检测模型的蔬菜水果新鲜度检测系统。作为一个计算机视觉领域的实用项目,它能够通过图像分析技术自动判断蔬果的新鲜程度,支持单张图片、视频文件以及摄像头实时画面三种输入方式。整套系统采用PyTorch框架开…

2026/7/24 5:38:31

鸿蒙应用集成Unreal Engine:高性能3D渲染与跨平台开发实践

1. 项目概述:当鸿蒙遇见Unreal Engine最近在捣鼓鸿蒙应用开发,发现一个挺有意思的方向:把Unreal Engine(UE)集成进来。这可不是简单的“把游戏引擎塞进手机系统”,而是一个关于如何将顶级的实时3D渲染与交互…

2026/7/24 5:38:31

YOLOv11与旋转检测在遥感图像识别中的优化实践

1. 项目背景与核心价值遥感图像识别技术正在经历从传统方法向深度学习的范式转移。去年参与某省自然资源调查项目时,我们团队发现传统遥感解译方法在复杂场景下的平均准确率不足65%,而人工复核需要消耗70%的项目时间。正是在这种背景下,我们决…

2026/7/24 10:13:46

多模态常用数据集

一、开放程度标记 标记含义典型情况A:直接开放媒体文件和标注均可公开下载CLEVR、ScienceQA、部分合成数据集B:申请开放免费注册、申请或签署协议后获取Ego4D、MIMIC-CXR、ScanNet相关数据C:链接开放只发布URL、元数据或下载脚本LAION、CC3M…

2026/7/24 10:13:46

TPS99000S-Q1光反馈系统设计:跨阻放大器配置与PCB布局实战指南

1. 项目概述与核心价值在汽车照明和投影显示这类对光品质要求极为苛刻的领域,LED的亮度和色彩稳定性直接决定了最终产品的用户体验和性能等级。无论是HUD抬头显示需要精准投射到风挡上的信息,还是数字大灯投射出的清晰路面标识,其背后都离不开…

2026/7/24 10:13:46

ADS131M03同步采样ADC原理与实战:寄存器配置与多设备同步详解

1. 项目概述:为什么ADC同步如此重要?在电力计量、工业自动化或者多通道数据采集系统里,我们常常会遇到一个头疼的问题:几个ADC通道采集到的数据,在时间上对不上。比如,你想同时测量三相电压和电流来计算瞬时…

2026/7/24 10:13:46

AI技术如何提升学术专著写作效率

1. AI专著撰写核心痛点解析写学术专著向来是件耗时费力的工程,尤其是AI领域的技术专著,既要保证理论深度又要兼顾前沿技术覆盖。我去年完成《多智能体强化学习实战》一书时,光是整理参考文献就花了三周时间。传统写作流程中,文献管…

2026/7/24 10:13:46

以太网PHY接口深度解析:从MII、RGMII到SGMII的设计与实战

1. 项目概述:为什么我们需要关注以太网PHY接口?搞硬件设计,尤其是网络设备,绕不开的一个核心组件就是以太网PHY。它就像是网络世界的“翻译官”,一头连着数字世界的MAC控制器,另一头连着模拟世界的网线或光…

2026/7/23 12:54:51

Unity与Python本地通信:基于Flask的跨语言数据交换实战

1. 项目概述:为什么我们需要一个本地通信服务器?在游戏开发、数字孪生、仿真训练等众多领域,Unity作为强大的实时3D内容创作平台,其核心逻辑通常由C#驱动。然而,当我们需要进行复杂的数据分析、机器学习推理、科学计算…

2026/7/24 0:03:10

不同品牌斜齿行星减速机如何替换?以PX与PAG系列为例

不同品牌斜齿行星减速机如何替换?以 PX 与 PAG 系列为例 一、系列对应不等于型号直接互换 PX 与 PAG 都属于斜齿、方法兰、输出轴式精密行星减速机,结构形式和应用方向具有对应关系。 原设备使用PX系列时,可以优先从PAG系列中寻找替换型号。但…

2026/7/24 0:03:10

jdk8 把list 扁平化成String 多个以逗号分隔

在 JDK 8 中&#xff0c;将 List 扁平化为以逗号分隔的 String&#xff0c;有几种非常简洁且高效的方法。&#x1f680; 推荐方案&#xff1a;使用 Collectors.joining()这是最标准的 Java 8 写法&#xff0c;适用于 List<String>。javaimport java.util.stream.Collecto…

2026/7/24 0:03:10

java 两个 long id 怎么合并成一个long id 并且不重复

“把两个 Long ID 合并成一个唯一的 Long ID&#xff0c;且保证不重复”这个需求&#xff0c;在 Java 里直接做数学上的“完美合并”是不可能的。因为两个 Long&#xff08;各 64 位&#xff09;要合并成一个 Long&#xff08;64 位&#xff09;&#xff0c;在信息论上是有损压…

2026/7/23 23:42:43

3个高效策略:快速掌握Axure中文界面配置

3个高效策略&#xff1a;快速掌握Axure中文界面配置 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包。支持 Axure 11、10、9。不定期更新。 项目地址: https://gitcode.com/gh_mirrors/ax/axure-cn 还在为Axure RP的英文界面感…