设备故障模式识别(MATCH_RECOGNIZE)
# 设备故障模式识别案例
物联网 / 工业场景里,很多告警不是"单点越限",而是一组按特定顺序出现的事件——连续几次超阈值、升温后骤降、开机→运行→停机的工作流、突发振动。这类"事件序列模式"正是 MATCH_RECOGNIZE(模式识别 / CEP)的用武之地。
本案例围绕一个设备读数流 stream,串联六个子场景,覆盖模式识别最常用的几种形态:
| 子场景 | 模式 | 核心能力 |
|---|---|---|
| A. 连续越限防抖 | A{3} | 固定次数重复——避免单点抖动误报 |
| B. 升温后骤降(故障前兆) | A+ B | 量词 + 序列——持续态后转态 |
| C. 振动突发 | A{5,} | 下限重复——高频突发检测 |
| D. 开停机工作流 | Start Running Stop | 跨事件类型序列 |
| E. 乱序鉴权 | PERMUTE(A, B) | 任意顺序——不关心先后 |
| F. 时间约束确认 | A B ... WITHIN | 时间窗——必须在 N 秒内完成 |
如何运行这些 SQL
本文每个场景只给 SQL + 输入 + 输出;运行方式见案例集锦 · 如何运行案例 SQL。模式识别查询必须用 Emit + AddSink(EmitSync 不支持 MATCH_RECOGNIZE);想保序用 AddSyncSink。
# 场景 A:连续越限防抖(A{3})
# 业务目标
温度偶尔抖动到高值是正常的(电磁干扰、采样毛刺)。只有连续 3 次都越过 80°C 才认定真实过热并告警,避免单点抖动误报。每个设备各自计数(PARTITION BY deviceId)。
# SQL
SELECT * FROM stream
MATCH_RECOGNIZE (
PARTITION BY deviceId ORDER BY ts
MEASURES MATCH_NUMBER() AS mn, COUNT(*) AS hits, LAST(A.temp) AS peak
ONE ROW PER MATCH
PATTERN (A{3})
WITHIN '1h'
DEFINE A AS temp > 80
)
2
3
4
5
6
7
8
9
# 输入与输出
dev-01 连续三次超 80 构成一次匹配;dev-02 只有两次,不够 3 次:
{"deviceId": "dev-01", "ts": 1, "temp": 60}
{"deviceId": "dev-01", "ts": 2, "temp": 82}
{"deviceId": "dev-01", "ts": 3, "temp": 85}
{"deviceId": "dev-01", "ts": 4, "temp": 88}
{"deviceId": "dev-02", "ts": 5, "temp": 90}
{"deviceId": "dev-02", "ts": 6, "temp": 91}
2
3
4
5
6
输出(dev-01 第 2–4 条构成 A{3}):
[alert] {deviceId:dev-01 mn:1 hits:3 peak:88}
# 行为说明
PATTERN (A{3}):A 连续重复恰好 3 次;中间出现一个不满足temp>80的行就打断,需重新凑满 3 次。PARTITION BY deviceId:每个设备各自的匹配,dev-02的两次不会被算进dev-01。COUNT(*)数 A 的行数(这里恒为 3),LAST(A.temp)取最后一次温度作为峰值。- 默认
AFTER MATCH SKIP PAST LAST ROW:匹配不重叠,下一次从本次最后一行之后找起。
# 场景 B:升温后骤降(故障前兆,A+ B)
# 业务目标
设备故障前常见"持续高温运行 → 温度骤降"的形态(如冷却液突然失效、传感器脱开)。识别"连续多行高温后,紧接着一行回落到低温"。
# SQL
SELECT * FROM stream
MATCH_RECOGNIZE (
PARTITION BY deviceId ORDER BY ts
MEASURES MATCH_NUMBER() AS mn, MAX(A.temp) AS peak, B.temp AS drop_to
ONE ROW PER MATCH
PATTERN (A+ B)
WITHIN '1h'
DEFINE
A AS temp > 80,
B AS temp < 30
)
2
3
4
5
6
7
8
9
10
11
# 输入与输出
dev-01 高温 3 行后骤降到 25:
{"deviceId": "dev-01", "ts": 1, "temp": 85}
{"deviceId": "dev-01", "ts": 2, "temp": 90}
{"deviceId": "dev-01", "ts": 3, "temp": 95}
{"deviceId": "dev-01", "ts": 4, "temp": 25}
2
3
4
输出:
[alert] {deviceId:dev-01 mn:1 peak:95 drop_to:25}
# 行为说明
A+(贪婪,至少 1 次):持续吃掉temp>80的行,尽可能多匹配;B吃第一个temp<30的行。- 整个匹配
[85,90,95,25]:A= 前三行,B= 25 那行。 MAX(A.temp)聚合 A 段最高温,B.temp取 B 行的温度(符号限定字段取该符号最后一次出现)。- 若高温段后没有回落(一直高温到流末),匹配未闭合,
Stop时经Flush才产出(贪婪等延伸终止)。
# 场景 C:振动突发(A{5,})
# 业务目标
振动传感器在设备失衡时高频抖动。识别"至少连续 5 次"振幅超阈值的突发段(A{5,} = 5 次及以上)。
# SQL
SELECT * FROM stream
MATCH_RECOGNIZE (
PARTITION BY deviceId ORDER BY ts
MEASURES MATCH_NUMBER() AS mn, COUNT(*) AS bursts, MAX(A.amp) AS max_amp
ONE ROW PER MATCH
PATTERN (A{5,})
WITHIN '1h'
DEFINE A AS amp > 50
)
2
3
4
5
6
7
8
9
# 输入与输出
dev-01 连续 6 次高振幅后回落(突发段 ≥5,匹配);末尾 amp=40 不满足 A,结束这段突发并触发输出:
{"deviceId": "dev-01", "ts": 1, "amp": 60}
{"deviceId": "dev-01", "ts": 2, "amp": 65}
{"deviceId": "dev-01", "ts": 3, "amp": 70}
{"deviceId": "dev-01", "ts": 4, "amp": 62}
{"deviceId": "dev-01", "ts": 5, "amp": 68}
{"deviceId": "dev-01", "ts": 6, "amp": 71}
{"deviceId": "dev-01", "ts": 7, "amp": 40}
2
3
4
5
6
7
输出:
[alert] {deviceId:dev-01 mn:1 bursts:6 max_amp:71}
# 行为说明
A{5,}(贪婪):至少 5 次,尽量多匹配。dev-01的 6 次全部进入一个匹配。- 贪婪量词没有自然终点时,匹配会延伸到下一个不满足 A 的行(这里的
amp=40)才产出、选最长;若一直满足到流末,则在Stop时经Flush产出。少于 5 次的突发段(如下方只有 4 次的情形)不产出。 - 突发段中间只要有一行
amp≤50就打断;AFTER MATCH SKIP PAST LAST ROW后从断点之后重新累计。
# 场景 D:开停机工作流(跨事件类型序列)
# 业务目标
设备的生命周期事件用 type 字段区分(start/running/stop)。识别完整的"开机 → 运行 → 停机"序列,统计该周期的运行峰值。每个符号匹配不同的 type。
# SQL
SELECT * FROM stream
MATCH_RECOGNIZE (
PARTITION BY deviceId ORDER BY ts
MEASURES MATCH_NUMBER() AS cycle, MAX(Running.power) AS peak_power
ONE ROW PER MATCH
PATTERN (Start Running+ Stop)
WITHIN '24h'
DEFINE
Start AS type == "start",
Running AS type == "running",
Stop AS type == "stop"
)
2
3
4
5
6
7
8
9
10
11
12
# 输入与输出
dev-01 一个完整周期:start → 两行 running → stop:
{"deviceId": "dev-01", "ts": 1, "type": "start", "power": 0}
{"deviceId": "dev-01", "ts": 2, "type": "running", "power": 120}
{"deviceId": "dev-01", "ts": 3, "type": "running", "power": 150}
{"deviceId": "dev-01", "ts": 4, "type": "stop", "power": 0}
2
3
4
输出:
[cycle] {deviceId:dev-01 cycle:1 peak_power:150}
# 行为说明
- 每个符号用
type == "..."匹配不同事件(字符串相等用==)。 Running+:运行态可以持续多行,MAX(Running.power)取运行段峰值。WITHIN '24h':一个开停机周期不应跨天;超窗的部分匹配被主动清理,避免"开了机一直没停"的事件永远挂留。- 符号名区分大小写,建议用有意义的名字(
Start/Running/Stop)提升可读性。
# 场景 E:乱序鉴权(PERMUTE,任意顺序)
# 业务目标
安全审计要求"一次会话里既出现登录(login)又出现鉴权(auth)",但两者先后无所谓(有的客户端先 auth 再 login)。用 PERMUTE 匹配任意顺序。
# SQL
SELECT * FROM stream
MATCH_RECOGNIZE (
PARTITION BY sessionId ORDER BY ts
MEASURES MATCH_NUMBER() AS mn, FIRST(Login.ts) AS t1, FIRST(Auth.ts) AS t2
ONE ROW PER MATCH
PATTERN (PERMUTE(Login, Auth))
WITHIN '10m'
DEFINE
Login AS event == "login",
Auth AS event == "auth"
)
2
3
4
5
6
7
8
9
10
11
# 输入与输出
会话 s1 先 login 后 auth;会话 s2 先 auth 后 login——两者都匹配:
{"sessionId": "s1", "ts": 1, "event": "login"}
{"sessionId": "s1", "ts": 2, "event": "auth"}
{"sessionId": "s2", "ts": 3, "event": "auth"}
{"sessionId": "s2", "ts": 4, "event": "login"}
2
3
4
输出:
[audit] {sessionId:s1 mn:1 t1:1 t2:2}
[audit] {sessionId:s2 mn:1 t1:4 t2:3}
2
# 行为说明
PERMUTE(Login, Auth)等价于(Login Auth) | (Auth Login),两个符号的任意顺序都算匹配。PARTITION BY sessionId:每个会话各自识别。FIRST(Login.ts)/FIRST(Auth.ts):取该符号第一次出现的时间戳,可据此判断实际先后。- 符号多时 PERMUTE 编译为全排列(N!),边缘通常 2–3 个符号。
# 场景 F:时间约束确认(WITHIN)
# 业务目标
告警产生后,运维必须在 30 秒内确认(ack)。识别"告警事件后 30 秒内出现确认事件"——超时则匹配失败(告警未被及时处理)。这依赖 WITHIN 时间窗。
# SQL
SELECT * FROM stream
MATCH_RECOGNIZE (
PARTITION BY deviceId ORDER BY ts
MEASURES MATCH_NUMBER() AS mn, Alert.ts AS alert_at, Ack.ts AS ack_at
ONE ROW PER MATCH
PATTERN (Alert Ack)
WITHIN '30s'
DEFINE
Alert AS event == "alert",
Ack AS event == "ack"
)
2
3
4
5
6
7
8
9
10
11
ts 用毫秒级 epoch 时间戳(WITH (TIMESTAMP='ts', TIMEUNIT='ms') 语义,MATCH_RECOGNIZE 的 ORDER BY 字段自动判单位归一化)。
# 输入与输出
dev-01 告警后 10 秒确认(匹配);dev-02 告警后 60 秒才确认(超 30 秒窗,匹配未完成、被过期清理):
{"deviceId": "dev-01", "ts": 1700000000000, "event": "alert"}
{"deviceId": "dev-01", "ts": 1700000010000, "event": "ack"}
{"deviceId": "dev-02", "ts": 1700000020000, "event": "alert"}
{"deviceId": "dev-02", "ts": 1700000080000, "event": "ack"}
2
3
4
输出:
[ok] {deviceId:dev-01 mn:1 alert_at:1700000000000 ack_at:1700000010000}
# 行为说明
WITHIN '30s':Alert 到 Ack 必须在 30 秒内。dev-01间隔 10 秒 ✓;dev-02间隔 60 秒 ✗。dev-02的部分匹配(Alert 已匹配、等 Ack)在超过 30 秒后由后台 sweeper 主动清理——不必等 Ack 事件到达才判定,空闲分区的超窗状态不会挂留。- 未在窗内完成的告警不产出(可另配一条规则统计"未及时确认")。
ts用 epoch 量级(秒/毫秒/微秒/纳秒)才会触发 wall-clock 主动过期;用小值序号(1,2,3…)时维持纯被动(事件到达时检查)。
# 场景对比一览
| 场景 | 模式骨架 | 关键子句 | 解决什么 |
|---|---|---|---|
| A 防抖 | A{3} | 固定次数 | 单点抖动误报 |
| B 故障前兆 | A+ B | 贪婪量词 + 序列 | 持续态后转态 |
| C 突发 | A{5,} | 下限次数 | 高频重复段 |
| D 工作流 | Start Running+ Stop | 跨类型 + 量词 | 生命周期序列 |
| E 乱序 | PERMUTE(A, B) | 排列 | 先后无所谓 |
| F 时间约束 | A B WITHIN | 时间窗 + 主动过期 | 必须 N 秒内 |
# 📚 相关文档
- 模式识别(MATCH_RECOGNIZE) —— 完整语法参考(PATTERN / DEFINE / MEASURES / 导航 / 聚合 / SUBSET / FINAL-RUNNING / SKIP)
- 如何运行案例 SQL —— 统一的 Go 运行样板
- 分析函数 —— 相邻两事件的变化检测(
lag/had_changed),与 CEP 互补