# 高可用与故障域

多实例部署能扛住哪些故障、扛不住哪些，以及怎么演练。

最后校对于 2026-10-10

夜莺的高可用没有额外组件：**多跑几个 `n9e`，配置一致，共享同一套 MySQL 和 Redis**。 这页讲这套机制到底保护了什么，哪些东西它一点都没保护，以及怎么在没出事的时候先验一遍。

## 集群怎么组[​](#集群怎么组 "集群怎么组的直接链接")

在几台机器上分别部署 `n9e`，配置文件保持一致，指向同一个数据库和同一个 Redis。 没有主节点要选，没有集群要初始化，也没有第三个组件要装。

必须一致的只有三段：

```
[DB]

DSN = "n9e:<password>@tcp(mysql:3306)/n9e_v6?charset=utf8mb4&parseTime=True&loc=Local"



[Redis]

RedisType = "standalone"

Address = "redis:6379"



# 同一个引擎集群里的实例才会互相分担规则

[Alert.Heartbeat]

EngineName = "default"
```

`[Alert.Heartbeat] IP` 留空时会自动探测本机出向 IP，实例标识就是 `<IP>:<HTTP 端口>`。 容器里跑、或者机器有多张网卡时，显式写死它，避免实例标识跟着网络环境漂。

**系统配置 → 告警引擎**这页列的就是当前活着的实例，按引擎集群分组， 「上次心跳时间」不再往前走，就是这个实例出问题了。

## 规则是怎么分派的[​](#规则是怎么分派的 "规则是怎么分派的的直接链接")

每个实例每秒往 `alerting_engines` 表写一次心跳（`[Alert.Heartbeat] Interval`，默认 1000 毫秒）。 **30 秒内有心跳的实例算活的**，活实例列表被组成一个一致性哈希环， 每条告警规则按规则 ID 落到环上的某个实例——所以一条规则在同一时刻只会有一个实例在跑， 不会重复告警。

哈希环是按数据源分别建的：一个实例只参与它关联的那些数据源的分派。 超过 600 秒没心跳的行，中心进程每 10 分钟清理一次。

故障接管的时间是这么来的：

| 阶段                     | 耗时                     |
| ------------------------ | ------------------------ |
| 实例挂掉到被判定为不活跃 | 最多 30 秒               |
| 哈希环重建（下一次心跳） | 约 1 秒                  |
| 接管方开始判定           | 立即，除非它也是刚重启的 |

进程**刚启动后会等 30 秒才开始判定**（`[Alert] EngineDelay`，默认 30 秒）， 留给配置从数据库同步进内存。滚动重启时要把这一段算进去：一次重启一个实例， 等新实例出现在告警引擎页上再动下一个。

## 能扛住什么，扛不住什么[​](#能扛住什么扛不住什么 "能扛住什么，扛不住什么的直接链接")

| 故障                      | 结果                                                                                                 |
| ------------------------- | ---------------------------------------------------------------------------------------------------- |
| 一个 `n9e` 实例挂了       | 30 秒内它的规则被别的实例接管；这段时间这批规则不判定                                                |
| 一个 `n9e` 实例被重启升级 | 同上，另加新实例启动后的 30 秒判定延迟                                                               |
| 元数据库（MySQL）不可用   | **全站不可用**——规则、用户、事件都在这里，没有降级模式                                               |
| Redis 不可用              | **启动时连不上 Redis 进程直接退出**；运行中断开则登录态、心跳缓存大面积异常                          |
| 某个数据源不可用          | 只影响用到它的规则；判定报错会记在判定记录里                                                         |
| 通知媒介不可用            | 事件照常产生，通知记录里是失败状态                                                                   |
| 内置时序库所在实例挂了    | 这段时间的指标写不进去，历史数据也查不到                                                             |
| 中心与边缘机房断链        | 边缘继续本地判定和通知，见[边缘断网行为](https://n9e.github.io/zh/docs/operations/edge-partition.md) |

结论很直接：**多实例只解决了 `n9e` 进程这一个故障域**。 MySQL 和 Redis 的高可用得各自单独做，夜莺不替它们兜底。

## 必须先关掉内置时序库[​](#必须先关掉内置时序库 "必须先关掉内置时序库的直接链接")

内置时序库的数据在**单个 Center 进程的本地磁盘**上。多副本时每个副本只有一部分数据， 而且它们会互相改写自动注册的那个数据源地址，查询结果会静默缺失——不报错，只是图上少一截。

```
[EmbeddedTSDB]

Enable = false
```

Center 启动时如果发现同一个引擎集群里还有别的活跃实例，会打一条 warning 日志提醒这件事。 迁移办法见[内置时序库的单 Center 限制](https://n9e.github.io/zh/docs/operations/embedded-tsdb-limits.md)。

## 怎么演练[​](#怎么演练 "怎么演练的直接链接")

在没出事的时候做一遍，比出事时读文档强。

1. **确认基线。** 打开告警引擎页，记下实例列表；`curl --noproxy '*' http://n9e:17000/metrics | grep n9e_alert_rule_eval_total` 记下判定总次数。
2. **停掉一个实例。** `kill` 掉其中一个 `n9e` 进程（不要 `kill -9`，正常退出才走优雅关闭）。 **预期结果**：30 秒内告警引擎页上这个实例消失。
3. **确认规则还在跑。** 在剩下的实例上再取一次 `n9e_alert_rule_eval_total`， **预期结果**：这个数在继续增长，而且增速比停机前更快（它多接了一批规则）。
4. **确认告警还能出来。** 提前建一条阈值设得必然满足的规则，让它每个周期都触发， **预期结果**：停掉一个实例后，这条规则仍然按周期产生事件并发出通知。
5. **把实例拉回来。** **预期结果**：约 30 秒后它重新出现在告警引擎页上， 再过一个判定周期，`n9e_alert_rule_eval_total` 在两边都在涨。

演练里最容易被忽略的一步是第 4 步。实例活着、指标在涨，不等于通知链路是通的—— 通知媒介、模板、通知规则任何一环配错，前三步都会显示「正常」。

## 相关[​](#相关 "相关的直接链接")

* [生产拓扑](https://n9e.github.io/zh/docs/operations/topology.md)
* [内置时序库的单 Center 限制](https://n9e.github.io/zh/docs/operations/embedded-tsdb-limits.md)
* [升级与数据库迁移](https://n9e.github.io/zh/docs/operations/upgrade.md)
* [监控夜莺自身](https://n9e.github.io/zh/docs/operations/self-monitoring.md)
