5 节点边缘冗余方案(上):基于 aiRaft 的物联网高可用控制面设计

5 节点边缘冗余方案(上):基于 aiRaft 的物联网高可用控制面设计

在物联网边缘,最贵的不是算力,而是确定性。

设备分散、网络抖动、供电不稳、现场无人值守,这些是常态。中心云再强,也救不了一个已经断网的边缘现场。于是,越来越多系统开始把关键控制面下沉到边缘:本地选主、本地故障切换、本地状态同步、本地进程托管。

但边缘高可用不能只靠"多部署几个节点"。节点放错位置,一次机房断网就能全灭;没有维护余量,坏一台之后连升级都不敢做;没有人工干预窗口,自动化系统可能在异常时做出不可逆决策。

本文给出一个基于 aiRaft 的 5 节点边缘冗余方案:跨故障域部署、独立供电供网、独立 etcd、Learner 入网、人工确认门、带外管理,目标是在物联网边缘构建一个既能自动切换、又能为人为干预留出"时空间隙"的高可用控制面。

下篇将聚焦坏节点处理流程、日常运维与适合场景。


一、为什么是 5 节点?

Raft 集群的可用性取决于多数派。节点数不是越多越好,3、5、7 是常见选择。

集群规模 多数派 quorum 可容忍故障 坏 1 台后剩余 还能再停几台维护
3 节点 2 1 2 0
5 节点 3 2 4 1
7 节点 4 3 6 2

3 节点能扛一台,但坏一台后只剩刚好够多数派,没有任何维护余量。此时再停一台做升级,集群立刻不可用。

5 节点则不同:坏 1 台后还剩 4 台,quorum=3,你还能再停 1 台做更换、修复或滚动升级。坏 2 台后剩 3 台,刚好 quorum,服务还能跑,但不能再动第三台。

所以,如果希望"坏一台还能从容更换或修复",5 节点是可靠性、容错窗口和成本之间比较均衡的选择。


二、理论失效率:5 节点为什么明显更稳?

假设只有 voter 节点计入 quorum,单节点失效率为 (p),节点故障相互独立。集群失效率近似为:

P_{\\text{fail}} \\approx \\binom{N}{f+1}p\^{f+1}

其中 (f) 是可容忍故障数。于是:

  • 3 节点约 (3p^2)
  • 5 节点约 (10p^3)
  • 7 节点约 (35p^4)

下表是独立故障假设下的理论失效率:

voter 数 容忍故障 (p=0.1%) (p=1%) (p=5%) (p=10%)
3 1 0.0003% 0.0298% 0.725% 2.8%
5 2 约 0.000001% 0.000985% 0.1158% 0.856%
7 3 约 0 0.000034% 0.0194% 0.273%

换算成年不可用时间,假设一年 8760 小时:

voter 数 (p=0.1%) (p=1%) (p=5%) (p=10%)
3 1.58 分钟 2.61 小时 63.5 小时 245 小时
5 0.315 秒 5.18 分钟 10.15 小时 75.0 小时
7 0.0011 秒 10.8 秒 1.70 小时 23.9 小时

从理论上看,5 节点把失效率从 (p^2) 级降到 (p^3) 级,提升非常明显。7 节点更低,但收益递减,且 Raft 复制、选举延迟和运维复杂度都明显上升。

但必须强调:以上全部建立在"节点故障相互独立"的假设上。

如果 5 台机器放在同一个机房、同一路电、同一交换机、同一出口,那么一次断网或断电就能让它们同时失效。此时 3、5、7 节点没有本质区别。

所以,5 节点的可靠性有一个前提:跨故障域,并且独立供电、独立供网。


三、跨故障域与独立供电供网

推荐布局:2 + 2 + 1,跨三个故障域。
#mermaid-svg-GgKgXyW5L96GsdyV{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-GgKgXyW5L96GsdyV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GgKgXyW5L96GsdyV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GgKgXyW5L96GsdyV .error-icon{fill:#552222;}#mermaid-svg-GgKgXyW5L96GsdyV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GgKgXyW5L96GsdyV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GgKgXyW5L96GsdyV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GgKgXyW5L96GsdyV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GgKgXyW5L96GsdyV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GgKgXyW5L96GsdyV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GgKgXyW5L96GsdyV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GgKgXyW5L96GsdyV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GgKgXyW5L96GsdyV .marker.cross{stroke:#333333;}#mermaid-svg-GgKgXyW5L96GsdyV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GgKgXyW5L96GsdyV p{margin:0;}#mermaid-svg-GgKgXyW5L96GsdyV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GgKgXyW5L96GsdyV .cluster-label text{fill:#333;}#mermaid-svg-GgKgXyW5L96GsdyV .cluster-label span{color:#333;}#mermaid-svg-GgKgXyW5L96GsdyV .cluster-label span p{background-color:transparent;}#mermaid-svg-GgKgXyW5L96GsdyV .label text,#mermaid-svg-GgKgXyW5L96GsdyV span{fill:#333;color:#333;}#mermaid-svg-GgKgXyW5L96GsdyV .node rect,#mermaid-svg-GgKgXyW5L96GsdyV .node circle,#mermaid-svg-GgKgXyW5L96GsdyV .node ellipse,#mermaid-svg-GgKgXyW5L96GsdyV .node polygon,#mermaid-svg-GgKgXyW5L96GsdyV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GgKgXyW5L96GsdyV .rough-node .label text,#mermaid-svg-GgKgXyW5L96GsdyV .node .label text,#mermaid-svg-GgKgXyW5L96GsdyV .image-shape .label,#mermaid-svg-GgKgXyW5L96GsdyV .icon-shape .label{text-anchor:middle;}#mermaid-svg-GgKgXyW5L96GsdyV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GgKgXyW5L96GsdyV .rough-node .label,#mermaid-svg-GgKgXyW5L96GsdyV .node .label,#mermaid-svg-GgKgXyW5L96GsdyV .image-shape .label,#mermaid-svg-GgKgXyW5L96GsdyV .icon-shape .label{text-align:center;}#mermaid-svg-GgKgXyW5L96GsdyV .node.clickable{cursor:pointer;}#mermaid-svg-GgKgXyW5L96GsdyV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GgKgXyW5L96GsdyV .arrowheadPath{fill:#333333;}#mermaid-svg-GgKgXyW5L96GsdyV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GgKgXyW5L96GsdyV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GgKgXyW5L96GsdyV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GgKgXyW5L96GsdyV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GgKgXyW5L96GsdyV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GgKgXyW5L96GsdyV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GgKgXyW5L96GsdyV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GgKgXyW5L96GsdyV .cluster text{fill:#333;}#mermaid-svg-GgKgXyW5L96GsdyV .cluster span{color:#333;}#mermaid-svg-GgKgXyW5L96GsdyV 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-GgKgXyW5L96GsdyV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GgKgXyW5L96GsdyV rect.text{fill:none;stroke-width:0;}#mermaid-svg-GgKgXyW5L96GsdyV .icon-shape,#mermaid-svg-GgKgXyW5L96GsdyV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GgKgXyW5L96GsdyV .icon-shape p,#mermaid-svg-GgKgXyW5L96GsdyV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GgKgXyW5L96GsdyV .icon-shape .label rect,#mermaid-svg-GgKgXyW5L96GsdyV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GgKgXyW5L96GsdyV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GgKgXyW5L96GsdyV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GgKgXyW5L96GsdyV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 人为干预通道
故障域 C
故障域 B
故障域 A
独立 etcd 集群
etcd-1
etcd-2
etcd-3
节点 1

aiRaft voter
节点 2

aiRaft voter
节点 3

aiRaft voter
节点 4

aiRaft voter
节点 5

aiRaft voter
带外管理 BMC / 串口 / 4G
LeaderServer 扩展 API

/pause /resume /approve /yield

这个布局下:

  • 挂掉故障域 A(2 台),还剩 3 台,quorum=3,集群可用;
  • 挂掉故障域 C(1 台),还剩 4 台,集群可用,还有维护余量;
  • 每个故障域独立供电、独立供网,避免共因故障;
  • etcd 也跨故障域部署,避免 aiRaft 的 leader key、成员注册、Lease 租约、watch 兜底被单点拖死;
  • seed_endpoints 跨域配置,新节点 join 时不会因为某个域断网而找不到集群。

"独立供电、独立供网"不等于同机房双路电、双网线。真正的独立应尽量做到:

层级 能防什么 防不了什么
双电源、双网卡 单电源、单网卡、单端口 同一 PDU、同一 UPS、同一交换机
双 PDU、双交换机 单 PDU、单交换机 同一 UPS、同一核心交换机、同一出口
独立 UPS、独立运营商 单 UPS、单运营商、单上行 同一机房总电、总出口、火灾、水淹
独立市电、独立光缆路由、独立运营商 单路市电、单运营商、单物理路由 同一建筑、同一机房、地震、消防、误操作
跨机房 / 跨 AZ 机房级断电、断网、火灾、水淹 区域级灾难、同版本软件 bug、全局误操作

所以,5 节点 + 真独立供电 + 真独立供网 + 跨故障域,才能接近理论可靠性。同机房 5 节点只能防单机、单机架、单路电,防不了机房整体断电、断网、火灾、水淹。


四、方案总览

这个方案的控制面由 aiRaft 承担。aiRaft 是基于 5d-Raft / NuRaft 的分布式高可用管理守护进程,为 Kthena 等服务提供 Leader 选举、故障切换、健康检查与进程管理能力。

核心组件:

组件 作用
RaftManager 封装 5d-raft / NuRaft,负责选主、禅让、AddPeer/RemovePeer、Learner 提权
ClusterMembership 自动入网/注销、租约心跳、死节点收割、Learner 提权看门狗
ProcessManager fork/exec 受管子进程,监控、崩溃重启、SIGTERM 优雅退出
HealthChecker Lua 5.5 驱动,采集 loadavg + meminfo,计算健康分,触发禅让
EtcdClient etcd v3 HTTP API:KV、watch、Lease 租约
LeaderServer HTTP API:GET / 查 Leader,POST /probe 探测,POST /join 报名,POST /leave 注销
libalgo.so 健康检查算法动态库,运行时 dlopen 载入,与主程序解耦

在边缘场景中,aiRaft 负责回答三个问题:

  1. 现在谁是 Leader?
  2. 某个节点掉线了,要不要剔除?
  3. 新节点加入后,什么时候能获得竞选权?

关键特性包括:

  • Raft 共识选主:多节点自动选举 Leader;
  • 启动防双主:bootstrap 节点启动时探测其他 peers 的 LeaderServer,若已有 Leader 则不自举;
  • 预测性禅让:健康分连续低于阈值,Leader 主动让权,避免故障节点拖垮集群;
  • Learner 入网:新节点先以学习者身份加入,只同步日志、不投票不参选;日志追平后才提权;
  • 自动入网 / 注销 :上线 POST /join,下线 POST /leave;异常崩溃靠 etcd Lease 超时自动注销;
  • 子进程管理:仅 Leader 节点启动受管业务进程,崩溃自动重启,SIGTERM 优雅退出;
  • etcd 双重保障:Leader 信息写入 etcd,watch 作为 Raft 回调的兜底;
  • Lua 健康检查 :采集 /proc/loadavg/proc/meminfo 计算健康分,支持热更新脚本。

五、Learner 入网:换机不扰动选举

边缘现场最典型的运维动作是换机。旧设备坏了,新设备上线。如果新节点空日志直接成为 voter,可能扰动选举,甚至拖慢集群。

aiRaft 的 Learner 机制正好解决这个问题:

  1. 新节点以 mode=join 启动,配置 seed_endpoints
  2. 自动向 Leader 发起 POST /join
  3. Leader 调 addLearner,新节点以 Learner 身份加入;
  4. Learner 只从 Leader 同步日志,不投票、不参选;
  5. 日志追平 Leader 后,Leader 自动 flip_learner_flag 提权;
  6. 新节点获得投票和竞选权,正式成为 voter。

旧节点如果还能优雅退出,走 POST /leave;如果已经坏了,靠 etcd Lease 租约超时自动删除成员键,Leader 看门狗连续 reap_miss_count 次探测不到就自动 remove_srv 剔除。

这套机制让"坏一台,换一台"变得平滑:新节点先追日志,再提权,不打扰现有集群。


六、人为干预的"时空间隙"

边缘系统不能只靠自动化。有些决策需要人确认:切换、回滚、维护、剔除节点、重启关键进程。问题是,人需要时间,而系统不能一直等。

5 节点冗余 + 跨故障域,本质上是在制造"时空间隙":

维度 来源 作用
时间间隙 Raft 选举超时、quorum 容忍、健康检查连续阈值、预测性禅让、etcd Lease TTL、进程重启上限、优雅退出超时 故障后系统不瞬间崩溃,留出缓冲期
空间间隙 跨故障域、独立供电供网、多路径网络、带外管理、本地操作口 某些点失效时,其他点仍可服务,人还能到达可操作节点

在这个窗口里,系统可以进入"待人工确认"模式,而不是直接做不可逆动作。

可以在 LeaderServer 基础上扩展人工干预 API:

  • POST /pause:暂停关键写操作,进入只读/待确认模式;
  • POST /resume:人工确认后恢复;
  • POST /approve:对高风险动作做二次确认;
  • POST /yield:人工触发 Leader 禅让;
  • POST /maintenance:进入维护模式,禁止自动提权/自动重启;
  • POST /rollback:触发回滚或恢复快照。

配套要求:

  • 强认证、权限控制、审计日志、防重放;
  • 超时自动退出干预模式,避免人为忘记恢复;
  • 带外通道:BMC、串口、本地 Console、独立 4G/5G 链路;
  • 安全关键动作仍要自动兜底,不能全等人工。

一个典型闭环:

  1. 边缘节点检测异常:负载、内存、网络、温度、电源;
  2. 健康分下降,连续 N 次低于阈值;
  3. Leader 预测性禅让,或进入"待人工确认"模式;
  4. 系统仍保持多数派,基本服务降级运行;
  5. 通知运维,人工通过带外或 LeaderServer 介入;
  6. 人工确认切换、修复、回滚或替换节点;
  7. 新节点以 Learner 加入,日志追平后提权;
  8. 集群恢复,退出维护模式。

核心原则是:人工干预不能绕过 quorum,不能制造双主,不能破坏一致性。

人工只能在共识安全边界内暂停、确认、放行或回滚。


七、配置示例

节点 1 可以是 bootstrap 节点,节点 2~5 使用 mode=join,通过 seed_endpoints 跨域联系。

json 复制代码
{
  "node_id": 1,
  "node_name": "edge-node1",
  "raft": {
    "mode": "bootstrap",
    "host": "0.0.0.0",
    "port": 9000,
    "peers": [
      {"id": 1, "endpoint": "10.0.1.11:9000"},
      {"id": 2, "endpoint": "10.0.1.12:9000"},
      {"id": 3, "endpoint": "10.0.2.11:9000"},
      {"id": 4, "endpoint": "10.0.2.12:9000"},
      {"id": 5, "endpoint": "10.0.3.11:9000"}
    ]
  },
  "membership": {
    "enabled": true,
    "lease_ttl_sec": 10,
    "heartbeat_sec": 3,
    "reap_miss_count": 3,
    "member_key_prefix": "/arkos/kthena/members"
  },
  "leader_server": { "port": 9100 },
  "process": {
    "enabled": true,
    "name": "edge_controller",
    "binary_path": "/usr/local/bin/edge_controller",
    "args": ["--config", "/etc/edge/config.yaml"],
    "health_check_interval_s": 5,
    "auto_restart": true,
    "max_restart_count": 3
  },
  "health": {
    "check_interval_ms": 1000,
    "threshold_switch": 70,
    "consecutive_required": 3,
    "sources": ["loadavg", "meminfo"],
    "lua_script_path": "scripts/health_score.lua"
  },
  "etcd": {
    "endpoints": [
      "http://10.0.1.21:2379",
      "http://10.0.2.21:2379",
      "http://10.0.3.21:2379"
    ],
    "leader_key": "/arkos/kthena/leader",
    "snapshot_key": "/arkos/kthena/state/last_snapshot",
    "standby_key": "/arkos/kthena/standby_nodes"
  },
  "log": {
    "level": "info",
    "path": "/var/log/airaft.log"
  }
}

节点 2~5 使用类似配置,但 mode 改为 join,并提供 seed_endpoints

json 复制代码
"raft": {
  "mode": "join",
  "host": "0.0.0.0",
  "port": 9000,
  "seed_endpoints": [
    "10.0.1.11:9100",
    "10.0.2.11:9100",
    "10.0.3.11:9100"
  ]
}

注意:aiRaft 默认只让 Leader 启动受管子进程。如果希望每个边缘点都运行本地采集或预处理模块,不能只靠 process 段。更合理的是:

  • 本地采集/预处理模块独立运行;
  • aiRaft 只托管"主处理/协调进程";
  • 或扩展 ProcessManager,支持 role: leader / follower / all

下篇将详细展开:坏节点处理流程、日常运维流程、适合场景、风险与边界,以及最终结论。

相关推荐
AISenz2 小时前
AIMesh 2.5 vs SmartMesh IP:6TiSCH 工业无线协议对比与选型指南
物联网·工业物联网·物联网协议
当下新鲜事4 小时前
空调机房冷冻泵与冷却泵技术分析:赛莱默B&G背包式智能变频管道泵的工程应用
运维·物联网·业界资讯
qq_199886874 小时前
第6板块·第4节:构建系统与多文件项目
c++·人工智能·gpu算力
汉克老师6 小时前
GESP2026年9月认证C++三级( 第三部分编程题(2、分割字符串))精讲
c++·gesp·小学生·学c++编程
估值探索者6 小时前
【Python量化系统工程化 #08】关了 SSH 就停?systemd 让脚本开机自启 + 异常自动拉起
java·c++·人工智能·分类·数据挖掘
天若有情6736 小时前
C++花式魔改!手写头文件复刻Java语法,彻底打破编码思维定式
java·c++·语言做语言·jac++
qq_199886876 小时前
第7板块·第3节:CUTLASS 的 GEMM 实现与优化策略
c++·人工智能·gpu算力·cuda
hetao17338376 小时前
2026-09-17 hetao1733837 的刷题记录
c++·算法