

做工业 IoT 平台的人,很多经历过这样一条曲线:告警系统刚上线时,每天几十条,每条都值得看;三个月后设备全量接入,每天几千条;半年后,运维开始无视告警群——不是他们不负责任,是告警里十有八九不需要处理。等到真故障发生,那条关键的告警躺在几百条"离线告警""越限告警"中间,没人点开。
这就是告警风暴,以及它带来的告警疲劳。告警系统的价值不在于"能报多少",而在于"每一条都值得看"。一个每天几千条的告警系统,实际效果不如一个每天二十条、条条有效的系统。
告警风暴有三个典型来源:重复轰炸(同一个问题反复报)、边界抖动(指标在阈值附近来回穿越,每次穿越报一条)、共因群发(一条产线停电,两百台设备同时报离线)。这三个来源对应三道治理闸门:单条告警的卫生(去重、持续确认、滞回抑制)、群发告警的共因收敛、以及分级静默路由。
本文用 DolphinDB 的流计算引擎(响应式状态引擎 +
@state状态函数)把这三道闸门逐个实现出来,最后给出一组度量告警治理效果的指标。流计算引擎本身的性能调优(吞吐、延迟)不是本文重点,本文聚焦的是跑在引擎之上的告警治理逻辑。
先诊断,再开药。告警量失控,来源无非三类:
来源一:重复轰炸
同一个故障没恢复,告警每分钟报一次。设备轴承磨损,振动持续超标,于是从超标那一刻起每小时 60 条告警,直到有人处理。一条故障,几百条噪音。
来源二:边界抖动
阈值设在 5.0,而设备的振动值恰好在 4.9~5.1 之间自然震荡。于是告警状态反复"触发→恢复→触发→恢复",一小时能产生上百次状态翻转。每次翻转都通知,就是每次都在喊狼来了。
来源三:共因群发
一条产线停电,线下 200 台设备全部离线。设备级的离线告警逐台触发,200 条告警瞬间涌入——但根因只有一个:停电。运维要看的不是 200 条"设备离线",而是一条"三号产线供电异常"。

三个来源,占了告警噪音的绝大多数。它们分别对应下面三节的三道闸门。
这一节处理来源一和来源二,核心是三个机制:同源去重、持续时长确认、滞回抑制。
去重的原则:同一个设备 + 同一条规则,告警未恢复前只报一次。后续数据继续越限,只更新"持续时长",不再产生新告警。
实现上,这意味着告警判断必须是有状态的——要记住"这台设备这条规则当前是否已处于告警态"。这正是 @state 状态函数的用武之地:用它写的函数在流计算引擎里,每个设备(keyColumn)维护一份独立的状态,跨消息持续存在。
瞬时毛刺不该报。振动值因为一次物料冲击窜到 5.2、一秒后回落,这不是设备故障,是正常工况波动。规则改成:越限并持续 N 秒才触发。
用 @state 维护一个持续计数器:
// 持续时长确认:越限且连续满 confirmSecs 次采样才告警
@state
def persistentAlarm(value, threshold, confirmSecs) {
mutable dur = 0
if (value > threshold) {
dur += 1
} else {
dur = 0
}
return iif(dur >= confirmSecs, 1, 0)
}这段逻辑的关键在 mutable dur:它是跨消息持续的。同一台设备每来一条数据,函数执行一次,dur 在上一次的基础上累加或清零。毛刺窜一下立刻回落,dur 清零,不会误报;持续超标,dur 涨到阈值才触发。
对付边界抖动的经典手段是滞回(hysteresis):触发和恢复用两个阈值,中间留一段缓冲带。
振动值
5.5 ┤ ●━━━━━ 告警持续中
5.0 ┤ ─ ─ ─ ─ 触发阈值(向上穿越才触发)─ ─ ─
4.5 ┤ ─ ─ ─ ─ 恢复阈值(向下穿越才恢复)─ ─ ─
┤ ●──●──● 值在 4.6~5.1 震荡 → 不再反复翻转
触发阈值 5.0、恢复阈值 4.5:值窜过 5.0 进入告警态后,必须跌回 4.5 以下才恢复。在 4.5~5.0 之间的震荡不会引起任何状态翻转。
// 滞回抑制:双阈值状态机
@state
def hysteresisAlarm(value, onThreshold, offThreshold) {
mutable alarmed = 0
if (alarmed == 0 and value > onThreshold) {
alarmed = 1
} else if (alarmed == 1 and value < offThreshold) {
alarmed = 0
}
return alarmed
}两个阈值之间的缓冲带宽度是调参重点:太窄抑制不住抖动,太宽会让告警恢复迟钝。经验起点是缓冲带取正常波动幅度的 2~3 倍。
把两个函数挂到响应式状态引擎,按设备维护独立状态:
// 输入:实时测点流(每台设备每秒一条)
// 输出:带状态翻转标记的告警流
alertOut = table(1:0, `deviceId`ts`value`alarmState, [SYMBOL, DATETIME, DOUBLE, INT])
alertEngine = createReactiveStateEngine(
name = "alertHygiene",
metrics = <[
persistentAlarm(value, 5.0, 10) as confirmed,
hysteresisAlarm(value, 5.0, 4.5) as hysteresis
]>,
dummyTable = sensorStream,
outputTable = alertOut,
keyColumn = "deviceId"
)
subscribeTable(tableName=`sensorStream, actionName="hygiene",
handler=tableInsert{alertEngine}, msgAsTable=true)输出流里,真正要通知的只有状态翻转的边沿(0→1 触发、1→0 恢复),持续期间的 1 不再重复报——这就是去重的落地:下游订阅时只消费边沿。
单条告警干净了,群发还没解决。200 台设备同时离线,即使每台都做了去重,还是 200 条。
共因故障的特征是时间和空间上的聚集:同一产线(或同车间、同配电回路)的设备,在一个很短的时间窗内集中越限。治理思路是把告警判断从设备级上卷到组级:

这样停电场景收敛成一条告警,单点故障不受影响。
实现是两级流水线:第一级做设备级卫生判断(上一节的引擎),第二级用时间序列聚合引擎按组统计越限比例:
// 第一级输出(alertOut)带 deviceId,先映射到所属产线
// 假设设备档案(PKEY 表)提供 deviceId → lineId 映射
enriched = select a.deviceId, a.ts, a.confirmed,
p.lineId
from aj(alertOut as a, loadTable("dfs://iot_meta", "deviceProfile") as p, `deviceId)
share enriched as enrichedStream
// 第二级:每 10 秒一个窗口,按产线统计越限比例
groupOut = table(1:0, `lineId`windowEnd`alarmRatio`alarmCount, [SYMBOL, DATETIME, DOUBLE, INT])
convEngine = createTimeSeriesAggregator(
name = "groupConverge",
windowSize = 10s,
step = 10s,
metrics = <[sum(confirmed) \ count(confirmed) as alarmRatio,
sum(confirmed) as alarmCount]>,
dummyTable = enrichedStream,
outputTable = groupOut,
timeColumn = `ts,
keyColumn = `lineId
)
subscribeTable(tableName=`enrichedStream, actionName="converge",
handler=tableInsert{convEngine}, msgAsTable=true)然后在 groupOut 上判断:alarmRatio 超过 0.6 的窗口,产出一条组级告警,同时抑制该组窗口内的设备级告警(组级优先);比例不高但有零星越限的,放行设备级。
组级告警之外,收敛过程中留下的"哪些设备、多短时间内、集中越限"的记录本身就是根因分析的线索。把每个窗口的越限设备清单存下来,事后回看:如果 200 台设备在 8 秒内集中离线,基本可以排除 200 个独立故障同时发生的可能,指向供电/网络这类共享基础设施——收敛不是丢信息,是把信息换了一种更集中的形态。
前两道闸解决"报什么",这一节解决"怎么送"。
不是所有告警都值得打断人。按影响程度分级,不同级别走不同通道:
级别 | 定义 | 通知方式 |
|---|---|---|
提示 | 偏离正常但无风险 | 只进看板,不通知 |
警告 | 需要关注,可等班次交接 | 班次汇总推送 |
严重 | 影响生产,需当日处理 | 即时消息 + 工单 |
紧急 | 安全风险,需立即响应 | 电话/短信 + 工单 |

分级的依据通常是"规则类型 × 设备关键度"——关键设备的警告可能比边缘设备的严重更高优先级。设备关键度放在设备档案表(PKEY 引擎)里,告警产生时 join 上。
计划检修期间,设备轮流停启,会产生大量"离线""参数越限"的伪告警。做法是维护一张静默配置表,检修计划登记进去,告警发送前 join 这张表,落在静默窗口内的降级为"仅记录,不通知":
// 静默配置表:哪台设备、哪个时间窗、静默原因
share table(1:0, `deviceId`startTime`endTime`reason,
[SYMBOL, DATETIME, DATETIME, SYMBOL]) as silenceCfg
// 发送前过滤:命中静默窗的只记录不通知
toNotify = select a.deviceId, a.ts, a.alarmState,
iif(s.deviceId != NULL, 0, 1) as shouldNotify
from aj(alertOut as a, silenceCfg as a2, `deviceId) ...
// 简化示意:实际按 deviceId + 当前时间是否落在 [startTime, endTime] 过滤
// 静默配置由排程系统写入,也可用 scheduleJob 定时同步 CMMS 的检修计划一个容易忽略的细节:静默解除后要做一次状态对账——检修期间被静默的告警里,可能有已经真实恶化的,解除静默时把仍未恢复的重新评估一遍,不能一静了之。
严重告警发出后 N 分钟无人确认,自动升级(通知下一级负责人)。确认与升级的状态流转放在工单表里做(用 IMOLTP 事务引擎存告警工单,保证流转一致性),流引擎只负责把"超时未确认"这个事件检测出来:
// 每 30 秒扫一次未确认的严重告警,超 5 分钟未确认 → 推升级流
def escalateCheck() {
pending = select ticketId, deviceId, createdAt
from loadTable("dfs://iot_txn", "alertTicket")
where status = `SEVERE_UNCONFIRMED
and createdAt < now() - 5m
if (pending.size() > 0) {
loadTable("dfs://iot", "escalationStream").tableInsert(pending)
}
}
scheduleJob("escalate_scan", "严重告警升级扫描", escalateCheck,
now() + 30s, now() + 1d, 'S', "30s")三道闸装完,怎么知道有没有效?告警治理不是一次性工程,需要指标 + 循环。

// 每日告警治理指标
dailyMetrics = select
date(createdAt) as day,
count(*) as total_alerts,
// 噪音比:自动恢复(从未被处理)的告警占比 —— 越低越好
sum(iif(status = `AUTO_RECOVERED, 1, 0)) \ count(*) as noise_ratio,
// 可行动率:引发工单的告警占比 —— 越高越好
sum(iif(hasTicket, 1, 0)) \ count(*) as actionable_rate,
// 平均确认时长(分钟):告警到被确认的耗时
avg((confirmedAt - createdAt) \ 60000) as mtta_min
from loadTable("dfs://iot_txn", "alertTicket")
group by date(createdAt)指标每周看一次,按这个循环调:
告警量上涨 → 看是哪个来源(重复/抖动/群发)
→ 重复多:检查去重边沿逻辑有没有漏
→ 抖动多:加宽滞回缓冲带,或提高持续确认时长
→ 群发多:检查组收敛的分组维度(按产线?按配电回路?)
→ 都不是:规则本身过敏感,调阈值或降级治理的本质是持续把"不值得看的告警"挡在通知之外,让每条送达的告警都配得上一次人工关注。
告警这件事有三个层次:第一层是把告警做出来——从海量数据到实时预警,这是大多数平台起步时攻坚的部分;第二层再往前一步,预测故障——在告警发生之前预判风险;而容易被忽略的第三层是:告警做出来之后,怎么让它值得被看。本文写的就是这一段——它没有前两层技术含量高,却直接决定告警系统有没有人信。
三道闸门,一句话总结:
技术上的共同点是:告警治理的核心是有状态判断。去重要记住"是否已告警",持续确认要数"连续越限几次",滞回要维护"当前状态",收敛要统计"组内比例"——这些都是跨消息的状态,@state 状态函数 + 响应式状态引擎的组合正是为此设计的:每个设备一份独立状态,增量更新,毫秒级完成判断。
最后记住那个反直觉的结论:告警系统的价值不在能报多少,而在每一条都值得看。每天几千条的告警系统不是监控完善,是监控失效——它已经训练所有人学会了忽略它。治理的目标,是把告警量压回到"每条都配得上一次人工关注"的水平。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。