# Pipeline 为什么路由到了错误的目的地？

通知发错人，通常是目的地在链路里被决定的位置和预期不一样：处理器顺序、匹配之前的标签改写、两个挂载点的作用域差异。

最后校对于 2026-10-10

通知发出去了，但发给了错误的人：本该只进值班群的进了全员群、本该被屏蔽的还是响了、 同一条事件收到两遍、或者标签改写完之后反而没人收到。 这类问题的共同点是**目的地在链路的哪一步被决定的，和你以为的不一样**。

紧急程度按方向判断：**多发**（发给了不该收的人）是噪音，可以慢慢查； **漏发**（该收的人没收到）是盲区，先按 [有告警事件，为什么收不到通知？](https://n9e.github.io/zh/docs/troubleshooting/no-notification.md) 止血， 再回来查路由。

## 先看通知记录：这一条是谁发出去的[​](#先看通知记录这一条是谁发出去的 "先看通知记录：这一条是谁发出去的的直接链接")

**告警事件 → 打开这条事件 → 通知记录**。每条记录都写明了它是**哪条通知规则**、 **哪个媒介**发出去的。先把这两个值抄下来，问题范围立刻小一半：

| 通知记录里看到的                     | 说明目的地是在哪决定的                                                                                                   |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| 只有一条记录，通知规则不是你想的那条 | 告警规则上挂的通知规则选错了                                                                                             |
| 有多条记录，通知规则 ID 不同         | 这条规则挂了多条通知规则，**它们各发各的**                                                                               |
| 记录出现在「订阅规则通知」那张表里   | 是订阅扩散出来的副本，不是原规则发的                                                                                     |
| 记录的渠道显示「屏蔽」               | 命中了「只屏蔽通知」的屏蔽规则                                                                                           |
| 一条记录都没有，但人收到了消息       | 事件是在 edge 上产生的，见 [Edge 为什么与中心失联？](https://n9e.github.io/zh/docs/troubleshooting/edge-disconnected.md) |

## 目的地是在哪一步决定的[​](#目的地是在哪一步决定的 "目的地是在哪一步决定的的直接链接")

```
规则判定产生事件

      │

      ├─① 告警规则上挂的工作流   ← 改这里的标签，下游所有人都看到

      │

      ├─② 屏蔽规则               ← 用的是 ① 改完之后的标签

      │

      ├─③ 落库；订阅规则在这里扩散出副本

      │

      └─④ 逐条通知规则（挂几条跑几条，互不影响）：

            ├─ 通知规则上挂的工作流  ← 改这里的标签，只有这一条通知规则看得到

            ├─ 按 时段 → 级别 → 标签 → 属性 依次匹配

            └─ 逐条通知配置选媒介和模板，发送
```

三个决定目的地的地方，按被误解的频率排序：

1. **④ 里的通知配置筛选**——真正决定「发给谁」的是这里，不是告警规则上的接收人；
2. **① 的标签改写**——它改完的标签会被 ② 和 ④ 全程使用；
3. **③ 的订阅扩散**——它只加不减，原规则该发的照发。

完整模型见 [降噪与路由模型](https://n9e.github.io/zh/docs/concepts/noise-routing.md)。

## 两个挂载点，作用域完全不同[​](#两个挂载点作用域完全不同 "两个挂载点，作用域完全不同的直接链接")

工作流有两个挂载点，日志里能直接看出是哪一个：`processor_by_` 后面跟的 就是 `alert_rule` 或 `notify_rule`。

```
INFO dispatch/dispatch.go:290 processor_by_notify_rule_id:1 pipeline_id:1, pipeline executed, status:success, message:
```

|                    | 挂在告警规则上                                         | 挂在通知规则上                                                                             |
| ------------------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| 日志里的标识       | `processor_by_alert_rule_id:<规则ID>`                  | `processor_by_notify_rule_id:<通知规则ID>`                                                 |
| 改标签影响谁       | 屏蔽规则、订阅、**所有**通知规则                       | **只有这一条**通知规则的这一次发送                                                         |
| 丢弃事件的效果     | 事件不落库，谁都收不到                                 | 只有这一条通知规则不发，别的照发                                                           |
| 丢弃时写通知记录吗 | **不写**，只在判定记录里留一个 `drop_by_pipeline` 阶段 | **写**一条失败记录，详情是 `processor_by_notify_rule_id:1 pipeline_id:3, drop by pipeline` |

**每条通知规则拿到的都是事件的一份深拷贝**，所以在通知规则 A 的工作流里改标签， 通知规则 B 完全看不到。「我改了标签，为什么另一个群还是老样子」的答案通常就是这一条： 挂错了挂载点。

## 标签改写发生在匹配之前[​](#标签改写发生在匹配之前 "标签改写发生在匹配之前的直接链接")

这是本页最容易反直觉的一点：**两个挂载点上的标签改写，都跑在用它匹配之前**。

* 挂在告警规则上的工作流跑完，**屏蔽规则才开始判断**。所以一个把 `env=prod` 改写成 `env=production` 的处理器，会让所有按 `env=prod` 写的屏蔽规则集体失效—— 现象是「我明明配了屏蔽，还是响了」；
* 挂在通知规则上的工作流跑完，**通知配置的标签筛选才开始判断**。所以在这里加标签， 是可以用来做路由的：加完的标签下一步就能被筛选用上。

反过来，工作流内部的处理器顺序也一样重要：**先改写后丢弃**和**先丢弃后改写**不是一回事。 事件被丢弃之后，后面的处理器一律不跑，执行记录里也不会有它们的输出。

## 常见根因与确认方法[​](#常见根因与确认方法 "常见根因与确认方法的直接链接")

### 多发：同一条事件被两条通知规则各发一遍[​](#多发同一条事件被两条通知规则各发一遍 "多发：同一条事件被两条通知规则各发一遍的直接链接")

通知规则之间**没有优先级、没有「先匹配到就停」**。告警规则上挂了几条通知规则， 就依次跑几条；一条通知规则里配了几条通知配置，每条匹配上都会发。

**怎么确认**：日志里搜这条事件的哈希，`notify rule ids:` 会为每条通知规则各打一行：

```
INFO dispatch/dispatch.go:176 notify rule ids: 1, event: 63bfcd082b52afc11f504509aea89de0
```

出现几行就是跑了几条。通知记录的条数应该和它对得上。

**怎么修**：不要靠「删掉一条通知规则」来去重——那会连带影响别的告警规则。 在多余的那条通知配置上加标签筛选或级别筛选，把这类事件排除掉。

### 改了标签之后屏蔽规则不再命中[​](#改了标签之后屏蔽规则不再命中 "改了标签之后屏蔽规则不再命中的直接链接")

**怎么确认**：打开事件详情看**标签**那一栏，那是工作流跑完之后的结果； 再打开屏蔽规则的过滤条件，两边逐个键值对比。对不上就是它。 判定记录里那条事件的阶段也不会是 `muted`。

**怎么修**：二选一——把屏蔽规则改成按改写后的标签写，或者把改写挪到通知规则的挂载点上 （那样屏蔽规则看到的还是原始标签）。前者更好，因为改写后的标签才是这套系统里的事实。

### 工作流根本没进去[​](#工作流根本没进去 "工作流根本没进去的直接链接")

工作流有自己的**生效范围**（适用标签、适用属性），不满足就整条跳过。 这跟「跑了但没效果」的排查方向完全相反，先分清。

**怎么确认**：日志里这四行，措辞固定，直接 grep `pipeline_id:`：

```
processor_by_notify_rule_id:1 pipeline_id:1, event pipeline not applicable, event: <哈希>

processor_by_notify_rule_id:1 pipeline_id:1, event pipeline is disabled, event: <哈希>

processor_by_notify_rule_id:1 pipeline_id:1, event pipeline not found, event: <哈希>

processor_by_notify_rule_id:1 pipeline_id:1, pipeline executed, status:success, message:
```

* `not applicable` —— 生效范围没匹配上，**这是最常见的一条**；
* `is disabled` —— 工作流被禁用了（禁用不会解除引用，规则上那一项还在）；
* `not found` —— 工作流被删了，引用变成了死链；
* `pipeline executed` —— 真的跑了，那就去执行记录里看它做了什么。

也可以从 UI 走：**工作流 → 执行记录**，先把时间范围拉宽（默认只有 6 小时）， 用事件 ID 搜。**一条记录都没有，就是没进去。**

**怎么修**：把生效范围的条件放宽，或者干脆关掉过滤开关—— **过滤开关关着的时候，所有事件都进来**。

### 通知配置的筛选顺序[​](#通知配置的筛选顺序 "通知配置的筛选顺序的直接链接")

匹配是**时段 → 级别 → 标签 → 属性**依次进行，**遇到第一个不匹配就返回**。 所以看到 `not match severity filter`，说明时段那一关已经过了——这个顺序本身就是线索。

**怎么确认**：四种拒绝原因措辞固定：

```
event time not match time filter

event severity not match severity filter

event tag not match tag filter

event attributes not match attributes filter
```

**这四行是 ERROR 级别打出来的，但它们是正常的筛选行为，不是故障。** 日志里看到一片红不用紧张。

匹配通过时也有一行，`notify_config` 后面把这条通知配置的全部筛选条件都打了出来， 比翻界面快：

```
INFO dispatch/dispatch.go:453 notify send timeMatch:true severityMatch:true tagMatch:true attributesMatch:true event:<哈希> notify_config:&{ChannelID:7 TemplateID:181 Params:map[] Type: Severities:[1 2 3] TimeRanges:[] LabelKeys:[] Attributes:[] ChannelIdent: UserNames:[] UserGroupNames:[]}
```

两个「空」的语义正好相反，是这一节的坑： **`TimeRanges:[]` 为空表示全时段生效**，而 **`Severities:[]` 为空表示永远不匹配**。 级别没有「不填即全选」。

## 确认修好了[​](#确认修好了 "确认修好了的直接链接")

不要等下一次真实告警。按这个顺序验：

1. **在通知规则的那条通知配置上点「运行测试」，选「历史事件」模式**，挑一条真出过问题的事件。 这个模式**会先跑一遍匹配条件**，不匹配会直接告诉你是哪一条不匹配——正好是本页要验的东西；
2. 收到之后回到**事件详情 → 通知记录**，确认通知规则 ID 和媒介都是你期望的那一组， 并且**条数**和期望一致（多发问题就看这个数）；
3. 涉及标签改写的，再去**工作流 → 执行记录**里打开这条记录，看标签改写节点的前后 diff；
4. 涉及屏蔽的，等一条真实事件，确认判定记录里的阶段变成了 `muted`。

「模拟事件」模式跳过所有筛选，验不了路由，只能验渲染和通道。

## 收集这些再去提问[​](#收集这些再去提问 "收集这些再去提问的直接链接")

1. 事件 ID / 哈希，以及完整的通知记录（记录里的目标已脱敏，可以直接给）；
2. 告警规则上「通知规则」这一项，和每条通知规则里每条通知配置的**时段、级别、标签、属性**四项；
3. 日志里这一时刻按事件哈希过滤出来的全部行，重点是 `processor_by_`、`notify rule ids:`、 `notify send timeMatch:` 和四种 `not match` ；
4. 工作流的生效范围配置，和这条事件对应的执行记录（含标签前后 diff）。

**脱敏**：webhook 地址、access token、手机号、邮箱一律替换； 标签的**键名要保留**（路由就是靠它们判定的），值可以替换成占位符。

## 下一步[​](#下一步 "下一步的直接链接")

* 完全没收到：[有告警事件，为什么收不到通知？](https://n9e.github.io/zh/docs/troubleshooting/no-notification.md)
* 试跑对了、线上不对：[工作流试跑成功，为什么生产执行失败？](https://n9e.github.io/zh/docs/troubleshooting/workflow-prod-fails.md)
* 链路的完整模型：[降噪与路由模型](https://n9e.github.io/zh/docs/concepts/noise-routing.md)
* 工作流怎么配：[事件 Pipeline](https://n9e.github.io/zh/docs/events/pipelines.md)
* 执行记录怎么读：[查看 Pipeline 执行记录](https://n9e.github.io/zh/docs/events/execution-records.md)
* 屏蔽规则的两种方式：[屏蔽规则](https://n9e.github.io/zh/docs/events/mute-rules.md)
