数据采集与预处理:数据源类型与清洗实战

数据采集与预处理,说白了就是给异常检测模型「喂饭」。饭喂得好不好,直接决定模型能不能吃饱、吃对。我见过太多团队花大把时间调模型,结果数据源没处理好,最后告警全是噪音。今天咱们就把这块硬骨头啃下来。

数据源类型:Metrics、Logs、Traces

体量异常检测依赖三类数据源,我习惯叫它们「运维三剑客」。每类数据都有自己的脾气,采集方式也完全不同。

数据源 典型格式 采集频率 存储量级 异常检测价值
Metrics Prometheus 指标、StatsD 秒级~分钟级 GB/天 趋势突变、阈值越界
Logs JSON、Syslog、文本 事件驱动 TB/天 错误率、关键字爆发
Traces OpenTelemetry、Jaeger 请求采样 GB~TB/天 延迟异常、调用链断裂

我个人习惯把 Metrics 作为第一道防线。为什么?因为它轻量、实时、容易做基线。Logs 虽然信息量大,但噪声也多。Traces 呢,适合做根因定位,但采样率一低就容易漏掉异常。

我的经验: 刚开始做体量异常时,别贪多。先盯住 Metrics 里的 CPU、内存、QPS、错误率这四个核心指标。等告警收敛了,再逐步加入 Logs 和 Traces。

数据清洗:去掉那些「脏东西」

数据采集上来,第一件事不是分析,而是清洗。我在项目中遇到过好几次,告警平台半夜狂响,查了半天发现是采集器挂了,发了一堆空值。你说气不气人?

常见的脏数据有这几类:

  • 重复数据:采集器重试导致同一时间戳多条记录
  • 异常值:传感器故障产生的 -1、999999 等明显不合理数值
  • 格式错误:Log 里字段错位、JSON 解析失败
  • 时间戳乱序:采集端时钟不同步导致数据「穿越」

清洗策略我一般分三步走:

  1. 去重:按时间戳+标签做唯一键,保留第一条或最后一条
  2. 过滤:设定合理范围,比如 CPU 使用率 0~100,超出直接丢弃
  3. 校验:对 Log 做正则匹配,格式不对的打入死信队列
# 一个简单的 Metrics 清洗示例(Python)
def clean_metric(data_point):
    # 去重:相同时间戳和标签只保留一个
    key = (data_point['timestamp'], frozenset(data_point['labels'].items()))
    if key in seen_keys:
        return None
    
    # 范围过滤
    if data_point['value'] < 0 or data_point['value'] > 100:
        return None
    
    # 时间戳合理性检查
    now = time.time()
    if abs(data_point['timestamp'] - now) > 3600:
        return None
    
    return data_point
避坑指南: 我曾经把异常值直接丢弃,结果模型学到的全是「完美数据」,上线后对真实异常毫无反应。后来我改成「标记+保留」,让模型自己判断哪些是异常、哪些是噪声。效果好了不止一倍。

缺失值处理:别让模型「饿肚子」

数据缺失在运维场景里太常见了。机器重启、网络抖动、采集器 OOM,都会导致数据断点。你想想看,如果模型在某个时间窗口里只有 50% 的数据,它怎么判断体量是否异常?

常用的处理方法有四种:

方法 适用场景 优点 缺点
前向填充 短时间缺失(<5 个点) 简单、保持趋势 会掩盖突变
线性插值 周期性指标 平滑、合理 计算开销稍大
均值填充 稳定指标(如内存使用率) 快速、稳定 会拉低方差
标记缺失 长时间缺失(>10 个点) 不引入偏差 模型需支持缺失值

我个人习惯的做法是:先判断缺失比例。低于 5% 的,用前向填充;5%~20% 的,用线性插值;超过 20% 的,直接标记为「数据不可用」,不参与异常检测。为什么?因为强行填充太多假数据,会让模型产生幻觉。

核心原则: 宁可漏报,不要误报。缺失值处理的目标不是「补全数据」,而是「让模型在数据不完整时依然做出合理判断」。

数据标准化:让不同指标站在同一起跑线

QPS 可能是几万,CPU 是百分之几,延迟是毫秒级。这些指标的量纲完全不同,如果不做标准化,模型会天然偏向数值大的指标。说白了,就是「以大欺小」。

常用的标准化方法:

  • Z-score 标准化:减去均值,除以标准差。适合数据近似正态分布的场景
  • Min-Max 缩放:映射到 [0,1] 区间。适合有明确上下界的指标
  • Robust 缩放:用中位数和四分位距。适合有离群点的数据
# Z-score 标准化示例
def zscore_normalize(series):
    mean = np.mean(series)
    std = np.std(series)
    if std == 0:
        return series  # 全相同值,不做处理
    return (series - mean) / std

# Min-Max 缩放示例
def minmax_normalize(series, min_val, max_val):
    if max_val == min_val:
        return series
    return (series - min_val) / (max_val - min_val)

嗯,这里要注意:标准化参数(均值、标准差、最大最小值)必须在训练集上计算,然后应用到测试集。我见过有人对整个数据集做标准化,结果模型在线上表现一塌糊涂——因为线上数据分布变了,标准化参数没跟着更新。

我的建议: 对于体量异常检测,我推荐先用 Robust 缩放。运维数据里离群点太多了,Z-score 容易被带偏。Robust 缩放对异常值不敏感,更适合我们的场景。

知识体系总览

下面这张图把数据采集与预处理的完整流程串起来了。你可以把它当作一个检查清单,每次搭建检测系统时对照着走一遍。

数据采集与预处理流程 Metrics Logs Traces 数据清洗 去重 → 范围过滤 → 格式校验 → 时间戳对齐 缺失值处理 前向填充 | 线性插值 | 均值填充 | 标记缺失 数据标准化 Z-score | Min-Max | Robust 缩放 标准化时序数据 → 异常检测模型

从这张图能看出来,数据预处理不是一步到位的。它是一个流水线:采集 → 清洗 → 补缺 → 标准化。每一步的输出都是下一步的输入。我在实际项目中,会把每一步都做成独立的处理单元,方便调试和替换。比如某个数据源突然格式变了,我只需要改清洗那一步,其他环节完全不受影响。

好了,数据采集与预处理这块就聊到这儿。记住一句话:垃圾数据进,垃圾结果出。把基础打牢,后面的异常检测才能站得住脚。