多模冗余(XMR)
可用性:两个变体(Full / Tiny)均支持。头文件
onepath_xmr.h。
XMR(X-Modular Redundancy,多模冗余)把「N 份冗余 + 投票选举」收敛为按组名 join 的 位置透明角色:一个任务在主机 A 提交,一群 worker(可在任意机器)冗余计算,投票消费端 (可在主机 C)本地投票选出可靠结果。N 任意:1=无冗余、2=DMR(检错)、3=TMR(纠 1 错)、 N=NMR(纠 ⌊(N−1)/2⌋ 错)。
一、设计要点
- 位置透明的组:一个组 = 一个名字。各角色按组名 join,不假设同机;跨节点天然可用。
- 每角色一行:
submit/compute(worker)/consume(投票消费端)/store(副本)/put/get。opts=NULL即全默认。 - auto-N(存活感知自动计数):worker 上线即被消费端自动计入 N,spawn 几个就是几模 冗余,worker 增减自适应;
opts.expected可钉死。 - 边缘投票(无单点):投票在消费端本地完成。投票器本身是单点(SPOF)——标准解 是让每个消费端各自投票(等价 triplicated voters),XMR 默认即如此。
- 端到端完整性:每份结果默认带 CRC32;被翻转的结果在投票前剔除 (
opts.integrity = ONEPATH_XMR_NONE可关)。 - 可插投票:默认精确多数;
opts.vote_fn可换 median / 阈值 / 取最值等策略。
二、快速开始(跨节点,每角色一行)
#include <onepath.h>
#include <onepath_xmr.h>
/* 主机 A — 生产者 */
onepath_xmr_submit(s, "img-infer", data, len);
/* 任意机器 — 每个 worker 一行, spawn N 个 */
static void infer(void *ud, const void *in, size_t n, onepath_xmr_sink_t *sink) {
/* ... 计算 ... */ onepath_xmr_emit(sink, out, out_len);
}
onepath_xmr_t w; onepath_xmr_compute(s, &w, "img-infer", infer, NULL, NULL);
/* 主机 C — 投票消费端 (= voter + consumer 合并, 边缘投票) */
static void on_result(void *ud, const onepath_xmr_result_t *r) {
if (r->agreed) use(r->data, r->data_len); /* 可靠结果 */
else handle_no_consensus(); /* DMR 检出分歧 */
}
onepath_xmr_t c; onepath_xmr_consume(s, &c, "img-infer", on_result, NULL, NULL);存储同理:onepath_xmr_store(副本,spawn N)/ onepath_xmr_put(写)/ onepath_xmr_get (读端按键投票选举)。配套示例程序 onepath_xmr_compute_demo 与 onepath_xmr_store_demo 给出完整可运行版本。
三、API 参考
int onepath_xmr_submit(onepath_session_t s, const char *group, const void *data, size_t len);
int onepath_xmr_compute(onepath_session_t s, onepath_xmr_t *out, const char *group,
onepath_xmr_fn fn, void *ud, const onepath_xmr_opts_t *opts);
int onepath_xmr_emit(onepath_xmr_sink_t *sink, const void *out, size_t len);
int onepath_xmr_consume(onepath_session_t s, onepath_xmr_t *out, const char *group,
onepath_xmr_result_cb cb, void *ud, const onepath_xmr_opts_t *opts);
int onepath_xmr_store(onepath_session_t s, onepath_xmr_t *out, const char *group,
const onepath_xmr_opts_t *opts);
int onepath_xmr_put(onepath_session_t s, const char *group, const char *key,
const void *data, size_t len);
int onepath_xmr_get(onepath_session_t s, const char *group, const char *keyexpr,
const onepath_xmr_opts_t *opts, onepath_xmr_result_cb cb, void *ud);
void onepath_xmr_close(onepath_xmr_t h);| 函数 | 说明 |
|---|---|
onepath_xmr_submit(s, group, data, len) | 生产者:提交一条计算任务 |
onepath_xmr_compute(s, &out, group, fn, ud, opts) | worker:订阅任务→计算→发布结果 |
onepath_xmr_emit(sink, out, len) | 在 worker 计算函数内产出结果 |
onepath_xmr_consume(s, &out, group, cb, ud, opts) | 投票消费端:收集→投票→选举(边缘投票) |
onepath_xmr_store(s, &out, group, opts) | 存储副本:摄入写入、应答读取 |
onepath_xmr_put(s, group, key, data, len) | 写入(广播到全部副本) |
onepath_xmr_get(s, group, keyexpr, opts, cb, ud) | 读取并按键投票选举 |
onepath_xmr_close(h) | 关闭 worker / 消费端 / 副本句柄 |
选项 onepath_xmr_opts_t
| 字段 | 含义 | 默认 |
|---|---|---|
expected | 钉死 N;0 = 存活感知自动计数 | 0(auto) |
quorum | 需多少票一致;0 = 多数 ⌊N/2⌋+1 | 0 |
window_ms | 一次选举的收齐超时 | 2000 |
vote_fn / vote_ud | 自定义投票;NULL = 精确多数 | NULL |
integrity | ONEPATH_XMR_CRC32 / ONEPATH_XMR_NONE | CRC32 |
republish_key | 消费端非空则把 elected 转发到此键供扇出 | NULL |
node_id | 覆盖自动节点编号 | NULL(用节点 ID) |
encoding | 发布编码 | NULL |
选举结果 onepath_xmr_result_t
agreed=1 表示达到多数、结果可靠;agreed=0 表示无定论(DMR 两份分歧或收齐窗口内不足 多数),不应被当作可靠结果。votes/n 为一致票数与参与候选数(已剔除 CRC 失败者)。
创建 / 销毁配对
| 创建 | 销毁 |
|---|---|
onepath_xmr_compute / onepath_xmr_consume / onepath_xmr_store | onepath_xmr_close |
四、N、quorum 与投票
- N 的来源:默认由存活感知自动计数在线 worker / 副本数;
opts.expected可钉死 (适合 N 固定的场景)。 - 多数与容错:精确多数需 ⌊N/2⌋+1 票一致,可容忍 ⌊(N−1)/2⌋ 个故障。
- N=1:无冗余;N=2(DMR):能检错不能纠错(两份分歧 →
agreed=0); N=3(TMR):纠 1 错;N=5:纠 2 错。
- N=1:无冗余;N=2(DMR):能检错不能纠错(两份分歧 →
- 低延迟快路径:一旦达到多数即选举,不必等齐全部 worker(落后者不阻塞结果)。
- 自定义投票:
vote_fn接收候选数组,返回胜者索引——可实现 median(数值)、阈值 (近似一致)、取最值、加权等策略。
五、可靠性模型与限制(务必阅读)
XMR 用软件冗余屏蔽独立瞬态故障(如不同 worker 各自被偶发错误命中)。它的能力边界 如下。
投票器是单点;用边缘投票化解
多数投票器本身若出错则结果出错。消除单点的标准做法是让每个消费端各自投票(不是共用 一个投票节点)。XMR 默认即「投票消费端」合一:消费端订阅 N 份原始结果并本地投票, 因此:
- 没有独立「已选举结果」的额外传输跳;
- 没有单一投票节点单点(每个消费端等价一个独立投票器)。
Case 1:节点有物理加固 / EDAC(纠错存储)
投票消费端在加固节点上投票,结果在该节点可靠。但若把选举结果再传给另一台未加固节点 的消费者,这一跳与远端内存又脱离 XMR 保护、可能再次出错。
建议:要么就地消费(voter+consumer 合一于加固节点),要么远端消费者不要信任转发来 的结果,而是自己也订阅 N 份原始结果本地再投一次(边缘投票)。republish_key 的转发 仅用于带宽 / 扇出,不应作为跨未加固跳的「可信」结果。
Case 2:无加固 / 无 EDAC
XMR 能赋能到:
- 屏蔽 worker 阶段的独立瞬态故障(N=3 容 1、N=5 容 2…);
- 端到端 CRC 把传输中被翻转的结果在投票前剔除,把可靠边界推到消费节点。
但 XMR 不能根除以下风险
- 最终合并那一刻:投票消费端自身那一次比较 / 内存若出错,结果仍可能错。软件只能用 边缘投票把它从「单点」降为「多点」,但每个消费节点那一刻的内存仍需该节点有 EDAC 才 彻底可靠。
- 共模 / 相关故障:所有 worker 跑相同代码 / 相同硬件时,确定性缺陷、或同一窗口多个 worker 同时被命中,会让多数一致地错,投票失效。抗共模需实现多样性(不同算法 / 不同硬件,即 N-version)。XMR over 同构 worker 只屏蔽独立瞬态故障。
- 共享资源:共用电源 / 时钟 / 链路、或同机多 worker 同时失效 → 无保护。
一句话:XMR 提供「可调 N 的独立瞬态故障屏蔽 + 端到端校验 + 无单点的边缘投票」;残留风险 是消费节点最终合并的内存(需硬件 EDAC)、共模故障(需实现多样性)。
六、注意事项
- 同一组内各角色的
integrity设置应一致(默认全开即一致)。 - 任务序号跨生产者须唯一:每次
onepath_xmr_submit即一个独立任务实例;多生产者建议用 不同组。 onepath_xmr_get内部对读取使用「查询全部副本 + 不合并应答」(对应onepath_get_opts_t的target = ONEPATH_QUERY_TARGET_ALL、consolidation = ONEPATH_CONSOLIDATION_NONE), 以便看到每个副本的应答用于投票。- 计算结果与存储值对 XMR 是不透明字节;编码 / 元信息请自行编排在值内。