5Draft(五帝)测试报告
报告日期:2026-09-05 | 测试版本:commit 7818ff1 | 报告人:5Draft 团队
项目地址:https://gitee.com/galaxy_0/5d-raft.git
一、测试概述
1.1 测试目的
验证 5Draft 预测性健康感知 Raft 的核心功能正确性,覆盖:
- 趋势分析(劣化检测、毛刺过滤、预测钳制)
- 消息编解码(精度、脏数据降级、边界处理)
- 主动调度(连续计数触发、分差门禁、乒乓抑制)
- 健康感知投票(Lease 有效择优 / 失效回退)
- HealthMonitor 真实 CPU 记账(线程注册/注销、负载下降/恢复)
- 仿真集群端到端禅让闭环
- 真实 NuRaft 集群上的主动禅让
- 全链路真实验证(无 Mock:真实集群 + 真实 CPU 压力触发禅让)
1.2 测试范围
| 层次 |
测试程序 |
验证目标 |
| 单元测试 |
unit_tests |
各模块函数级正确性 |
| 仿真集成 |
5draft_demo |
无网络依赖的闭环行为验证 |
| 真实集成 |
nuraft_demo |
真实 NuRaft 集群 + 混沌注入禅让 |
| 全链路验证 |
nuraft_real_test |
真实 CPU 压力触发禅让(无 Mock) |
1.3 测试环境
| 项目 |
值 |
| 操作系统 |
WSL2 Debian (kernel 6.18.33.2-microsoft-standard-WSL2) |
| 编译器 |
g++ 14.2.0 (Debian 14.2.0-19) |
| 构建工具 |
CMake 3.31.6 |
| 在线逻辑核数 |
36 |
| 构建类型 |
Release |
| 代码版本 |
commit 7818ff1 |
二、单元测试结果
程序 :./build/unit_tests | 退出码 :0 | 总用例数 :31 | 通过 :31 | 失败:0
2.1 趋势分析模块(5 项)
| 用例 |
结果 |
验证点 |
| Trend/持续劣化: 一阶导为负 |
✅ PASS |
健康分持续下降时 H′ < 0 |
| Trend/持续劣化: 预测分低于当前分 |
✅ PASS |
H_pred = H + 4·H′ < H |
| Trend/瞬态毛刺: 恢复后预测分不过度悲观 |
✅ PASS |
单周期暴跌后恢复,pred > 0.7 |
| Trend/瞬态毛刺: 当前分恢复 |
✅ PASS |
H 恢复至 0.9 |
| Trend/钳制: 预测分落在 0,1 |
✅ PASS |
快速上升场景 pred 被钳制 |
2.2 消息编解码模块(11 项)
| 用例 |
结果 |
验证点 |
| Codec/正常解码 |
✅ PASS |
"0.83,-0.02,0.003" 正确解析 |
| Codec/数值一致 |
✅ PASS |
全精度(setprecision 15)往返一致 |
| Codec/空串拒绝 |
✅ PASS |
空串返回 false |
| Codec/脏数据拒绝 |
✅ PASS |
"abc,def" 返回 false |
| Codec/负数 h 钳制为 0 |
✅ PASS |
h = -0.5 → 0.0 |
| Codec/超大 h 钳制为 1 |
✅ PASS |
h = 5.0 → 1.0 |
| Codec/多余数值字段取前 3 个 |
✅ PASS |
"0.5,0.1,0.01,0.9" 取前 3 |
| Codec/仅 2 个字段拒绝 |
✅ PASS |
"0.5,0.1" 返回 false |
| Codec/空格容忍 |
✅ PASS |
" 0.5 , 0.1 , 0.01 " 解析成功 |
| Codec/非数字多余字段拒绝 |
✅ PASS |
"0.5,0.1,0.01,extra" 返回 false |
| Codec/NaN 拒绝 |
✅ PASS |
"nan,0.0,0.0" 返回 false |
| Codec/Inf 拒绝 |
✅ PASS |
"inf,0.0,0.0" 返回 false |
2.3 主动调度模块(4 项)
| 用例 |
结果 |
验证点 |
| Scheduler/连续2次不触发 |
✅ PASS |
consecutive_required=3,2 次不触发 |
| Scheduler/连续3次触发禅让给最优 Peer |
✅ PASS |
第 3 次触发 transfer_leadership(2) |
| Scheduler/分差不足不触发禅让 |
✅ PASS |
diff=0.02 < threshold=0.08,不触发 |
| Scheduler/差值回落重置计数(乒乓抑制) |
✅ PASS |
差值回落后计数清零,不再触发 |
2.4 投票决策模块(3 项)
| 用例 |
结果 |
验证点 |
| Vote/Lease 有效且候选人更差 → 拒绝 |
✅ PASS |
候选人 pred < Leader pred + threshold |
| Vote/Lease 有效但候选人显著更优 → 授票 |
✅ PASS |
diff=0.10 > 0.08,授票 |
| Vote/Lease 失效 → 回退标准 Raft 授票 |
✅ PASS |
lease_valid=false 时直接授票 |
2.5 HealthMonitor 模块(4 项)
| 用例 |
结果 |
验证点 |
| HealthMonitor/RTT 200ms 评分归一化到 0 |
✅ PASS |
net 权重 1.0,RTT=200ms → score=0 → H<0.1 |
| HealthMonitor/Reset 清零导数 |
✅ PASS |
Reset 后 h1=h2=0 |
| HealthMonitor/注册负载线程后 CPU 可用率真实下降 |
✅ PASS |
满核施压后 CPU < 0.80 |
| HealthMonitor/卸载负载后 CPU 可用率恢复 |
✅ PASS |
卸载后 CPU > 施压期 |
2.6 仿真集群端到端(3 项)
| 用例 |
结果 |
验证点 |
| SimCluster/端到端: 劣化 Leader 主动禅让 |
✅ PASS |
Leader 0 → 1,pred diff=0.56 |
| SimCluster/端到端: 禅让给最健康节点 node1 |
✅ PASS |
禅让目标为预测分最高的节点 |
| SimCluster/端到端: 禅让后任期递增 |
✅ PASS |
term 1 → 2 |
2.7 单元测试汇总
==== 31 checks, 0 failures ====
三、仿真集群演示结果
程序 :./build/5draft_demo | 退出码:0
场景 1:Leader 缓慢劣化(内存泄漏式)
- 输入:Leader node0 健康分从 0.90 逐步下降至 0.35,Follower node1=0.90、node2=0.85
- 预期:连续 3 个周期预测分显著落后 → 主动禅让
- 实际 :
5Draft: Leader 0 主动禅让给 1 (pred diff=0.56, term=1)
- 结论:✅ 成功触发预测性禅让,新 Leader = node1
场景 2:Leader 瞬态抖动(单周期暴跌后恢复)
- 输入:Leader 健康分单周期跌至 0.30 后立即恢复 0.90
- 预期:二阶导数识别为触底反弹(H″>0),连续计数不累积 → 不切换
- 结论:✅ 瞬态抖动被二阶导数 + 连续计数抑制,未发生切换
场景 3:RequestVote 健康感知
| 子场景 |
预期 |
实际 |
| Lease 有效,弱候选人挑战健康 Leader |
拒绝 |
✅ 拒绝 |
| Lease 有效,强候选人挑战劣化 Leader |
授票 |
✅ 授票 |
| Lease 失效(Leader 失联) |
回退标准 Raft 授票 |
✅ 授票 |
结论:✅ 全部符合预期
四、真实 NuRaft 集群演示结果
程序 :./build/nuraft_demo | 退出码:0 | 端口:25011~25013
测试过程
- 启动 3 个真实 NuRaft 节点,组阁成功:初始 Leader = node1,term=1
- 对 Leader node1 注入混沌劣化健康:h=0.35, h′=-0.05, pred=0.15
- Follower 保持真实健康,心跳 meta 持续交换
关键日志(真实输出)
[INFO] 集群就绪,初始 Leader = node1,term=1
[WARN] 5Draft node 1 进入混沌演练:自身健康被注入为 h=0.35 h'=-0.05 pred=0.15
[INFO] 已对 Leader node1 注入劣化健康(pred≈0.15),等待 5Draft 主动禅让...
[INFO] 5Draft: Leader 1 主动禅让给 3 (pred diff=0.723977, term=1)
[WARN] [NuRaft] Server is elected as leader for term 2
[INFO] ===== 禅让成功:Leader node1 → node3(新 term=2)[PASS]
验证点
| 检查项 |
结果 |
| 真实 NuRaft 集群组阁成功 |
✅ |
| 混沌注入劣化健康后 Follower 预测分更优 |
✅ (diff=0.724) |
| ActiveScheduler 调用 yield_leadership 禅让 |
✅ |
| 新 Leader node3 当选,term 1→2,三节点一致 |
✅ |
五、全链路真实验证结果(无 Mock)
程序 :./build/nuraft_real_test | 退出码 :0 | 核心特点:不注入任何健康数据,全程真实采集
5.1 验证方案
#mermaid-svg-NT3SkVgyYoRi2QT6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NT3SkVgyYoRi2QT6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NT3SkVgyYoRi2QT6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NT3SkVgyYoRi2QT6 .error-icon{fill:#552222;}#mermaid-svg-NT3SkVgyYoRi2QT6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NT3SkVgyYoRi2QT6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NT3SkVgyYoRi2QT6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NT3SkVgyYoRi2QT6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NT3SkVgyYoRi2QT6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NT3SkVgyYoRi2QT6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NT3SkVgyYoRi2QT6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NT3SkVgyYoRi2QT6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NT3SkVgyYoRi2QT6 .marker.cross{stroke:#333333;}#mermaid-svg-NT3SkVgyYoRi2QT6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NT3SkVgyYoRi2QT6 p{margin:0;}#mermaid-svg-NT3SkVgyYoRi2QT6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NT3SkVgyYoRi2QT6 .cluster-label text{fill:#333;}#mermaid-svg-NT3SkVgyYoRi2QT6 .cluster-label span{color:#333;}#mermaid-svg-NT3SkVgyYoRi2QT6 .cluster-label span p{background-color:transparent;}#mermaid-svg-NT3SkVgyYoRi2QT6 .label text,#mermaid-svg-NT3SkVgyYoRi2QT6 span{fill:#333;color:#333;}#mermaid-svg-NT3SkVgyYoRi2QT6 .node rect,#mermaid-svg-NT3SkVgyYoRi2QT6 .node circle,#mermaid-svg-NT3SkVgyYoRi2QT6 .node ellipse,#mermaid-svg-NT3SkVgyYoRi2QT6 .node polygon,#mermaid-svg-NT3SkVgyYoRi2QT6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NT3SkVgyYoRi2QT6 .rough-node .label text,#mermaid-svg-NT3SkVgyYoRi2QT6 .node .label text,#mermaid-svg-NT3SkVgyYoRi2QT6 .image-shape .label,#mermaid-svg-NT3SkVgyYoRi2QT6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-NT3SkVgyYoRi2QT6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NT3SkVgyYoRi2QT6 .rough-node .label,#mermaid-svg-NT3SkVgyYoRi2QT6 .node .label,#mermaid-svg-NT3SkVgyYoRi2QT6 .image-shape .label,#mermaid-svg-NT3SkVgyYoRi2QT6 .icon-shape .label{text-align:center;}#mermaid-svg-NT3SkVgyYoRi2QT6 .node.clickable{cursor:pointer;}#mermaid-svg-NT3SkVgyYoRi2QT6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NT3SkVgyYoRi2QT6 .arrowheadPath{fill:#333333;}#mermaid-svg-NT3SkVgyYoRi2QT6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NT3SkVgyYoRi2QT6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NT3SkVgyYoRi2QT6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NT3SkVgyYoRi2QT6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NT3SkVgyYoRi2QT6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NT3SkVgyYoRi2QT6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NT3SkVgyYoRi2QT6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NT3SkVgyYoRi2QT6 .cluster text{fill:#333;}#mermaid-svg-NT3SkVgyYoRi2QT6 .cluster span{color:#333;}#mermaid-svg-NT3SkVgyYoRi2QT6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NT3SkVgyYoRi2QT6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NT3SkVgyYoRi2QT6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-NT3SkVgyYoRi2QT6 .icon-shape,#mermaid-svg-NT3SkVgyYoRi2QT6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NT3SkVgyYoRi2QT6 .icon-shape p,#mermaid-svg-NT3SkVgyYoRi2QT6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NT3SkVgyYoRi2QT6 .icon-shape .label rect,#mermaid-svg-NT3SkVgyYoRi2QT6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NT3SkVgyYoRi2QT6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NT3SkVgyYoRi2QT6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NT3SkVgyYoRi2QT6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 真实 3 节点 NuRaft 集群
基线期 3s
真实采集 CPU/内存
对 Leader 施加真实 CPU 压力
36 线程 × 85% 占空
Leader CPU 真实下滑
内核记账 /proc/self/task
心跳 meta 携带真实健康
ActiveScheduler 判定
pred diff=0.82
yield_leadership 主动禅让
卸载压力
旧 Leader CPU 真实恢复
5.2 实测数据时间线
| 阶段 |
时刻 |
node1 角色 |
node1 H |
node1 H′ |
node1 pred |
node1 CPU |
说明 |
| 基线 |
t=3.00s |
Leader |
0.93 |
0.00 |
0.93 |
1.00 |
三节点健康一致 |
| 施压 |
t=0.50s |
Follower |
0.51 |
-0.09 |
0.13 |
0.15 |
已禅让,node2 为新 Leader |
| 恢复 |
t=4.00s |
Follower |
0.93 |
0.00 |
0.93 |
1.00 |
压力卸载后恢复 |
施压后仅 0.5s(一个采样间隔)内即完成禅让,远早于任何 election timeout。
5.3 六项检查结果
| 检查项 |
通过标准 |
实测值 |
结果 |
| 检查1:集群组阁 |
Leader 选出,term=1 |
node1, term=1 |
✅ PASS |
| 检查2:基线健康 |
CPU > 0.9, H > 0.75 |
CPU=1.00, H=0.934 |
✅ PASS |
| 检查3:施压降载 |
CPU < 0.5 |
CPU=0.153(内核记账) |
✅ PASS |
| 检查4:预测下滑 |
pred 下降 > 0.15 |
0.934 → 0.130 |
✅ PASS |
| 检查5:主动禅让 |
Leader 切换,term 递增 |
node1→node2, term 1→2 |
✅ PASS |
| 检查6:卸载恢复 |
CPU > 0.9 |
CPU=1.00 |
✅ PASS |
5.4 真实关键日志
[INFO] 本机在线逻辑核数:36,将对 Leader 施加 36 线程 × 85% 占空比的真实 CPU 压力
[INFO] [检查2] 基线期 Leader 真实 CPU 可用率=1、健康分 H=0.933951 [PASS]
[INFO] 5Draft: Leader 1 主动禅让给 2 (pred diff=0.820605, term=1)
[INFO] [检查3] 施压后 Leader 真实 CPU 可用率最低降至 0.15305(内核记账,非注入) [PASS]
[INFO] [检查4] Leader 真实预测健康分最低降至 0.13001(基线 H=0.933951) [PASS]
[INFO] [检查5] 主动禅让发生:node1 → node2,term 1 → 2 [PASS]
[INFO] [检查6] 卸载后旧 Leader 真实 CPU 可用率恢复至 1 [PASS]
[INFO] ===== 真实验证结论:5/5 项检查通过,整体 [PASS] =====
六、测试覆盖率分析
6.1 模块覆盖
| 模块 |
单元测试 |
仿真验证 |
真实集群验证 |
全链路验证 |
| TrendAnalyzer |
✅ 5 项 |
✅ |
--- |
--- |
| MessageExtension |
✅ 11 项 |
✅(编解码链路) |
✅ |
✅ |
| ActiveScheduler |
✅ 4 项 |
✅ |
✅ |
✅ |
| VoteDecider |
✅ 3 项 |
✅ |
✅ |
✅ |
| HealthMonitor |
✅ 4 项 |
--- |
--- |
✅(真实 CPU) |
| FiveDraftNode |
--- |
✅ |
✅ |
✅ |
| NuraftNode |
--- |
--- |
✅ |
✅ |
| IRaftServer 适配 |
--- |
✅ |
✅ |
✅ |
6.2 功能点覆盖
| 功能点 |
覆盖情况 |
| 健康分加权计算 |
✅ HealthMonitor 权重测试 |
| 一阶/二阶导数趋势分析 |
✅ TrendAnalyzer 测试 |
| 预测分外推与钳制 |
✅ TrendAnalyzer 钳制测试 |
| 消息编解码精度 |
✅ setprecision 15 往返一致 |
| 脏数据/边界数据降级 |
✅ 11 项编解码边界测试 |
| 连续计数防抖 |
✅ Scheduler 连续 2/3 次测试 |
| 分差阈值门禁 |
✅ Scheduler 分差不足测试 |
| 乒乓抑制 |
✅ Scheduler 差值回落重置测试 |
| Lease 有效择优投票 |
✅ VoteDecider 测试 |
| Lease 失效回退 Raft |
✅ VoteDecider 测试 |
| 真实 CPU 内核记账 |
✅ HealthMonitor 线程注册测试 + 全链路验证 |
| 负载卸载后恢复 |
✅ HealthMonitor 恢复测试 + 全链路验证 |
| 主动禅让(仿真) |
✅ SimCluster 端到端 |
| 主动禅让(真实集群,注入) |
✅ nuraft_demo |
| 主动禅让(真实集群,无 Mock) |
✅ nuraft_real_test |
| 禅让后任期递增 |
✅ 仿真 + 真实集群 |
| 禅让目标为最健康节点 |
✅ SimCluster |
七、已知限制与未覆盖项
| 项目 |
说明 |
| macOS 健康采集 |
HealthMonitor 在 macOS 上未实现平台接口,返回中性默认值并降级;未在 macOS 上测试 |
| 网络 RTT 维度 |
全链路验证中网络分固定为 0.8(loopback),未覆盖真实网络抖动场景 |
| 大规模集群 |
仅在 3 节点集群上验证,未测试 >5 节点场景 |
| 长时稳定性 |
单次运行约 30~60 秒,未做 24h+ 长时稳定性测试 |
| 并发压力测试 |
ActiveScheduler 多线程并发调用 UpdatePeerHealth + OnLeaderTick 的压力测试未覆盖 |
| 成员变更场景 |
未测试禅让过程中伴随 add_srv/remove_srv 的并发场景 |
八、结论
8.1 总体结果
| 测试层次 |
用例数 |
通过 |
失败 |
通过率 |
| 单元测试 |
31 |
31 |
0 |
100% |
| 仿真集群演示 |
3 场景 |
3 |
0 |
100% |
| 真实 NuRaft 演示 |
1 |
1 |
0 |
100% |
| 全链路真实验证 |
6 项检查 |
6 |
0 |
100% |
| 合计 |
41 |
41 |
0 |
100% |
8.2 核心结论
- 趋势分析正确:一阶导/二阶导能区分持续劣化与瞬态抖动,预测分钳制在 0,1
- 编解码健壮:全精度序列化,脏数据/越界/NaN/Inf 均正确降级
- 主动调度有效:连续计数 + 分差阈值 + 乒乓抑制三层防抖,仅在持续显著劣化时禅让
- 投票安全:Lease 失效自动回退标准 Raft,不破坏 Raft Safety
- 真实 CPU 记账可信:注册线程后 CPU 可用率真实下降(1.00→0.15),卸载后恢复(→1.00)
- 全链路禅让成功:真实 36 核 CPU 压力下,Leader 在 500ms 内主动禅让,term 1→2,无 Mock
8.3 建议
- 补充 macOS 平台健康采集实现
- 增加 >5 节点集群与成员变更并发的集成测试
- 增加长时稳定性测试(24h+)
- 增加 ActiveScheduler 多线程并发压力测试
- 在真实网络环境(非 loopback)下验证网络 RTT 维度的健康感知投票
附录:测试复现命令
# 构建
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
# 1. 单元测试
./build/unit_tests
# 2. 仿真集群演示
./build/5draft_demo
# 3. 真实 NuRaft 集群演示(混沌注入)
./build/nuraft_demo
# 4. 全链路真实验证(无 Mock,推荐)
./build/nuraft_real_test