WebhookWise:把吵闹的告警做成一个小型 AIOps 控制面

一个「告警太吵、飞书天天刷屏」的痛点,怎么长成了一套带 AI 分析、降噪去重、事务性转发和全链路可观测的告警中枢。这篇讲它是什么,也讲关键设计背后的取舍。

一、从痛点说起

监控体系搭起来之后,最先崩的往往不是系统,是人。Prometheus、Grafana、Alertmanager、云监控、飞书机器人……每个都能发告警,于是一线收到的是:同一个故障被十几条告警反复轰炸、半夜的抖动尖峰和真正的 P0 混在一起、飞书群一天几百条没人看得过来。

告警疲劳的本质是信噪比太低。真正需要的不是「更多告警」,而是一层中间大脑:接住所有来源的告警,判断哪些重要、哪些是重复、哪些是噪音,只把该看的、带着上下文的,精准推给该看的人。

WebhookWise 就是这层大脑。它不是一个「收到 webhook 转发出去」的简单中继,而是一个小型 AIOps 控制面。

项目地址:https://github.com/itswl/WebhookWise

二、整体架构

技术栈

  • API / 接收层:FastAPI,只做鉴权、限流、入队、基础落库,快速返回。
  • 异步队列:TaskIQ + Redis Stream(RedisStreamBroker),是「接收」和「处理」之间的解耦带,也是耐久性边界。
  • Worker 管道:归一化 → 去重 → AI/规则分析 → 降噪 → 转发决策 → 事务性投递,全在 worker 里。
  • Scheduler:定时任务(日/周/月报、数据归档维护),TaskIQ cron,带缺失补偿(missed-fire catch-up)。
  • 存储:PostgreSQL(事件、决策追踪、outbox、深度分析)+ Redis(去重记账、限流、缓存、队列)。
  • AI:OpenAI 兼容接口(instructor 做结构化输出)+ 可选 OpenClaw 深度分析(延迟任务轮询)。
  • 可观测性:OpenTelemetry-first,OTLP 出口对接 Alloy / Prometheus / Tempo / Loki / Pyroscope。

组件拓扑

1
2
3
4
5
6
7
8
9
10
11
12
                         ┌──────────────── OpenTelemetry (OTLP) ────────────────┐
│ ▼
告警源 ──HTTP──▶ FastAPI (webhook-service) ──入队──▶ Redis Stream ──▶ Worker 管道
(Prom/Grafana/ │ 鉴权·限流·落库·200OK (TaskIQ broker) │
Alertmanager/ │ │ 归一化→去重→分析→降噪→转发决策
飞书/任意源) ▼ ▼
PostgreSQL ◀──────────────── 同事务写入 ────────── 事务性 Outbox
▲ │ 独立消费·重试·幂等
Scheduler (日/周/月报·归档) ▼
飞书 / OpenClaw / 通用 Webhook

可观测出口:webhook-service + worker + scheduler ──OTLP──▶ Alloy → Prometheus/Tempo/Loki/Pyroscope → Grafana

一条告警的完整流程

一条 webhook 进来,会依次经过(括号是管道里对应的处理阶段):

  1. 接收parse_request):验签/鉴权、限流、按来源归一化成统一内部结构(各来源一个 adapter),入队,立刻返回 200 OK
  2. 去重resolve_dedup):算告警指纹(identity → alert_hash / dedup_key),时间窗内重复的收敛,只在状态翻转时继续。原子 Lua 记账 + Postgres advisory lock 串行化同指纹。
  3. 分析_run_fresh_analysis / 降级到 analyze_with_rules):优先 LLM 结构化判定(重要性/摘要/根因线索),熔断打开或失败时退回规则分析。
  4. 降噪compute_noise):识别相关告警组(同根因/级联),抑制从属告警。
  5. 转发决策decide_forwarding):结合重要性、匹配的转发规则、静默规则,决定转发还是跳过(带跳过原因码)。
  6. 落库 + 投递意图persist_and_schedule):事件落库、生成 outbox 记录(同事务),由独立 worker 消费 outbox 做实际投递(重试/退避/幂等)。
  7. 决策追踪record_decision_trace):把整条决策链落一条 trace(SAVEPOINT 隔离,写失败不阻断转发)。

慢的部分(AI、投递)都在入队之后异步做;快返回和耐久性由「入队成功」这条边界保证。

三、几个核心设计,以及为什么这么选

1. 接收即入队:把「耐久性边界」画在最前面

API 层只做四件事:鉴权、限流、入队、基础落库,然后立刻返回 200 OK。真正耗时的处理(归一化、AI 分析、转发)全部丢进 TaskIQ + Redis Stream 异步做。

取舍:上游(Prometheus 之类)的 webhook 发送通常有超时和重试,你在请求里同步跑 AI 分析(几百毫秒到数秒),上游会超时重发,造成重复、雪崩。所以耐久性边界画在「入队成功」——一旦进了队列就保证不丢,剩下的慢慢做。这也意味着入队之前的失败要对上游可见(返回非 2xx 让它重试),入队之后的失败由内部重试兜底。这条边界想清楚了,整个可靠性模型就清晰了。

2. AI 分析 + 规则分析的双轨,和「熔断降级」

每条告警会走一次分析:优先用 LLM 做结构化判断(重要性、摘要、根因线索),拿不到就退回确定性的规则分析(关键词/阈值)。

取舍:LLM 是整条链路里最慢、最不稳定的一环。它超时、限流、抽风,不能拖垮告警投递——告警系统自己挂了是最讽刺的事。所以 AI 调用外面套了个熔断器:连续失败到阈值就 OPEN,之后一段时间直接走规则分析、不再打 LLM,静默降级;等冷却窗口过了再半开试探。宁可某段时间「判得糙一点」,也不能因为模型挂了让告警发不出去。

一个诚实的边界:AI 判定目前没有「人工对不对」的 ground truth,所以我们不吹「准确率」,只暴露代理信号——有多少是新鲜 AI 判定(vs 命中缓存/复用/规则)、规则纠正 AI 重要性的比率(override rate)、降级率。可观测但不自欺。

3. 去重与降噪:把「同一件事」收敛成一条

同一个故障会以多种形态反复进来。去重基于告警指纹(identity → alert_hash / dedup_key),在一个时间窗内把重复的收敛,只在状态翻转时通知。降噪则更进一步,识别「相关的一组告警」(同根因、级联),抑制从属告警。

踩过的坑:去重是「读—改—写」,多 worker 并发下非原子就会重复计数、重复转发。解决办法是把去重记账做成 Redis 里的原子 Lua 脚本(单次往返、EVALSHA 缓存),再加 Postgres 事务级 advisory lock 让同一指纹的处理串行化。并发正确性这种事,测试要在真实 Postgres上验——advisory lock 在 SQLite 上是 no-op,单测过了不代表生产对。

4. 事务性 Outbox:把业务状态和外部副作用解耦

「事件已处理」是数据库里的事实,「飞书已收到」是一次可能失败的外部 HTTP。把这两件事塞进一个流程里,任何一步失败都会让状态不一致。

WebhookWise 用事务性 outbox:转发决策产生一条 outbox 记录(和事件落库同事务),投递由独立的 worker 消费 outbox 做,带重试、指数退避、幂等键。投递失败不影响「事件已处理」这个事实,只是 outbox 那条还没到终态。这样「为什么这条转发了但飞书没响」变成一个可查询、可重放的状态,而不是一次性丢失的副作用。

转发渠道(飞书 / OpenClaw / 通用 webhook)用策略注册表收敛,加新渠道不用改投递主流程——早期是一串 if/elif,重构成注册表之后干净多了。

5. 决策可追溯:每条告警「为什么转发/为什么跳过」

这是我自己最喜欢的一块。每条告警处理完,落一条 decision trace:一条有序的决策链(去重→分析→降噪→静默→规则匹配→转发),加上扁平化的结果字段(转发/跳过、跳过原因码、命中的规则、是否被静默、投递状态)。

取舍:这条 trace 写在事件落库的同一个事务里,但套在 SAVEPOINT 里——trace 写失败只回滚 trace,绝不阻断真正的转发决策。它是可观测记录,不是链路上的一道闸。扁平化那几个字段(结果/跳过码/路由)是为了大盘能便宜地 GROUP BY 聚合,而完整决策链塞进 JSONB 供逐条下钻。有了它,「这条为什么没发」从玄学变成一次查询。

6. 静默收益与「僵尸静默」

手动静默(snooze)是降噪的重要一环——运维知道某类告警在维护窗口内该闭嘴。但静默本身也需要被监控:一条静默规则在生命周期内到底挡掉了多少条告警?一条生效中却从没匹配过的静默,很可能是配错了、或早该删了(僵尸静默),甚至可能在悄悄吃掉本该报的告警。

所以静默页会显示每条规则的拦截数,把「0 拦截」的生效规则标红。这不是 nice-to-have——当大部分告警都被静默挡掉时,「静默会不会过度挡杀」是真实的风险。

四、可观测性:控制面自己也要被看见

一个降噪系统自己变成黑盒是很危险的。WebhookWise 是 OTel-first 的:指标、trace、日志、事件串在一起。但有一条自律的原则——不为了「有」而加指标:每一个 metric 都要有明确的消费方(大盘、告警规则、SLO、或自动决策),否则不加。指标的价值在于被用,不在于数量。

五、诚实的部分:一次回退

不是所有设计都留下了。曾经做过一版「按严重度分双队列 + 优先 worker」,想让 P0 告警插队。技术上跑通了、也部署验证了,但最后回退了。原因:这套系统的转发决策依赖 AI 判定的重要性,而 AI 又恰是最慢的一步——真正的「零等待优先级」得把转发语义改成「先按规则快转、AI 再异步增强」,那会引入「规则转发 vs AI 转发」的去重问题,风险和复杂度不划算。

留着的是 batch 1-3(AI 熔断、备份/恢复、渠道注册表),砍掉的是队列分裂。能砍掉自己刚写的东西,也是设计的一部分——尤其当收益撑不起它带来的复杂度时。

六、小结

WebhookWise 想做的其实很朴素:把噪音挡在人和告警之间

  • 接收即入队,把耐久性边界画在最前;
  • AI + 规则双轨,熔断降级保证「模型挂了告警也不停」;
  • 原子去重 + 降噪,把同一件事收敛成一条;
  • 事务性 outbox,把「已处理」和「已送达」解耦、可重放;
  • 决策可追溯,每条告警的命运都能查;
  • 静默收益 + 可观测自律,让降噪系统自己不变黑盒。

一个 AIOps 控制面不需要很大,但每个决策点都得想清楚取舍——包括什么时候该把已经写好的东西删掉。