工业设备接入平台后,一个常见问题很快就会暴露出来:同一台变频器在电压波动或通信不稳定时,可能在几秒内连续上报多条相同故障。如果服务端收到一条就通知一次,值班人员很快会被重复消息淹没。
本文以创安睿控工业驱控设备的接入场景为例,拆解一套可直接落地的告警去抖方案。重点不在于"少发消息",而在于保留故障事实的同时,把同一事件的重复抖动合并掉。
先定义什么算重复告警
设备上报的原始消息可以简化成下面这样:
json
{
"deviceId": "INV-1024",
"faultCode": "OV",
"level": 2,
"occurredAt": 1785990123000,
"status": "ACTIVE"
}
仅用 deviceId + faultCode 去重还不够。同一个过压故障在上午解除,下午再次发生,这是两个独立事件,不能被长期压掉。比较实用的规则是:
- 同一设备、同一故障码、同一状态组成去重键。
- 在短时间窗口内重复到达,只更新计数,不再次通知。
- 状态从
ACTIVE变为RECOVERED时立即放行。 - 窗口结束后再次出现,视为新事件。
这个规则能区分"网络重传"和"故障重新发生"。窗口长度要根据采样频率与现场容忍度设置,不宜写死在代码里。
Redis 去抖的原子实现
单实例服务可以用本地缓存,但设备平台通常会横向扩容。本地缓存无法在多个实例之间共享,重启后状态也会丢失。Redis 更适合保存短周期去抖状态。
最简单的实现是 SET key value NX EX:
java
public boolean shouldNotify(AlarmEvent event) {
String key = String.format("alarm:dedup:%s:%s:%s",
event.deviceId(), event.faultCode(), event.status());
Boolean first = stringRedisTemplate.opsForValue()
.setIfAbsent(key, "1", Duration.ofSeconds(30));
return Boolean.TRUE.equals(first);
}
第一次写入成功,说明当前窗口内还没有同类事件,可以继续发送通知。后续重复消息写入失败,只记录到数据库或指标系统。
如果还需要统计窗口内重复次数,可以使用 Lua 脚本,让"首次写入、设置过期时间、计数递增"在 Redis 内一次完成:
lua
local count = redis.call('INCR', KEYS[1])
if count == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
return 1
end
return 0
Java 侧只根据返回值决定是否通知:
java
private static final DefaultRedisScript<Long> DEDUP_SCRIPT =
new DefaultRedisScript<>(LUA_TEXT, Long.class);
public boolean shouldNotify(AlarmEvent event) {
String key = buildDedupKey(event);
Long result = stringRedisTemplate.execute(
DEDUP_SCRIPT, List.of(key), "30");
return Long.valueOf(1L).equals(result);
}
Lua 的价值在于原子性。如果先 INCR 再单独 EXPIRE,服务恰好在两条命令之间中断,键可能永不过期,后续真实告警也会被误判。
恢复事件不能简单去重
故障恢复代表设备状态已经变化,应该让运维人员及时看到。可以在收到 RECOVERED 时删除对应的活动告警键,并为恢复消息建立独立的短窗口:
java
if (event.status() == AlarmStatus.RECOVERED) {
redisTemplate.delete(activeKey(event.deviceId(), event.faultCode()));
return shouldNotifyRecovery(event);
}
更稳妥的做法是把通知状态建模为小型状态机:NORMAL -> ACTIVE -> RECOVERED -> NORMAL。Redis 负责快速判断,数据库保存完整事件轨迹。这样即使通知渠道暂时失败,平台仍能补发,而不是把"是否通知"误当成"故障是否存在"。
上线前要补的三个边界
第一,设备时间不能直接作为唯一判断依据。现场网关可能离线缓存,恢复网络后批量补传。服务端应同时保留设备发生时间和平台接收时间。
第二,去抖键需要版本。故障码映射规则调整后,旧键可能干扰新逻辑,可以在键前缀中加入规则版本,例如 alarm:v2:dedup。
第三,去抖不等于丢弃。被压制的重复消息仍应累计 repeatCount,并在告警详情里显示。值班人员不需要收到二十次推送,但需要知道故障在三十秒内抖动了二十次。
在创安睿控这类变频器与工业驱控设备接入场景中,告警质量往往比告警数量更重要。先把事件边界、恢复逻辑和补传场景定义清楚,再选择 Redis 实现,系统才不会在"降噪"的同时漏掉真正的设备异常。