尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Raft如何做到线性一致性

Raft如何做到线性一致性
📅 发布时间:2026/7/31 9:57:50

一、为什么需要请求去重

考虑一次Append("name", "A"):

1. 客户端发送 Append 请求 2. Leader 将请求提交到 Raft 日志 3. KV 状态机执行成功,name 变成 "A" 4. 服务端返回响应 5. 但响应在网络中丢失 6. 客户端超时,重新发送同一个请求

如果服务端无法识别重试,Append会再次执行:

第一次执行:name = "A" 第二次执行:name = "AA"

客户端实际上只请求了一次,状态机却执行了两次,这会破坏客户端期望的语义。因此必须给每个逻辑请求一个唯一身份。

二、ClientId和CommandId分别是什么

图中每个客户端维护两个重要信息。

ClientId表示客户端身份,例如:

客户端A:ClientId = C1001 客户端B:ClientId = C2001

项目中,Clerk创建时会生成客户端 ID,并将请求序号初始化为 0:

Clerk::Clerk() : m_clientId(Uuid()), m_requestId(0), m_recentLeaderId(0) {}

CommandId在项目中叫RequestId,表示该客户端发出的第几个逻辑请求:

(C1001, 1) -> Put("x", "A") (C1001, 2) -> Append("x", "B") (C1001, 3) -> Get("x")

每次产生一个新的逻辑请求时,客户端将m_requestId加一:

m_requestId++; auto requestId = m_requestId;

三、为什么必须是二元组

唯一请求标识是:

(ClientId, RequestId)

不能只使用ClientId,因为一个客户端会发送很多请求。

不能只使用RequestId,因为不同客户端都可能存在RequestId = 1:

(C1001, 1) (C2001, 1)

这显然是两个不同请求。

因此,ClientId + RequestId共同构成一个类似“幂等键”的东西。

项目中的 Raft 命令Op包含这两个字段:

class Op { public: std::string Operation; std::string Key; std::string Value; std::string ClientId; int RequestId; };

四、重试时 RequestId 不能增加

这是图中非常关键的一点。

新的逻辑请求才增加RequestId:

第一次业务操作:RequestId = 10 第二次业务操作:RequestId = 11

同一个请求因为超时而重试时,必须继续使用原来的 ID:

发送请求:(C1001, 11) 超时重试:(C1001, 11) 再次重试:(C1001, 11)

不能变成 12、13,否则服务端会认为它们是三个不同请求。

这个项目做得是对的:requestId在进入重试循环之前保存,循环中始终复用它:

m_requestId++; auto requestId = m_requestId; while (true) { args.set_requestid(requestId); // 向不同服务器重试 }

五、服务端如何判断重复请求

每个 KV Server 保存:

std::unordered_map<std::string, int> m_lastRequestId;

含义是:

ClientId -> 该客户端已经执行过的最大 RequestId

例如:

C1001 -> 15 C2001 -> 8

表示C1001的 1 到 15 号请求已经处理过。

判断重复请求的逻辑是:

return RequestId <= m_lastRequestId[ClientId];

假设:

m_lastRequestId["C1001"] = 15

那么:

请求 16:新请求,可以执行 请求 15:重复请求,不能再次执行 请求 14:旧请求,也不能再次执行

这就是图中“只执行递增的 CommandId”的具体含义。

六、完整的丢包重试过程

客户端 C7 发送: (C7, 12, Append("x", "A")) Leader 写入 Raft 日志 ↓ 多数节点复制成功 ↓ 日志被提交 ↓ KV 状态机执行 Append ↓ lastRequestId[C7] = 12 ↓ 响应丢失 ↓ 客户端重试同一个 (C7, 12) ↓ 服务端发现 12 <= lastRequestId[C7] ↓ 不再执行 Append,直接返回成功

项目在 Raft 提交命令后,调用ifRequestDuplicate(),只有非重复请求才操作 KV 数据:

if (!ifRequestDuplicate(op.ClientId, op.RequestId)) { if (op.Operation == "Put") { ExecutePutOpOnKVDB(op); } if (op.Operation == "Append") { ExecuteAppendOpOnKVDB(op); } }

执行 KV 操作时还会更新:

m_lastRequestId[op.ClientId] = op.RequestId;

KV 状态和去重信息在同一把互斥锁保护下更新,这一点很重要。

七、它与线性一致性的关系

线性一致性要求每个操作看起来都在某一个瞬间原子完成,而且不能违反真实时间顺序。

Raft主要解决:

所有节点按照相同的日志顺序执行命令

请求去重解决:

客户端的一次逻辑操作,即使多次重试,也只产生一次状态变化

二者缺一不可。

例如没有去重时,Raft可能完全正确地提交两条相同的Append日志。Raft不知道它们是同一次请求的重试,因为对 Raft 来说,它们只是两条合法命令。

所以更准确地说:

Raft提供一致的提交顺序,ClientId + RequestId提供重试情况下的至多一次执行语义,二者共同支撑线性一致的客户端体验。

网络层无法真正保证消息“只发送一次”,这里只能保证重复消息不会导致状态机重复生效。

相关新闻

  • 村镇微能网建设提速:如何通过数智化运营释放农村能源长期价值?
  • QQ空间数据备份完整教程:三步找回全部历史记录的终极指南
  • DHCP协议解析:设备如何零配置自动获取IP地址

最新新闻

  • 2026 年至今,泽普可靠的挖掘机租赁公司哪家好,工地上能省出万元成本的狠招,竟藏在这台大家伙身上-富喜工程机械租赁 - 实业推荐官【官方】
  • 连锁餐饮设备远程运维:向日葵如何帮成长型连锁管好100家门店
  • 2026年搬家旧衣服怎么处理换钱?上门回收最高0.8元/公斤,一文说清3种变现方式 - 快递物流资讯
  • 没有实习经历怎么准备项目:5步搞定面试级项目 - 品牌品鉴馆
  • VMware虚拟机搭建VxWorks 6.9开发环境全攻略
  • 2026 西安装修公司推荐五家口碑实力品牌盘点 实力品牌全解析 - 装修大知识

日新闻

  • 7步掌握KMS智能激活工具:Windows和Office永久激活完整方案
  • 如何在Windows上运行iOS应用:ipasim跨平台模拟器终极指南
  • 2026年重庆工伤赔偿律师口碑推荐:洪家木律师用专业赢得信赖 - 本地品牌推荐

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号