异常检测基础

我开始做日志异常检测那会儿,最头疼的就是“到底什么样才算异常”。一个系统跑得挺好,突然报了个503错误——那是正常波动,还是真的出事了?没有异常检测的概念框架,你连问题都描述不清楚。这一节,我们就把地基打牢。

什么是异常检测

异常检测,说白了就是找“不同于大多数”的东西。在日志领域,它就是分析日志事件序列、指标或文本内容,找出与正常行为模式不同的样本。

我习惯把异常检测看成两类任务:

  • 监督式:有标注数据,知道哪些是异常,哪些是正常。比如你手动标记过十次宕机日志,用来训练分类器。
  • 无监督式:没有标签,全靠自动发现。真实生产环境中,大部分场景都是这种。你想想看,谁会闲着没事把每行日志都标上“正常”和“异常”?

个人经验是,日志异常检测极难做到100%监督,因为异常事件频率低、形态多变。我自己在运维一个电商平台时,遇到过一种诡异的慢查询日志——它只是在凌晨3点出现几秒,但每次都能拖垮数据库连接池。这种低频异常,人工根本没办法提前标注。

异常的主要类型

教科书上一般把异常分为三种。我在实际项目里摔过跟头才真正理解它们的区别。

点异常

一个单独的观测点显著偏离其他数据。比如API响应时间平时是200ms,突然跳到了5s。这种最好抓,阈值法就管用。我记得有一次监控报警被点异常淹没了——前端工程师部署了一个有bug的版本,每秒刷出上千条错误日志,我们不得不紧急加了个滑动窗口降噪。

上下文异常

在特定环境下才叫异常。比如凌晨3点的登录失败,单独看可能正常,但如果在工作日9点高峰时段连续出现一百次登录失败,那就是暴力破解。我踩过一个坑:只设置全局的失败次数阈值,结果半夜的正常测试被误判成攻击,页面被WAF直接封了。后来加上“时间上下文”就再没误报过。

集体异常

单个点没毛病,但一组数据点合在一起就诡异了。比如5个微服务的日志依次出现“连接超时”,每个单独看都是正常的超时重试,但组合起来恰好画出一条典型的雪崩链路图。举一个真实案例:我们曾有一组容器日志,每个容器CPU使用率只比基线高3%,放在一起看才发现它们在同一个时间窗口内同时升高——这是内存泄漏的早期信号。如果不做集体异常检测,根本发现不了。

下面这个表格可以帮你快速对比:

异常类型 定义 日志示例 检测特点
点异常 单个数据点偏离群体 单行ERROR日志出现 简单阈值即可
上下文异常 在特定上下文中才异常 深夜登录失败 vs 白天登录失败 需要时间/环境特征
集体异常 一组数据点共同异常 多个服务同时超时 需要序列建模

异常检测的应用场景

我从一线运维和故障排查中总结了四个最常用的场景:

  • 系统监控与告警:实时检测CPU、内存、QPS的异常突变。我上次接手一个旧系统,发现大量重复的OOM日志,就是靠点异常+上下文异常联动才定位到的。
  • 安全威胁检测:暴力破解、DDoS、SQL注入等。日志里的异常登录、单IP高频请求,都属于这类。
  • 故障根因分析:从一堆看似正常的日志里找出真正的元凶。比如集体异常模式能帮助把一堆“Connection refused”和“Disk quota exceeded”关联起来,而不是一个个点排查。
  • 业务异常发现:用户行为突变,比如支付失败率突然翻倍、下单转化率骤降。这类异常往往不在基础设施日志里,而是在业务日志里。

评估指标

检测出来了,怎么知道好不好?我见过太多团队把准确率当唯一指标,结果模型一个异常都抓不到——因为正负样本严重失衡。所以这三个指标必须理解透彻。

混淆矩阵先画出来

真实\预测    正常     异常
正常         TN       FP
异常         FN       TP
  • 准确率(Accuracy) = (TP+TN)/(TP+TN+FP+FN) —— 整体正确比例。但日志异常检测场景下,正常样本通常占99%以上,哪怕你全部预测为正常,准确率也是99%+,完全没有意义。
  • 召回率(Recall) = TP/(TP+FN) —— 真实异常里你逮住了多少。我重视这个指标,因为漏掉一个异常可能造成上百万损失。曾在一次故障复盘里发现,我们的模型召回率只有0.3,十个真正的异常只抓到了三个,差点让产品线瘫痪。
  • F1-score = 2 * (Precision * Recall) / (Precision + Recall) —— 精准率和召回率的调和平均。Precision = TP/(TP+FP) 衡量误报率。别以为只有召回重要,如果一天发出10万次告警,运维会被逼疯——所以我习惯在调优时让F1最大化。

实操时我一般会用这段代码快速算指标:

from sklearn.metrics import accuracy_score, recall_score, f1_score

y_true = [0,0,0,1,0,0,1,0,0,0]  # 1代表异常
y_pred = [0,0,0,0,0,0,1,0,0,0]  # 预测结果

acc = accuracy_score(y_true, y_pred)
rec = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)

print(f"准确率: {acc:.3f}, 召回率: {rec:.3f}, F1: {f1:.3f}")
# 输出: 准确率: 0.900, 召回率: 0.500, F1: 0.667

有没有发现问题?准确率看起来90%很高,但召回率只有50%——漏掉了一个异常。我当年踩过的坑是只看准确率就上线模型,结果被老板批评“系统完全不报警”。所以一定要盯着召回和F1。

在日志异常检测任务中,建议同时使用召回率+F1作为主要优化指标,准确率仅供参考。

下面这张图把本章的核心知识梳理了一遍,方便你回顾整体脉络:

异常检测基础知识框架 异常检测概念 异常类型 应用场景 评估指标 点异常 上下文异常 集体异常 系统监控告警 安全威胁检测 故障根因分析 业务异常发现 准确率 召回率 F1-score 提示:日志异常检测通常结合多种异常类型和指标综合评估,不能只依赖单一指标。
注意:本章讨论的评估指标均基于二分类场景。如果出现多类异常(比如不同类型错误),需要改用宏平均或微平均的F1。我在做多分类日志异常检测时,就因为这个细节被老板追问为什么F1在0.5上下浮动——最后发现是代码里用了默认参数,没有做样本加权。