
AI 把 MySQL 主库宕机的 P0 告警归类成了"基础设施",发到了一个只有 1 人值班的群。那个人正在处理另一个故障,P0 告警被晾了 8 分 12 秒。 这本该是一条 5 分钟内必须响应的紧急故障。
如果你只读 30 秒,记住这三个数据就够了:
指标 | 手动分派 | AI 自动分类 | 改善 |
|---|---|---|---|
告警响应时间 | 15 分钟 😩 | 2 分钟 🚀 | ⬇️ 87% |
误分派率 | 28% 🤦 | 3% ✅ | ⬇️ 89% |
值班人员告警量 | 300 条/天 | 30 条/天 | ⬇️ 90% |
时间:上个月某天下午 地点:运维值班室
我所在的运维团队 7 个人,每天收到 300+ 条告警。真实故障混在大量噪声里,根本分不清哪个该马上看。
痛点 | 具体表现 |
|---|---|
告警风暴 | 一个数据库故障触发 80+ 条关联告警,手机震个不停 |
分派混乱 | 告警群发所有人,3 个组同时响应又互相推诿 |
升级滞后 | P0 告警和 P3 告警混在一起,P0 反而被忽略 |
响应疲劳 | 每天 300 条轰炸,开始选择性忽略 |
去年有一次 P0 故障,告警发出后 12 分钟 才有人响应——因为大家已经对告警麻木了。

时间:2026 年 5 月某个晚上 场景:AI 告警分类系统刚上线第三天
一条 MySQL 主库宕机 的 P0 告警进来了。AI 分类器看了一眼,判断为"基础设施",路由到了基础设施值班群。
问题在于:基础设施群当晚只有 1 人值班,而且他正在处理另一个故障。这条 P0 告警被晾了 8 分 12 秒。
# 原始告警
告警名称: MySQLDown | 服务: order-db | 环境: production
摘要: MySQL 主库连接失败,已持续 2 分钟
# AI 分类结果(错误)
分类: infrastructure ← ❌ 应该分类到 database
置信度: 0.52 ← ⚠️ 阈值设得太低
# 路由结果(错误)
路由到: 基础设施值班群 ← ❌ 应该路由到 DBA 组
响应时间: 8 分 12 秒 ← ❌ P0 要求 < 5 分钟
# 检查分类器对 MySQLDown 的匹配
alert_text = "告警名称: MySQLDown 服务: order-db 摘要: MySQL 主库连接失败"
for cat, prototypes in CATEGORY_PROTOTYPES.items():
proto_embs = category_embeddings[cat]
a_emb = model.encode([alert_text])
sim = np.max(np.dot(proto_embs, a_emb.T))
print(f"{cat}: 最高相似度 {sim:.4f}")
# 输出:
# database: 0.48 ← 低,因为没包含"MySQL主库"原型
# network: 0.31
# application: 0.35
# infrastructure: 0.52 ← 最高!因为"连接失败"像网络问题
三个根因叠加:
# 修复 1:增强数据库分类原型
CATEGORY_PROTOTYPES["database"] = [
"MySQL 连接失败", "MySQLDown", "主库宕机",
"数据库不可用", "connection refused",
"replication lag", "主从延迟",
]
# 修复 2:置信度拒绝策略
defclassify_with_fallback(alert_text, threshold=0.65):
results = classify_alert(alert_text, top_k=2)
if results[0]["max_similarity"] < threshold:
return {"category": "pending_review", "fallback": True}
# P0/P1 告警即使分类低也通知所有相关群
if alert.severity in ("P0", "P1") and results[0]["max_similarity"] < 0.75:
return {"category": results[0]["category"], "notify_all": True}
return results[0]
# 修复 3:P0 告警广播通知
P0_BROADCAST_GROUPS = ["database", "infrastructure", "application"]
修复后 MySQLDown 分类:
- database 相似度: 0.81 ✅
- 分类准确率: 从 72% → 94%
- P0 误路由率: 从 8% → 0.5%
经验教训:


核心思路:用 text2vec-base-chinese 把告警文本转为向量,与每个类别的原型向量做余弦相似度匹配。
from sentence_transformers import SentenceTransformer
import numpy as np
model = SentenceTransformer("shibing624/text2vec-base-chinese")
# 定义分类原型
CATEGORY_PROTOTYPES = {
"database": [
"MySQL 数据库连接失败", "Redis 缓存命中率过低",
"MongoDB 副本集主从切换", "数据库慢查询数量超过阈值",
],
"network": [
"CDN 节点不可达", "负载均衡后端健康检查失败",
"防火墙规则导致连接被拒", "DNS 解析超时",
],
"application": [
"Spring Boot 服务不可用", "HTTP 接口错误率超过阈值",
"应用 OOM 内存溢出", "接口响应时间超时",
],
"infrastructure": [
"Kubernetes Pod CrashLoopBackOff", "服务器 CPU 使用率过高",
"磁盘 IO 等待时间过长", "节点 NotReady 状态",
],
}
# 预计算原型向量
category_embeddings = {}
for cat, descriptions in CATEGORY_PROTOTYPES.items():
category_embeddings[cat] = model.encode(descriptions)
defclassify_alert(alert_text: str) -> dict:
"""对告警进行语义分类"""
query_embedding = model.encode([alert_text])
best_cat, best_score = None, 0
for cat, proto_embeddings in category_embeddings.items():
similarities = np.dot(proto_embeddings, query_embedding.T).flatten()
max_sim = float(np.max(similarities))
if max_sim > best_score:
best_score = max_sim
best_cat = cat
return {"category": best_cat, "confidence": round(best_score, 4)}
# 测试
test_alert = "告警名称: MySQLDown | 服务: order-db | 环境: production | 摘要: MySQL 主库连接失败"
result = classify_alert(test_alert)
print(f"分类: {result['category']} (置信度: {result['confidence']})")
💡 为什么不用规则匹配? 运维告警的措辞千变万化,同一个故障可能有 20 种不同的表述。规则匹配永远追不上词穷的速度。语义分类用向量相似度兜底,没见过的告警也能"猜"到它该去哪个组。
现象:新上线的 Kafka 集群告警被分类为"基础设施"而非"中间件",发给了错误的组。
原因:分类原型中没有"middleware"类别,Kafka 描述与"基础设施"原型相似度偏高。
解决:增加分类原型 + 低置信度归"未分类"兜底:
# 新增中间件分类
CATEGORY_PROTOTYPES["middleware"] = [
"Kafka 消息积压", "RabbitMQ 队列堆积",
"RocketMQ 消费延迟", "ZooKeeper 会话过期",
]
# 低于阈值归为未分类
defclassify_with_threshold(alert_text: str, threshold: float = 0.45):
results = classify_alert(alert_text)
if results["confidence"] < threshold:
return {"category": "uncategorized"}
return results
🔑 记住:分类体系要跟着业务走,每季度 review 一次分类覆盖率。
现象:凌晨 P0 告警升级,电话打给了三个月前离职的同事。
原因:升级联系人列表写死在配置文件中,人员变动没有同步更新。
解决:从企业通讯录 API 动态获取值班人员:
defget_oncall_person(team: str) -> dict:
"""从企业通讯录获取当前值班人员"""
resp = requests.get(
f"http://oncall-api.internal/api/v1/schedules",
params={"team": team, "current": "true"}
)
data = resp.json()
return {
"name": data["oncall"]["name"],
"phone": data["oncall"]["phone"],
"dingtalk_id": data["oncall"]["dingtalk_id"],
}
🔑 记住:凡是涉及人员信息的,一律从动态源获取,写死的配置迟早过期。
现象:数据库故障引发 20 条关联告警,被分成 5 次独立通知发送,5 个人各自响应。
原因:group_wait 设为 10 秒太短,后续产生的关联告警来不及聚合。
解决:对同分类告警加宽聚合窗口,P0 设 30 秒,P1+ 设 2 分钟。通知中标注"此事件关联 N 条告警"。
🔑 记住:聚合窗口宁可稍长也不要太短——运维宁可晚 30 秒收一条完整通知,也不愿被轰炸 20 次。
超时条件 | 升级动作 | 通知方式 |
|---|---|---|
P0 告警 5 分钟未认领 | 升级到组长 | 钉钉 + 电话 |
P0 告警 15 分钟未恢复 | 升级到运维总监 | 钉钉 + 电话 + 短信 |
P0 告警 30 分钟未恢复 | 升级到技术 VP | 电话 + 短信 |
P1 告警 15 分钟未认领 | 升级到组长 | 钉钉 |
P2/P3 告警 | 不自动升级 | 仅记录 |
routes:
-match:
ai_category:database
ai_severity:P0
receiver:db-team-urgent
group_wait:10s
repeat_interval:5m
-match:
ai_category:database
receiver:db-team-normal
group_wait:30s
repeat_interval:30m
-match:
ai_category:application
ai_severity:P0
receiver:app-team-urgent
group_wait:10s
repeat_interval:5m

指标 | 实施前(手动) | 实施后(AI 自动) | 改善 |
|---|---|---|---|
告警响应时间 | 15 分钟 | 2 分钟 🚀 | ⬇️ 87% |
误分派率 | 28% | 3% ✅ | ⬇️ 89% |
P0 告警认领率 | 65%(常漏看) | 98% | ⬆️ 51% |
值班人员告警疲劳度 | 300 条/天 | 30 条/天 | ⬇️ 90% |
适用场景:告警量 > 100 条/天、多团队协作、7x24 运维保障要求高的团队 不适用场景:告警量 < 20 条/天(手动分派可控)、单团队无需路由
🔔 下一篇预告:5 分钟搞定:用 Hermes Agent 接入钉钉实现运维告警自动推送。让告警不再止步于微信群,直接触达责任人。