说明:告警系统的失效是最危险的失效------它失败时发出的信号是"安静"。本文按五段链路逐一分析单点故障,给出心跳看门狗、双终端冗余、演练制度与 MTTD/漏报度量四件套。代码以 Python 为主,可直接嵌入既有巡检体系,不放站外链接。
一、最危险的失效是"安静地失败"
Web 服务挂了,用户会喊;告警系统挂了,没有人会喊------这正是它存在的目的。它失效的方式不是报错,而是某天深夜该响的时候没响,然后故障在无人知晓中发酵,直到有人路过机房才被发现。这中间的全部时间,都是"你以为有监控,其实没有"的裸奔时间。
所以告警体系的第一性原则是:监控本身必须是全网被监控得最严密的系统。它的可用性目标应该比业务系统高一档,因为它失效的代价不是自己的功能不可用,而是所有业务系统失去发现故障的眼睛。
二、五段链路的单点分析
把告警链路摊开,逐段找"安静失效点":
text
[告警源] → [网络] → [终端] → [声光输出] → [人]
① ② ③ ④ ⑤
| 段 | 失效模式 | 为什么安静 | 对策 |
|---|---|---|---|
| ① 告警源 | 监控平台自身故障、规则被误删 | 平台死了没人发告警 | 平台纳入更外层监控(云拨测/另一套体系) |
| ② 网络 | 端口、交换机、ACL 变更 | 没有流量就没有报错 | 心跳流量常态化 |
| ③ 终端 | 掉电、固件卡死、IP 冲突 | 终端不会主动求救 | 看门狗轮询 + 就地自检 |
| ④ 声光输出 | 音量被调零、喇叭故障、通知组被关 | 灯还亮,只是没声 | 周播测试事件(真实发声) |
| ⑤ 人 | 手机静音、不在岗、疲劳 | 不可监控但可制度化 | 演练 + 交接清单 + 升级路径 |
第④段最容易被忽视:链路上所有设备都"在线",但音量被误调到 0 ------网络可达、API 正常、灯也闪,就是不响。能抓住这种失效的只有"真实发声测试":每周一次向终端推送一条测试告警,人耳或麦克风确认,自动化再严密也代替不了这一步。
三、心跳看门狗:让失效变得"响"
对付安静失效的核心手段是让链路里有常态流量:巡检端与终端之间双向心跳------巡检端定期探测终端,终端侧的关键状态被周期拉取。任何一端停跳,另一端在超时窗口内告警。
python
# watchdog.py ------ 告警终端双向心跳看门狗
import configparser
import datetime as dt
import json
import pathlib
import requests
CONF = configparser.ConfigParser()
CONF.read("bolin_ops.ini", encoding="utf-8")
BASE = CONF["device"]["base_url"]
STATE = pathlib.Path("watchdog_state.json")
FAIL_LIMIT = 3 # 连续失败 N 次才升级,防单次网络抖动
ESCALATE_WEBHOOK = CONF["watchdog"]["fallback_webhook"]
def load_state() -> dict:
if STATE.exists():
return json.loads(STATE.read_text(encoding="utf-8"))
return {"fails": 0, "last_ok": None}
def save_state(state: dict) -> None:
STATE.write_text(json.dumps(state, ensure_ascii=False), encoding="utf-8")
def probe_terminal() -> bool:
"""终端心跳:队列接口 2 秒超时,能答即活着"""
try:
r = requests.get(f"{BASE}/api/api/get_play_queue_size", timeout=2)
return r.status_code == 200
except requests.RequestException:
return False
def escalate(message: str) -> None:
"""第二通道:绝不经过被监控的终端本身"""
requests.post(ESCALATE_WEBHOOK,
json={"msgtype": "text",
"text": {"content": f"[看门狗] {message}"}}, timeout=3)
if __name__ == "__main__":
state = load_state()
if probe_terminal():
if state["fails"] >= FAIL_LIMIT:
escalate(f"{dt.datetime.now():%T} 终端恢复(此前连续失联 {state['fails']} 轮)")
state.update({"fails": 0, "last_ok": dt.datetime.now().isoformat(timespec="seconds")})
else:
state["fails"] += 1
if state["fails"] == FAIL_LIMIT:
minutes = FAIL_LIMIT * 5 # 与 crontab 每 5 分钟一轮对应
escalate(f"{dt.datetime.now():%T} 终端失联约 {minutes} 分钟,"
f"最后存活 {state['last_ok']} ------ 现场声光不可用,请立即处置")
save_state(state)
配套 crontab:*/5 * * * *。三个设计要点:连续失败 N 次才升级 (单次抖动不叫人);升级走第二通道 (IM webhook,不经终端);恢复也通知(失联期间的告警盲区需要人工回看,不能恢复得无声无息)。
四、双终端冗余:一盏灯倒下,另一盏接手
关键机房可以上双终端:两台设备接同一套告警源,正常时主终端播报、备终端静默跟灯;看门狗发现主终端失联,自动把发送目标切到备终端。
python
# failover.py ------ 双终端故障切换:目标列表 + 健康优先
import itertools
import requests
TARGETS = ["http://192.168.0.66", "http://192.168.0.67"] # 主 / 备
_active = itertools.cycle([0]) # 当前主选索引,运行期可被切换
def active_index() -> int:
idx = next(_active)
# 主选连续探活,失败则顺延到下一个可用终端
for offset in range(len(TARGETS)):
cand = (idx + offset) % len(TARGETS)
try:
requests.get(f"{TARGETS[cand]}/api/api/get_play_queue_size", timeout=2)
return cand
except requests.RequestException:
continue
raise RuntimeError("全部终端不可达 ------ 走第二通道")
def send(payload: dict) -> None:
requests.post(f"{TARGETS[active_index()]}/api/api/user_msg/alert",
data=payload, timeout=3).raise_for_status()
双终端的三个注意:两台设备播同一文案但重复次数减半 (各播 2 次代替主播 4 次,避免双响放大噪音);备终端配置定期同步 (配置备份/还原或脚本比对,漂移的备机比没有备机更危险);切换要有记录,否则故障早已发生而无人知晓。
五、演练制度:没拔过线的链路等于没验证过
纸面冗余不等于可用冗余。月度演练把失效模式变成肌肉记忆:
| 演练项 | 动作 | 通过标准 |
|---|---|---|
| 终端失联 | 拔主终端网线 | 看门狗 ≤15 分钟第二通道告警;切换脚本可切备机 |
| 声光测试 | 推送测试播报 | 最远值班位人耳确认音量与清晰度 |
| 通道演练 | 停掉主告警源 10 分钟 | 看门狗/对账发现缺口,恢复后补报流程走通 |
| 升级路径 | 值班员接报后按手册处置 | 10 分钟内找到人、找到手册、找到设备 |
| 恢复复核 | 恢复后回看盲区 | 失联期间的告警缺口清单已补齐 |
演练记录归档------"上次拔线是什么时候、结果如何"答不上来的团队,等于没有冗余,只有多买了一台设备的错觉。
六、度量:MTTD、漏报率与盲区时长
可用性要被数字盯着:
- MTTD(故障发生到被发现的时长):目标是分钟级,靠心跳与对账压下来;
- 漏报率:发送方已提交而设备侧无播报记录的比例(专栏日志篇的对账数据直接可用),健康线为零;
- 盲区时长:终端不可用但无人知晓的累计时间------看门狗报表的核心输出;
- 演练覆盖率:五种失效模式最近一次演练距今天数,超过 30 天即标红。
这四个数字进月度运维报表,与业务系统的可用率放在一起汇报------告警体系的可用性值得一个正式的 SLO,比如"月度盲区时长 ≤ 15 分钟"。
七、常见坑
- 看门狗走被监控的通道 → 终端挂了看门狗也哑,第二通道是底线;
- 单次失败即升级 → 网络抖动三分钟叫醒一次值班长,两周后看门狗被静音;
- 备终端从不验证 → 切换那天发现备机配置是半年前的,等于裸奔;
- 声光测试只看接口返回 → 接口 200 不代表喇叭响了,音量归零是经典失效;
- 演练变成签字表演 → 不真拔线、不真停源的演练是仪式,不是验证;
- 恢复无通知 → 失联与恢复都安静,盲区时长永远算不出来;
- 监控平台自身裸奔 → Zabbix/Prometheus 挂了谁发现?用体系外的拨测盯平台本身;
- 度量缺失 → 没有 MTTD 与漏报率的告警体系,可用性全凭感觉。
八、验收清单
- 看门狗上线:拔线 15 分钟内第二通道告警,恢复有通知;
- 双终端切换演练通过,备机配置与主机一致(比对工具输出为零差异);
- 周度真实发声测试:最远值班位人耳确认;
- 月度五项演练全部执行,记录归档;
- MTTD、漏报率、盲区时长、演练覆盖四项指标进月报;
- 告警监控平台自身有体系外的存活探测。
九、小结
告警链路的可用性工程,说到底是三句话:让失效变响 (心跳与看门狗把安静失效变成可捕获事件)、让冗余真实 (双终端要切换、要同步、要演练)、让盲区可量(MTTD、漏报率、盲区时长进报表)。设备可以是单点,但体系不能是单点------因为这套系统的客户是"下一次故障时的你",而它失职的样子,永远是沉默的。
至此,误报(上一篇)与漏报(本篇)这对矛盾都有了各自的工程解。下一篇用一个真实的深夜漏报事故,把这条链路上的七个断点串成一次完整的复盘------所有的方法论,最终都要在事故现场接受检验。