Raft协议实现数据的分布式存储

发布时间:2026/11/5 20:21:45

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/11/5 20:21:45

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

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

2026/11/5 20:21:44

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

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

2026/11/4 15:44:31

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

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

2026/11/5 16:51:33

EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被…

2026/11/5 16:26:13

Rust原来很简单二:所有权系统

Part 2 核心篇 —— 所有权系统 本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理 6…

2026/11/5 15:00:49

基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑