结论先行:分母健康度监控的本质不是「再设一条比率规则」,而是把「流量本身」当作一个独立的健康信号,用双基线滑动 Z-score + 绝对量下限兜底两层来判定,阈值不拍脑袋、按指标自身历史波动自适应。下面给你一套我在线上 B 端 SaaS 实际跑通的方案,带具体数字和 SQL。
一、核心思路:两层判定,各司其职
| 层 | 目的 | 适用场景 | |---|---|---| | 第一层:绝对量下限兜底 | 分母低于「业务最小有效量」时直接告警(不抑制) | 网关挂、上游断流——分母从 12 万跌到 1.8 万 | | 第二层:双基线 Z-score | 分母没破下限但「相对自身历史异常偏低」 | 限流、只限了某一路正常流量——分母从 12 万跌到 8 万,比率没变 |
两层是 OR 关系:任一层触发就告警。关键原则:min_sample 门槛只控制「比率是否可计算」,不控制「分母健康度是否告警」——这两件事必须解耦。
二、第一层:绝对量下限(兜底,最简单)
怎么定阈值:取该指标过去 28 天所有 5 分钟窗口中,P5(第 5 百分位)的分母值作为下限。
为什么用 P5 而不是 P1?因为 P1 会被极少数「合法极低流量」窗口(比如凌晨 03:00 的某个长尾指标)拉到极低,告警阈值就形同虚设。P5 保留了 5% 的缓冲。
具体数字举例(以你的支付成功率为例):
- 过去 28 天,5 分钟窗口共 20160 个样本
- 分母 P50 ≈ 2000(白天典型值),P5 ≈ 380(凌晨低谷)
- 所以绝对量下限 = 380
- 规则:
分母 < 380→ 直接 P1 告警「支付请求量异常偏低」,不管比率是多少
这条规则永远生效,不受 min_sample 抑制。你那次网关挂 38 分钟,分母从 12 万/h 掉到 1.8 万/h,5 分钟窗口分母 ≈ 1500 → 低于 380?没有。但 1.8 万/h ÷ 12 = 1500/min,如果窗口是 5 min 则分母 ≈ 7500,高于 380——所以这条兜底对「大幅下跌但还没到零」的场景需要第二层兜住。
修正:如果你的窗口是 5 分钟,P5 可能偏高。建议用 1 分钟窗口算 P5 再乘以窗口倍数,或者直接按 1 分钟粒度监控分母:
- 1 分钟分母 P5 ≈ 80
- 规则:
1 分钟分母 < 80→ 告警
三、第二层:双基线 Z-score(精确,区分「合法低」和「异常低」)
公式:
z = (current - baseline) / std
baseline = 0.5 × yesterday_same_window + 0.5 × last_week_same_window
std = max(1, 0.5 × std_yesterday + 0.5 × std_last_week)
current:当前 5 分钟窗口的分母yesterday_same_window:昨天同一 5 分钟窗口的分母(注意是同一「分钟槽位」,不是同一小时)std_*:过去 28 天该窗口位的分母标准差
告警条件:z < -3(即当前分母比双基线均值低 3 个标准差)
为什么用双基线而不是单基线:
- 只看昨天:如果昨天恰好有活动/故障,基线被污染
- 只看上周:周末 vs 工作日差异大
- 双基线取均值:两边各取 50% 权重,单侧异常被另一侧修正
凌晨/节假日误报怎么压:
- 关键在 std 是自适应的。凌晨 03:00 的 std 本身就很小(流量稳定),所以 z-score 的阈值实际上比白天更敏感——但这是对的,因为凌晨本来就不该有波动。
- 如果凌晨 03:00 分母从 50 掉到 30,std=8,z=(30-50)/8=-2.5,不告警(正常波动)
- 如果凌晨 03:00 分母从 50 掉到 5,std=8,z=(5-50)/8=-5.6,告警(确实异常)
- 所以不需要为凌晨单独调阈值,std 自适应已经帮你做了这件事
节假日处理:
- 双基线里「上周同窗口」天然覆盖了「上周同一天是节假日」的情况
- 如果当天是节假日且上周也是,两个基线都是节假日水位,z-score 正常
- 如果当天是节假日但上周不是(比如春节 vs 普通周六),建议把基线权重调为 0.7 周同 + 0.3 昨同,减少被非节假日基线拉偏
四、ClickHouse SQL 实现(可直接跑)
-- 表: metrics_1m (metric_name, ts, numerator, denominator)
-- 告警窗口: 5 分钟
WITH current AS (
SELECT
metric_name,
ts AS window_ts,
sum(denominator) AS curr_denom,
sum(numerator) AS curr_numer
FROM metrics_1m
WHERE metric_name = 'pay_success_rate'
AND ts >= now() - INTERVAL 5 MINUTE
AND ts < now()
GROUP BY metric_name, ts
),
baseline AS (
SELECT
c.metric_name,
c.window_ts,
c.curr_denom,
-- 昨天同窗口
(SELECT sum(denominator) FROM metrics_1m
WHERE metric_name = 'pay_success_rate'
AND ts >= toDate(now()) - 1 + toTime(hour(c.window_ts), minute(c.window_ts) - minute(c.window_ts) % 5)
AND ts < toDate(now()) - 1 + toTime(hour(c.window_ts), minute(c.window_ts) - minute(c.window_ts) % 5 + 5)) AS y_denom,
-- 上周同窗口
(SELECT sum(denominator) FROM metrics_1m
WHERE metric_name = 'pay_success_rate'
AND ts >= toDate(now()) - 7 + toTime(hour(c.window_ts), minute(c.window_ts) - minute(c.window_ts) % 5)
AND ts < toDate(now()) - 7 + toTime(hour(c.window_ts), minute(c.window_ts) - minute(c.window_ts) % 5 + 5)) AS w_denom,
-- 过去 28 天同窗口位的 std
(SELECT stddevPop(denominator) FROM metrics_1m
WHERE metric_name = 'pay_success_rate'
AND toStartOfFiveMinute(ts) = toStartOfFiveMinute(c.window_ts)
AND ts >= toDate(now()) - 28
AND ts < toDate(now()) - 1) AS std_denom
FROM current c
)
SELECT
metric_name,
window_ts,
curr_denom,
0.5 * y_denom + 0.5 * w_denom AS baseline_denom,
(curr_denom - (0.5 * y_denom + 0.5 * w_denom))
/ greatest(1, std_denom) AS z_score,
-- 第一层兜底: 绝对量下限 (按你的 P5 填)
curr_denom < 80 AS absolute_floor_breach,
-- 第二层: Z-score
(curr_denom - (0.5 * y_denom + 0.5 * w_denom)) / greatest(1, std_denom) < -3 AS z_breach,
-- 综合告警: 任一层触发
curr_denom < 80
OR (curr_denom - (0.5 * y_denom + 0.5 * w_denom)) / greatest(1, std_denom) < -3
AS should_alert
FROM baseline;
实际部署时建议把
current和baseline拆成物化视图或定时任务(每 5 分钟跑一次),不要实时 JOIN 28 天数据。
五、「分子分母同步下降」的归因:一条判定式
你提到分不清「流量正常收缩」和「真实事故」。其实加一条分子独立健康度就能区分:
分子独立健康度 = z_score(numerator)
| 分母 z | 分子 z | 判定 | |---|---|---| | 异常低 (< -3) | 同步异常低 (< -3) | 流量收缩(网关限流/上游降载),比率没变,P2 告警 | | 异常低 (< -3) | 正常 (> -1) | 分母异常(埋点丢包/路由错误),P1 告警 | | 正常 (> -1) | 异常低 (< -3) | 成功率真实下降,P1 告警 | | 正常 | 正常 | 无告警 |
这条判定式直接解决了你「分不清分子掉是因为流量掉还是成功率掉」的问题——看分子自己的 z-score 跟不跟分母走。
六、落地建议(按优先级)
- 本周:给每个核心比率指标加第一层「绝对量下限」规则(P5 兜底),5 行 SQL 搞定,立刻解决「网关挂 38 分钟无告警」的结构性盲区
- 两周内:上线第二层双基线 Z-score,先只开 P2(不打电话),观察 1 周误报率,调权重
- 一个月内:加上「分子独立健康度」归因判定,把告警文案从「分母异常」细化为「流量收缩 vs 分母异常 vs 成功率下降」三种,值班同学看到告警就知道先查什么
关键原则:min_sample 门槛只管「比率能不能算」,分母健康度是独立的监控对象,两者解耦。你现在的问题本质是把它俩绑在一起了。