当前位置: 首页 > news >正文

基于Raft分布式Kv存储:leaderHeartBeatTicker

源码里的准确名字是leaderHearBeatTicker()。它是Leader 的周期性调度器

控制什么时候启动下一轮心跳或日志复制;真正构造AppendEntries、发送快照和处理各节点复制进度的是doHeartBeat()

Raft 要求 Leader 定期向所有 Follower 发送AppendEntries;没有新日志时它就是空心跳,用来防止 Follower 选举超时。

整体结构

源码可以简化成:

void Raft::leaderHearBeatTicker() { while (true) { while (m_status != Leader) { sleep(HeartBeatTimeout); } lock(); wakeTime = now(); remaining = HeartBeatTimeout + m_lastResetHearBeatTime - wakeTime; unlock(); if (remaining > 1ms) { sleep(remaining); } if (heartbeat_was_reset_after(wakeTime)) { continue; } doHeartBeat(); } }

项目将心跳间隔配置为25ms,选举超时随机范围配置为300~500ms。也就是说,在正常情况下,一个选举超时区间内大约有 12~20 次心跳机会。

一、最外层无限循环

while (true)

Ticker 与 Raft 节点生命周期一致。它不会完成一次心跳就退出,而是永久执行:

等待成为 Leader → 等到下一次心跳截止时间 → 调用 doHeartBeat → 重新计算下一次截止时间

节点可能经历:

Follower → Candidate → Leader → Follower → Leader

所以 Ticker 不能只在第一次成为 Leader 时运行一次。

二、非 Leader 时轮询等待

while (m_status != Leader) { usleep(1000 * HeartBeatTimeout); }

Follower 和 Candidate 不应该主动发送 Leader 心跳,因此代码每隔25ms检查一次角色。

这里的换算是:

HeartBeatTimeout = 25ms usleep 参数单位 = 微秒 1000 × 25 = 25000μs = 25ms

如果节点一直是 Follower,这个循环会一直执行;当sendRequestVote()获得多数票并把状态改为 Leader,内部循环结束。

这是一种简单的轮询设计。代价是非 Leader 节点仍然每25ms醒来一次,更合适的工程实现通常会用条件变量,在角色变成 Leader 时主动唤醒 Ticker。

三、为什么不直接睡固定 25ms

代码没有简单地写:

sleep(25ms); doHeartBeat();

而是计算:

suitableSleepTime = milliseconds(HeartBeatTimeout) + m_lastResetHearBeatTime - wakeTime;

把它重新排列:

下一次截止时间 = 上次心跳时间 + 心跳间隔 还需等待时间 = 下一次截止时间 - 当前时间

即:

deadline = lastReset + 25ms remaining = deadline - now

这样心跳周期以“上次实际触发心跳的时间”为基准,不会简单地从 Ticker 本轮开始时间重新计算。

四、 正常时间示例

假设:

上次心跳时间:1000ms 心跳间隔: 25ms 当前时间: 1010ms

计算得到:

截止时间 = 1000 + 25 = 1025ms 剩余时间 = 1025 - 1010 = 15ms

Ticker 再睡15ms,然后在约1025ms调用:

doHeartBeat();

doHeartBeat()完成一轮请求构造和分发后,会执行:

m_lastResetHearBeatTime = now();

下一轮继续以这个新时间为起点。

五、 Ticker 已经晚了怎么办

假设:

上次心跳时间:1000ms 心跳截止时间:1025ms 当前时间: 1032ms

此时:

remaining = 25 + 1000 - 1032 = -7ms

代码只有在剩余时间大于约1ms时才睡眠;因此这里不再等待,直接调用doHeartBeat()

这可以处理:

线程调度延迟 互斥锁竞争 进程短暂停顿 前面的代码执行过久

但它不会补发错过的每一次心跳。例如错过了三个周期,也只会立即发送一轮,然后从新的发送时间重新计时。

六、wakeTime的作用

wakeTime是本轮计算开始时的时间快照:

wakeTime = now();

Ticker 睡眠期间,另一个路径可能已经调用了doHeartBeat()。例如:

Candidate 刚获得多数票 → sendRequestVote 将它改为 Leader → 启动线程立即调用 doHeartBeat

与此同时,leaderHearBeatTicker()也可能发现节点已经成为 Leader并开始计时。

如果另一个线程先发送心跳,它会更新:

m_lastResetHearBeatTime

Ticker 睡醒后检查:

m_lastResetHearBeatTime > wakeTime

如果成立,表示:

从我开始本轮等待之后,其他线程已经发送过一轮心跳。

于是执行:

continue;

重新根据最新心跳时间计算,而不是紧接着再发送一轮重复心跳。

七、 为什么叫“重置心跳计时器”

这里并没有真正的系统 Timer 对象,所谓“重置”只是更新时间戳:

m_lastResetHearBeatTime = now();

Ticker 每次根据这个时间戳计算截止时间,因此修改时间戳就等价于重新启动定时器:

旧截止时间 = 旧 lastReset + 25ms 新截止时间 = 新 lastReset + 25ms

这个设计和electionTimeOutTicker()很相似,只是:

选举超时:300~500ms,每轮随机 心跳间隔:固定25ms

八、doHeartBeat()会再次检查角色

Ticker 在等待期间,Leader 可能收到更高任期的响应并退回 Follower。

可能出现:

Ticker 看到 status == Leader → 开始睡眠 → 收到更高任期消息,变成 Follower → Ticker 睡醒 → 调用 doHeartBeat

doHeartBeat()自己会持锁并再次判断:

if (m_status == Leader) { // 才真正发送 }

因此,即使 Ticker 的角色判断已经过期,也不会以 Follower 身份构造 Leader RPC

九、 Ticker 触发的不只是空心跳

leaderHearBeatTicker()名字容易让人误以为它只发送空包。实际上,它调用的doHeartBeat()是整个复制调度入口。

对每个 Follower:

nextIndex <= lastSnapshotIncludeIndex → leaderSendSnapShot() → InstallSnapshot RPC nextIndex > lastSnapshotIncludeIndex → sendAppendEntries() → AppendEntries RPC

AppendEntries中:

entries 为空 → 纯心跳 entries 不为空 → 日志复制

所以这个 Ticker 同时驱动:

维持 Leader 权威 阻止 Follower 超时 复制新日志 修复日志冲突 推进 commitIndex 向严重落后的节点发送快照

十、时间从“发送”还是“回复”开始计算

源码在doHeartBeat()创建完各个发送线程之后就更新:

m_lastResetHearBeatTime = now();

它不会等待所有 Follower 回复。

因此心跳周期是:

本轮 RPC 开始分发 → 等待25ms → 下一轮 RPC 开始分发

而不是:

本轮所有RPC完成 → 等待25ms → 下一轮开始

这能避免一个慢 Follower 拖延其他节点的心跳,但也意味着 RPC 如果超过25ms,同一个 Follower 可能同时存在多轮尚未完成的AppendEntries。源码通过任期检查和max(matchIndex, ...)部分抵抗乱序回复,但旧失败响应仍可能让nextIndex回退,工程上更适合为每个 Follower 设置独立复制任务,保证单节点方向上的 RPC 串行化。(raw.githubusercontent.com)

http://www.jsqmd.com/news/1285593/

相关文章:

  • 全球蓄热瓷球市场竞争动态分析及未来前景展望报告2026年版
  • ARM汇编实战:从零点亮LED,深入理解GPIO与内存映射I/O
  • 学嵌入式和C语言编程|第七天:循环嵌套、循环辅助语句与数组
  • 豆包AI商业实战手册:33个副业变现案例解析
  • 十三层大一统模型下华夏六家核心同源体系——气化运化、天人混元完整论证
  • OpenClaw轻量级AI框架:模块化设计与跨平台部署
  • 2026 年现阶段,山东诚信的代做标书企业有哪些,投标小白如何靠它轻松中标? - 领域鉴赏官
  • 从零部署NAXSI:Nginx原生WAF模块的配置、白名单策略与实战调优
  • JavaScript乘性操作符原理与应用全解析
  • 壹遮安电动雨棚质量可靠吗 十大用户横评 所见即所得 - 工业推荐榜
  • VRTK:Unity VR开发核心交互框架深度解析与实战指南
  • HDMI 2.1核心技术解析:FRL、DSC、VRR与ALLM如何重塑视听体验
  • 生物素-冰片Biotin-Borneol|生物素 - 龙脑 冰片靶蛋白 Pull-down 垂钓筛选工具
  • 运算放大器反相与同相电路:原理、区别与工程选型指南
  • 永久保存你的QQ空间记忆:GetQzonehistory完整备份方案
  • 基于Halton序列的图像加密:原理、Matlab实现与相关性分析
  • Unity翻书插件Book-Page Curl Pro:从原理到实战的完全指南
  • 车载通信新选择:SEN协议原理、硬件设计与实战调优
  • 普中51单片机ISP下载全流程详解:从CH340驱动到STC-ISP操作避坑指南
  • 2026成都壹遮安户外用品服务口碑推荐强势出炉,零套路不踩坑,选购看这篇就够 - myqiye
  • Cortex-M内核PPA深度解析:性能、功耗与面积的嵌入式权衡艺术
  • 2026广东叛逆青少年成长学校口碑推荐 价格透明不踩坑 - 工业品网
  • 用Scratch编程可视化鸽巢原理:从数学公理到交互式模拟实验
  • GGUF格式与FLUX.2-dev模型本地部署实战指南
  • 深入解析ARM Cortex-M异常与中断:从原理到实战调试
  • 量子计算机:打破经典算力边界的未来计算
  • SOT-89-3封装LDO管脚定义陷阱:硬件工程师必知的封装兼容性问题
  • Python数据分析入门:Pandas安装与环境配置全攻略
  • 基于树莓派Pico与PIR传感器的智能感应吓人装置DIY全攻略
  • 开放式耳机什么牌子好用又实惠?盘点耳机排行榜前十名