设备运维管理系统开发实战:用规则引擎实现可配置的设备告警策略自研轻量引擎 vs Drools 落地对比
引言:告警规则是"月抛型"需求
设备运维系统的告警模块有个特点:上线之后永远在改。
夏天来了,温度阈值要调;客户换了工艺,某个测点的高峰时段要豁免;出了一次事故,客户要求"温度连续超 10 分钟才算告警,瞬时超不算"……第一版我们把规则写死在代码里,半年改了 17 次,每次都要发版。第 18 次需求来的时候,我们决定重做这块——本文就是那次重做的完整复盘。
先回答选型问题:为什么不用 Drools? Drools 当然强大,但对我们的场景它有三宗"罪":学习曲线陡(DRL 语法要培训)、规则文件由开发维护(客户想要的恰恰是"自己能改")、依赖重(私有化环境的客户机器跑不动太重的栈)。而设备告警规则的形态高度收敛——无非是"什么指标、超过什么阈值、持续多久、通知谁、多久静默"——这种收敛场景,自研一个轻量引擎是划算的。

一、规则模型:把一条告警规则拆干净
设计 DSL 之前,先跟运营/设备人员聊清楚他们脑子里的"一条告警规则"长什么样。最终抽象出五个要素:
# 告警规则 DSL 示例:排气温度过高告警
rule_id: ALARM_EXHAUST_TEMP_HIGH
tenant: t1002
target:
device_type: COMPRESSOR # 适用的设备型号
metric: exhaust_temp # 监测的测点
condition:
operator: ">" # GT / LT / GTE / LTE / BETWEEN
threshold: 85
unit: "℃"
duration: 10m # 持续超过 10 分钟才触发(防瞬时毛刺)
level: WARN # INFO / WARN / CRITICAL
silence: 2h # 触发后 2 小时内不重复告警(静默期)
action:
- notify: [DEVICE_SUPERVISOR] # 通知对象(角色)
- create_order: # 自动创建维修工单
template: FAULT_TEMP_HIGH
priority: HIGH
escalate: # 升级链
- after: 30m
to: [EQUIPMENT_MANAGER]
这份 DSL 设计里最重要的两个字段是 duration 和 silence,它们直接决定了告警系统的口碑:
- duration(持续时间):没有它,传感器每一次毛刺都是一条告警。某客户现场电流测点每天抖几十次,告警轰炸下第一周就被全员屏蔽。
- silence(静默期):没有它,同一个故障每分钟重复触发。告警的意义在于"唤起行动",重复噪声只会训练所有人忽略它。
二、评估引擎:滑动窗口 + 增量状态
持续时间的判断需要一个滑动窗口:"条件是否连续成立 10 分钟"。全量重算太浪费,我们用增量状态机的方式:每个"设备 × 测点 × 规则"维护一个轻量的窗口状态。
public class RuleWindow {
private long conditionStart = -1; // 条件首次成立的时间戳
/**
* 每来一条数据调用一次。返回 true 表示满足"持续 duration"可触发。
*/
public boolean feed(DataPoint point, RuleRule rule, long now) {
boolean hit = rule.getCondition().matches(point.getValue());
if (!hit) {
conditionStart = -1; // 条件中断,窗口清零
return false;
}
if (conditionStart < 0) {
conditionStart = now; // 条件首次成立
}
return now - conditionStart >= rule.getDurationMs();
}
}
整体数据流:
MQTT 数据 → 归一化 → 按(设备,测点)路由到匹配的规则集 → 逐条 feed 窗口
→ 满足持续条件 → 查静默期 → 发布告警事件 → 通知/建单/升级
三个工程要点:
- 内存态 + 快照恢复。窗口状态在内存里,服务重启会丢。我们对"条件已成立超过一半 duration"的窗口做定期快照到 Redis,重启后恢复,避免重启瞬间漏告警。
- 按测点路由规则,不做全量匹配。几千台设备 × 几十条规则如果每次数据都全扫,CPU 立刻爆炸。构建一张
(device_type, metric) → List<Rule>的路由表,每次数据只评估命中的三五条规则。 - 时间乱序容忍。断网续传的历史数据批量到达时,时间戳是乱的。窗口状态机对乱序数据的处理策略:早于"窗口状态最后更新时间"的数据直接旁路走"离线补评估"批任务,不进实时窗口,防止历史数据污染实时判断。
三、热更新:改规则不发版
规则存数据库,带版本号,服务端通过两种方式感知变更:
@Scheduled(fixedDelay = 30_000)
public void reloadRules() {
long latest = ruleMapper.getMaxVersion();
if (latest > loadedVersion) {
List<AlarmRule> fresh = ruleMapper.selectByVersionAfter(loadedVersion);
Map<String, List<AlarmRule>> newRoutes = buildRouteTable(fresh);
// 原子替换路由表;受影响的窗口状态清空重建
routeTable = newRoutes;
rebuildWindows(fresh);
loadedVersion = latest;
}
}
规则编辑界面给运营/设备主管用,表单化的条件配置,保存后 30 秒内全网生效。上线后我们统计了一下:告警相关的发版次数从月均 2~3 次降到接近零,所有调整都在界面完成,且每次变更留有操作日志,改坏了可以一键回滚到历史版本。
四、告警风暴治理:三层防线
规则引擎解决"怎么触发",风暴治理解决"别把人淹死"。我们踩过一次事故:某车间网络闪断 5 分钟,恢复后 300 台设备的断网补传数据瞬间涌入,加上恢复触发的状态变化,10 分钟内产生了 2000 多条告警,推送通道直接被打爆。之后的防线分三层:
第一层:源头收敛。同一设备同一规则处于静默期内不重复触发;设备离线告警在设备恢复后自动关闭,不残留。
第二层:空间合并。同一车间 10 台设备同时离线,大概率是车间交换机断了——按"设备组"聚合,合并成一条"3 号车间 10 台设备离线"的组告警,而不是 10 条独立告警。合并规则本身也是可配置的(按组织、按网关、按物理位置)。
第三层:通道限流与摘要。推送通道设置每用户每分钟上限,超出的部分折叠成一条摘要:"过去 10 分钟还有 23 条告警,点击查看全部"。
数据洪峰 → 源头静默 → 组合并 → 通道限流摘要
这套防线做完备之后,再也没有出现过"告警把客户惹毛"的投诉——告警系统的成败,一半在触发准不准,一半在打扰少不少。
五、踩坑记录
坑一:规则交叉冲突没检测。两条规则"A 温度 > 85 告警""A 温度 BETWEEN 60~90 正常"同时存在且都启用,行为变成玄学。后来在规则保存时做冲突检测:同设备同测点的区间规则要求互斥,检测不过不让保存。
坑二:阈值边界。">85" 到底含不含 85?不同运营人员理解不同。我们在 DSL 里强制用 GT/GTE 显式操作符,界面上也用文字写明"高于 85(不含 85)",消灭口头歧义。
坑三:规则导出没有环境隔离。测试环境调好的规则被误同步到生产,阈值是测试时临时放宽的 150℃。之后规则同步只能走"导出包 + 目标环境确认"流程,且导入时强制展示所有改动差异。
写在最后
自研规则引擎听起来像"重复造轮子",但当你的规则形态收敛、需要业务人员自助维护、还要跑在客户的低配私有化环境里时,一个几百行的轻量引擎往往比引入 Drools 更快更稳。判断标准就一条:你的规则 DSL 五年之内会不会长成另一个通用编程语言——如果不会,就大胆收敛、大胆自研。
至此,从采集(03)、评分(04)到告警(05),"数据侧"的链路讲完了。下一篇回到一线作业场景:巡检模块的扫码打卡、离线缓存与防作弊设计——那是收集真实数据的第一道关卡。
系列目录(持续更新)
- 设备保养工单系统开发实战:从计划自动生成到验收闭环的状态机设计
- 从 0 到 1 开发设备运维管理系统:整体架构设计与模块划分
- 设备数据采集协议怎么选?MQTT、Modbus、OPC UA 在运维场景的对比与落地
- 预测性维护不用深度学习?设备健康度评分的务实实现方案
- 用规则引擎实现可配置的设备告警策略:自研轻量引擎 vs Drools 落地对比(本文)
浙公网安备 33010602011771号