车间一跳闸,上位机里攒着没落盘的采集数据、写到一半的数据库事务、正在进行的配方下发,全都可能出问题。掉电保护不是"买个 UPS"就完事的,它是一套组合拳:硬件上争取缓冲时间、软件上优雅停机、数据上勤落盘勤备份、恢复上提前演练。这篇把我们交付项目里的完整方案拆开讲。
先盘点:断电瞬间到底会丢什么
内存里的在途数据
采集队列里还没写库的几百帧数据、未导出的报警记录,断电即蒸发。攒批入库的窗口期越长,丢得越多。
写一半的数据库事务
大批量 INSERT 进行到一半断电,轻则丢这一批,重则数据库文件损坏(SQLite 尤其敏感),开机连库都打不开。
进行中的控制动作
配方下发到一半、参数修改未确认,上电后上位机与 PLC 状态不一致,操作工不知道设备处于什么状态。
▲ 三类风险对应三套对策:在途数据靠"勤落盘 + 断电抢救",事务损坏靠"备份 + 日志模式",状态不一致靠"上电对账"。
第一道防线:UPS 不是摆设,要让程序"听得见"它
UPS 的价值不在"撑多久",而在把"突然死亡"变成"预告离场"。带 USB/串口通信的 UPS 可以上报市电状态,上位机监听到"市电丢失、电池供电"信号后,立刻走优雅停机流程:
没有通信功能的廉价 UPS 也有土办法:让 PLC 监测市电接触器状态,断电瞬间给上位机写一个"市电丢失"的 M 点,上位机轮询到这个位就走同样的流程——反正断电时 PLC 和上位机通常同回路,谁先撑不住谁后走,能抢救几秒是几秒。整个流程必须控制在 UPS 电池时间的三分之一以内,留足余量。
第二道防线:平时就勤落盘,断电时损失才小
掉电保护的上限,取决于断电那一刻内存里有多少没落盘的数据。两个关键设置:
- 攒批窗口别贪大:批量入库虽快,但 5 秒一批意味着最坏丢 5 秒数据。车间场景我们一般 1 秒或 100 条取先到者,追溯类数据(扫码、检验结果)逐条实时写,不进批;
- 关键状态写检查点:当前工单号、批次阶段、配方版本号这类"上电后要接着用"的状态,变化时立即写本地检查点文件(JSON 即可),上电先读检查点恢复上下文,而不是从零开始。
// 检查点:状态变化即写,原子替换防写坏 public void SaveCheckpoint(Checkpoint cp) { var tmp = _path + ".tmp"; File.WriteAllText(tmp, JsonSerializer.Serialize(cp)); File.Move(tmp, _path, overwrite: true); // 先写临时文件再原子替换,断电最多丢一次变更,不会写坏旧文件 } // 上电恢复:读检查点 + 与PLC对账 public void OnStartup() { var cp = LoadCheckpoint(); // 可能为空(首次运行) var plcState = ReadPlcState(); // PLC侧的工单/批次状态 if (cp != null && !cp.Match(plcState)) RaiseMismatchAlarm(cp, plcState); // 不一致 → 报警让人确认,不自动猜 }
第三道防线:备份不是"备了"就行,要"能恢复"
数据库备份策略按库型分:
| 库型 | 备份方式 | 注意 |
|---|---|---|
| SQLite | VACUUM INTO 每日全备 + 开启 WAL 模式 | 直接复制 db 文件可能复制到写一半的状态,必须用 VACUUM INTO 或官方 Backup API;WAL 模式断电后自动回滚未提交事务,库不易坏 |
| SQL Server | 每日全备 + 每小时日志备份 | 恢复模式设为 FULL,日志备份保证最多丢一小时;生产库和上位机库分开实例 |
| MySQL | mysqldump 每日全备 + binlog | innodb_flush_log_at_trx_commit=1 保证事务不丢 |
备份文件本身也要防"一锅端":本地留 7 天,每周拷一份到车间办公室的另一台机器或 NAS。备份和原库在同一块盘上,等于没备。
收尾:掉电保护验收清单
- 拉闸测试:断电后内存数据丢失量 ≤ 1 个攒批窗口;
- UPS 信号触发后,程序在电池耗尽前完成优雅停机(有停机日志为证);
- 断电瞬间正在写库,上电后数据库能正常打开、无损坏;
- 上电后检查点恢复正确,状态不一致时能报警提示;
- 备份文件在干净环境真实恢复成功一次(每季度重演)。
掉电保护这套东西,硬件花钱不多,软件逻辑也不复杂,真正值钱的是交付前那次真刀真枪的拉闸测试。如果你的项目从没测过断电场景,建议找个停产的晚上试一次——第一次测试发现的问题,绝对比客户现场发现的多。
