Java + Redis 实现工业设备告警去抖,避免变频器故障重复推送

工业设备接入平台后,一个常见问题很快就会暴露出来:同一台变频器在电压波动或通信不稳定时,可能在几秒内连续上报多条相同故障。如果服务端收到一条就通知一次,值班人员很快会被重复消息淹没。

本文以创安睿控工业驱控设备的接入场景为例,拆解一套可直接落地的告警去抖方案。重点不在于"少发消息",而在于保留故障事实的同时,把同一事件的重复抖动合并掉。

先定义什么算重复告警

设备上报的原始消息可以简化成下面这样:

json 复制代码
{
  "deviceId": "INV-1024",
  "faultCode": "OV",
  "level": 2,
  "occurredAt": 1785990123000,
  "status": "ACTIVE"
}

仅用 deviceId + faultCode 去重还不够。同一个过压故障在上午解除,下午再次发生,这是两个独立事件,不能被长期压掉。比较实用的规则是:

  1. 同一设备、同一故障码、同一状态组成去重键。
  2. 在短时间窗口内重复到达,只更新计数,不再次通知。
  3. 状态从 ACTIVE 变为 RECOVERED 时立即放行。
  4. 窗口结束后再次出现,视为新事件。

这个规则能区分"网络重传"和"故障重新发生"。窗口长度要根据采样频率与现场容忍度设置,不宜写死在代码里。

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 实现,系统才不会在"降噪"的同时漏掉真正的设备异常。

相关推荐
無限進步D1 小时前
Java 代码块
java·开发语言
AI视觉网奇1 小时前
cannot import name ‘model_urls‘ from ‘torchvision.models.resnet‘
linux·前端·javascript
用户3126874877201 小时前
@Async 加了没生效?Spring Boot 异步编程全链路拆解:从线程池到自调用陷阱
java·spring boot
Vuji1 小时前
深入 pi-agent 内核:完整追踪一次 CLI 交互的完整链路
前端·agent
zfoo-framework1 小时前
安装pi agent 支持duojie和deepseek
java·服务器·前端
l1m0_2 小时前
告别反复修改Prompt:AI生成React应用并落地开发的实战复盘
前端·react.js·ui·ai·设计
xexpertS2 小时前
前端工程转型实践:从 Ember 迁移到 React,提升构建速度与研发效能
前端·react.js·前端框架
大家的林语冰2 小时前
👍 JS 还在进化,ES2026 正式推出,最新七大特性补全!
前端·javascript·json
铁皮饭盒2 小时前
DeepSeek V4 Pro 0813发布了, 也可以部署到 Codex 了
前端·javascript·后端