AI 赋能的业务监控:从被动告警到基于 LLM 的主动异常检测
一、传统监控的"告警疲劳"困境
如果用一个词形容我们团队 2024 年的监控状态,那一定是"告警疲劳"。Prometheus + Grafana + Alertmanager 这套经典组合,每天产生的告警数量在 200~400 条之间波动。值班同事的工作模式逐渐变成了"收到告警 → 看一眼 Grafana 面板 → 如果看起来没什么大问题就关闭"。
统计数据显示,2024 年 Q3 的告警中,真正需要人工介入处理的有效告警仅占 7.3%。其余 92.7% 的告或是阈值设置过于敏感产生的噪音(如 CPU 瞬时飙升至 85% 又立即回落),或是关联告警的重复通知(同一磁盘故障触发了 12 条不同维度的告警),或是已知的周期性波动(每天凌晨 2 点的批处理任务导致数据库连接数峰值)。
告警系统的核心问题不是"告得不够多",而是"告得不够聪明"——它只能做简单的阈值比较,完全不具备对系统行为的理解能力。
二、分层异常检测的架构设计
我们设计了一套分层异常检测架构,从下到上分为三层。
第一层:统计异常检测。保留传统的阈值和同比/环比检测,但将阈值从"固定值"升级为"动态基线"。通过统计过去 30 天的指标分布,计算 P5/P95 分位数,仅在指标突破分位数范围时触发。这消除了由于固定阈值设置不合理导致的大多数误报。
第二层:向量化异常检测。这是引入 AI 的核心创新点。我们将系统的"正常状态"编码为高维向量。具体做法是:以每分钟为粒度,采集当前系统的 Top-50 指标(CPU、内存、GC 频率、接口 P99 延迟、QPS、错误率、数据库连接池使用率、MQ 积压数量等),组成一个 50 维的特征向量。通过 Isolation Forest 算法训练无监督异常检测模型,发现偏离"正常状态簇"的异常时刻。
异常检测不是简单地告诉你"某个指标超了",而是告诉你"当前系统的整体状态与过去 30 天同一时段有显著差异"。这一层的告警质量远高于单纯的阈值告警。
第三层:LLM 根因分析。当异常被检测到时,系统自动收集异常发生前后 5 分钟的指标快照、日志关键信息、调用链异常节点等上下文数据,格式化为结构化的 Prompt 提交给 LLM 进行分析。
/**
* 分层异常检测引擎
*/
@Service
public class AnomalyDetectionEngine {
@Resource
private MetricCollector metricCollector;
@Resource
private IsolationForestModel anomalyModel;
@Resource
private RootCauseAnalyzer rootCauseAnalyzer;
/**
* 每分钟触发一次的异常检测主流程
*/
public void detect() {
// 采集当前时刻的系统多维指标
double[] currentVector = metricCollector.collectCurrentVector();
if (currentVector == null || currentVector.length == 0) {
log.warn("指标采集为空,跳过异常检测");
return;
}
// 动态基线检测(第一层)
List<String> thresholdAlerts = checkDynamicThresholds(currentVector);
// 向量异常检测(第二层)
AnomalyScore score;
try {
score = anomalyModel.predict(currentVector);
} catch (ModelException e) {
log.error("异常检测模型预测失败", e);
score = AnomalyScore.normal(); // 降级为正常
}
if (!score.isAnomalous() && thresholdAlerts.isEmpty()) {
return; // 系统正常,无需处理
}
// 异常确认:收集上下文信息
AnomalyContext context = AnomalyContext.builder()
.timestamp(LocalDateTime.now())
.anomalyScore(score)
.thresholdAlerts(thresholdAlerts)
.metricSnapshot(metricCollector.snapshot(5)) // 前后5分钟快照
.recentLogs(logCollector.collect(5))
.traceAnomalies(traceCollector.detectAnomalies(5))
.build();
// LLM根因分析(第三层)
try {
RootCauseReport report = rootCauseAnalyzer.analyze(context);
// 根据严重程度决定告警策略
if (report.getSeverity() == Severity.CRITICAL) {
alertService.sendUrgent(report);
} else {
alertService.sendWarning(report);
}
log.info("异常检测完成: severity={}, summary={}",
report.getSeverity(), report.getSummary());
} catch (AnalyzerException e) {
log.error("LLM根因分析失败,降级为传统告警", e);
alertService.sendFallbackAlert(thresholdAlerts);
}
}
private List<String> checkDynamicThresholds(double[] vector) {
List<String> alerts = new ArrayList<>();
DynamicBaseline baseline = baselineService.getBaseline();
for (int i = 0; i < vector.length; i++) {
double value = vector[i];
BaselineStats stats = baseline.getStats(i);
if (stats == null) continue;
if (value > stats.getP95() * 1.2 || value < stats.getP5() * 0.8) {
alerts.add(String.format("指标[%d]异常: 当前值=%.2f, 基线P5-P95=[%.2f, %.2f]",
i, value, stats.getP5(), stats.getP95()));
}
}
return alerts;
}
}
三、LLM 根因分析的 Prompt 工程
根因分析是整个系统中 Prompt 设计要求最高的环节。输入信息包含多种格式的结构化数据:时序指标、日志片段、调用链图谱。如何让 LLM 从这些异构数据中推理出正确的根因?
我们的 Prompt 设计分为三个区块。上下文简报:用不超过 200 字的自然语言概括当前异常的整体情况(哪些维度异常、异常严重程度、是否有已知的关联事件)。结构化数据:以 Markdown 表格形式呈现关键指标的当前值与基线对比(值偏高用 ↑ 标记,偏低用 ↓ 标记);以列表形式呈现最近的异常日志(仅保留 ERROR 和 WARN 级别,去重后不超过 10 条);以文本形式描述调用链中的异常节点和对应的下游服务。历史关联:检索过去 90 天内相似异常的工单和处理记录,作为参考。
最后,通过一个严格的输出格式约束,要求 LLM 按"最可能的根因 → 置信度 → 关联证据 → 建议操作 → 是否需要立即处理"五段式输出。
/**
* LLM根因分析服务
*/
@Service
public class RootCauseAnalyzer {
@Resource
private LLMClient llmClient;
@Resource
private HistoricalTicketSearcher ticketSearcher;
/**
* 分析异常上下文,生成根因报告
*/
public RootCauseReport analyze(AnomalyContext context) throws AnalyzerException {
// 检索历史相似异常工单
List<Ticket> similarTickets = ticketSearcher.searchSimilar(
context.getMetricSnapshot(), 5);
String prompt = buildAnalysisPrompt(context, similarTickets);
String response;
try {
response = llmClient.chat(prompt);
} catch (LLMException e) {
throw new AnalyzerException("LLM调用失败", e);
}
try {
return parseRootCauseReport(response);
} catch (ParseException e) {
log.error("根因报告解析失败: {}", response);
// 解析失败时构建一个基础的降级报告
return buildDegradedReport(context);
}
}
private String buildAnalysisPrompt(AnomalyContext context,
List<Ticket> similarTickets) {
StringBuilder prompt = new StringBuilder();
prompt.append("你是一个资深的系统运维专家。请分析以下异常检测结果,找出根因。\\n\\n");
// 异常概况
prompt.append("## 异常概况\\n");
prompt.append(String.format("检测时间: %s\\n", context.getTimestamp()));
prompt.append(String.format("异常评分: %.2f (阈值0.7)\\n", context.getAnomalyScore().getValue()));
prompt.append(String.format("触发阈值告警数: %d\\n\\n", context.getThresholdAlerts().size()));
// 关键指标对比(表格形式)
prompt.append("## 关键指标对比\\n");
prompt.append("| 指标 | 当前值 | 基线均值 | 偏差 |\\n");
prompt.append("|——|——–|———-|——|\\n");
for (MetricSnapshot metric : context.getMetricSnapshot().getMetrics()) {
String deviation = metric.getDeviationPercent() > 0 ?
"↑" + String.format("%.0f%%", metric.getDeviationPercent()) :
"↓" + String.format("%.0f%%", Math.abs(metric.getDeviationPercent()));
prompt.append(String.format("| %s | %.2f | %.2f | %s |\\n",
metric.getName(), metric.getCurrentValue(),
metric.getBaselineMean(), deviation));
}
// 异常日志
prompt.append("\\n## 异常日志\\n");
for (String logLine : context.getRecentLogs()) {
prompt.append("- ").append(logLine).append("\\n");
}
// 历史相似工单
if (!similarTickets.isEmpty()) {
prompt.append("\\n## 历史相似工单\\n");
for (Ticket ticket : similarTickets) {
prompt.append(String.format("- [%s] %s (处理方案: %s)\\n",
ticket.getResolvedTime(), ticket.getTitle(),
ticket.getResolution()));
}
}
// 输出格式要求
prompt.append("\\n请严格按以下格式输出分析结果:\\n");
prompt.append("根因: <最可能的根因描述>\\n");
prompt.append("置信度: <0~100的数值>\\n");
prompt.append("关联证据: <支持该结论的具体证据>\\n");
prompt.append("建议操作: <具体的处理步骤>\\n");
prompt.append("紧急度: <critical/high/medium/low>\\n");
return prompt.toString();
}
private RootCauseReport parseRootCauseReport(String response) {
// 解析LLM返回的五段式结构化报告
RootCauseReport report = new RootCauseReport();
String[] lines = response.split("\\n");
for (String line : lines) {
if (line.startsWith("根因:")) {
report.setRootCause(line.substring(3).trim());
} else if (line.startsWith("置信度:")) {
String value = line.substring(4).trim().replace("%", "");
report.setConfidence(Integer.parseInt(value));
} else if (line.startsWith("建议操作:")) {
report.setSuggestedAction(line.substring(5).trim());
} else if (line.startsWith("紧急度:")) {
report.setSeverity(Severity.fromString(line.substring(4).trim()));
}
}
return report;
}
private RootCauseReport buildDegradedReport(AnomalyContext context) {
RootCauseReport report = new RootCauseReport();
report.setRootCause("LLM分析结果解析失败,请人工排查");
report.setConfidence(0);
report.setSuggestedAction("请查看原始监控数据和日志进行人工分析");
report.setSeverity(Severity.MEDIUM);
return report;
}
}
四、告警聚合与降噪
引入异常检测和根因分析后,单条告警的质量大幅提升。但随之而来的新问题是:根因分析的报告数量仍然不少,高峰时每小时仍有 15~20 条报告。值班同事反馈"信息质量提高了,但信息量还是太大"。
我们引入了一个轻量级的告警聚合引擎,从两个维度聚合:一是时间维度,将 5 分钟窗口内的多条根因报告合并,取置信度最高的一条作为代表,其余的作为补充细节;二是拓扑维度,通过服务依赖关系图(基于调用链数据自动生成),将有关联的服务告警聚合成一条"影响链报告",明确展示异常传播路径(如"Redis 连接超时 → 订单服务降级 → 支付服务队列堆积")。
五、效果评估与下一步方向
系统上线 4 个月后的对比如下:有效告警占比从 7.3% 提升至 62%;平均故障发现时间(MTTD)从 23 分钟降至 3.2 分钟;平均故障修复时间(MTTR)从 87 分钟降至 41 分钟;值班同事反馈的告警疲劳感从 8.5 分降至 3.2 分(10 分满分制)。
下一步的优化方向包括:一是引入预测性异常检测,在故障发生前 5~10 分钟预警(已在小规模实验中取得 72% 的提前预警率);二是构建自动化修复决策,对于置信度超过 90% 且修复方案明确的告警(如"重启 Pod"、"扩容 HPA"),自动触发修复操作;三是将异常检测场景从后端监控拓展到业务指标(如订单量异常下降、支付成功率异常波动),实现从技术监控到业务监控的全面覆盖。
AI 赋能监控的核心价值不是替代运维工程师,而是让机器处理那些"确定性的、重复性的、低价值的"判断工作,将人的精力集中在真正需要经验和创造力的疑难问题上。
作者:李然(程序员鸭梨),Java 架构师,专注可观测性与智能运维体系建设。




