第三章:软件冗余技术——N版本编程、恢复块与表决算法

软件冗余,说白了就是「多写几份代码,让它们互相盯着」。

我在刚入行那会儿,总觉得硬件冗余才叫真功夫,软件嘛,改改bug就行了。直到有一次,一个PLC的固件在特定工况下死循环,整个产线停了4小时……嗯,从那以后,我对软件冗余再也不敢马虎。

3.1 N版本编程(N-Version Programming)

这个思路很直接:同一个需求,让三个不同的团队,用三种不同的语言或工具去实现。然后同时运行,结果投票。

你想想看,三个团队都犯同一个错的概率,是不是极低?

核心思想: 设计多样性(Design Diversity)——避免共性故障(Common Mode Failure)。

3.1.1 典型架构

  • 版本A: C语言 + RTOS,侧重性能
  • 版本B: 梯形图 + 专用PLC,侧重可靠性
  • 版本C: Python原型 + 形式化验证,侧重逻辑正确性

三个版本同时接收输入,各自计算,输出送给表决器。

3.1.2 我踩过的坑

我曾经在一个核电项目中用过N版本编程。三个团队各自开发,结果联调时发现——版本A和版本B对「超限报警阈值」的理解差了0.5%。

为什么?因为需求文档里写的是「接近上限时报警」,一个团队理解为95%,另一个理解为99%。

教训: N版本编程的前提是——需求规格必须绝对精确,不能有任何歧义。

3.1.3 代码示例(伪代码)

// 表决器核心逻辑
float output = vote(
    versionA(input),
    versionB(input), 
    versionC(input)
);

// 投票函数(三取二)
float vote(float a, float b, float c) {
    if (abs(a-b) < THRESHOLD) return (a+b)/2;
    if (abs(a-c) < THRESHOLD) return (a+c)/2;
    if (abs(b-c) < THRESHOLD) return (b+c)/2;
    return ERROR;  // 三个都不一致,报错
}

3.2 恢复块技术(Recovery Blocks)

这个技术跟N版本编程不一样。它不是同时跑多个版本,而是「主版本先跑,出错了再换备胎」。

说白了,就像你开车——主刹车失灵了,还有手刹。手刹再不行,还有电子驻车。

3.2.1 工作流程

  1. 主块(Primary Block) 先执行
  2. 执行完后做 验收测试(Acceptance Test)
  3. 如果测试通过,输出结果
  4. 如果测试不通过,回滚到上一个检查点,执行 备选块(Alternate Block)
  5. 备选块再失败,继续下一个备选……直到所有备选都用完
我的习惯: 验收测试一定要简单、快速、可靠。千万别在验收测试里搞复杂计算,否则它自己都可能出错。

3.2.2 避坑指南

我曾经在一个飞行控制系统中用恢复块技术。主块是C语言写的,备选块是Ada写的。结果发现——主块崩溃时,内存已经被污染了,备选块读到的数据全是错的。

解决方案: 每个备选块启动前,必须从干净的检查点恢复数据。不能共享内存空间。

3.2.3 伪代码示例

// 恢复块结构
bool recovery_block(Input in, Output *out) {
    // 保存检查点
    save_checkpoint();
    
    // 尝试主块
    if (primary_block(in, out) && acceptance_test(out)) {
        return true;
    }
    
    // 主块失败,恢复检查点
    restore_checkpoint();
    
    // 尝试备选块1
    if (alternate_block_1(in, out) && acceptance_test(out)) {
        return true;
    }
    
    // 备选块1也失败
    restore_checkpoint();
    
    // 尝试备选块2(最保守的版本)
    if (alternate_block_2(in, out) && acceptance_test(out)) {
        return true;
    }
    
    // 全部失败
    return false;
}

3.3 一致性检查与表决算法

不管是N版本编程还是恢复块技术,最后都绕不开一个问题——怎么判断多个结果「一致」?

这就是一致性检查和表决算法的活儿了。

3.3.1 常见表决算法

算法名称 适用场景 优点 缺点
多数表决(Majority Vote) 离散值(如开关量) 简单、快速 奇数个版本才有效
中值表决(Median Vote) 连续值(如温度、压力) 抗单个野点 计算稍慢
加权表决(Weighted Vote) 各版本可靠性不同 更精准 需要先验可靠性数据
一致性表决(Consensus Vote) 高安全等级 严格、无歧义 可能无结果

3.3.2 一致性检查的要点

  • 时间一致性: 所有版本必须在同一时刻采样输入。我见过一个项目,版本A采样早了10ms,版本B晚了5ms,结果两个值永远对不上。
  • 数值一致性: 允许一定的误差范围(比如±1%),而不是要求完全相等。
  • 顺序一致性: 如果输出是一个序列(比如轨迹点),必须保证顺序也一致。
注意: 表决器本身不能成为单点故障。我建议表决器也做冗余——至少两个表决器互相校验。

3.4 知识体系图

软件冗余技术体系 软件冗余 N版本编程 恢复块技术 表决算法 设计多样性 三取二表决 验收测试 检查点回滚 备选块链 多数表决 中值表决 加权表决 核心目标:防止共性故障 三种技术可组合使用,形成多层防护

3.5 实战建议

我个人习惯,在项目初期就确定好冗余策略:

  • 安全等级SIL3及以上: N版本编程 + 恢复块 + 三取二表决,三层防护
  • SIL2: N版本编程 + 中值表决,两层就够了
  • SIL1: 恢复块技术 + 简单的验收测试

记住一点:软件冗余不是越多越好。版本太多,维护成本会爆炸。我见过一个项目搞了5个版本,结果每次需求变更,5个团队都要改,改完还要重新做一致性测试……最后项目延期了半年。

我的经验: 3个版本是黄金数量。少于3个没法表决,多于3个管理成本陡增。

好了,软件冗余的核心就这些。下一节我们聊聊硬件冗余——那又是另一番天地了。


公众号:蓝海资料掘金营,微信deep3321