实时数据处理基础

在做实时异常检测之前,得先把数据怎么“流”进来这件事搞清楚。我刚开始接触这块的时候,总觉得批处理和流处理差不多嘛,后来被线上延迟打脸才明白——流数据它不等人。

流数据概念

流数据就是一条条不停产生的、有先后顺序的数据记录。比如用户的每次点击、每笔交易、每台服务器的CPU打点。它有三个特征我几乎天天都要跟团队强调:

  • 无界 – 不知道什么时候结束,可能永远来下去
  • 持续 – 每分钟、每秒都有新记录
  • 时序敏感 – 事件时间比处理时间更重要

一个典型的例子:用户连续输错三次密码,你得在5秒内检测出来。你要是等一天跑一次批处理,黄花菜都凉了。

💡 我的习惯:画数据流图的时候,先把“时间窗口”标出来。异常检测90%的问题都出在窗口定义上——大了延迟高,小了漏报多。

实时计算引擎:Spark Streaming vs Flink

目前主流的实时计算引擎就两个:Spark Streaming和Flink。我两个都用过,说说我的真实感受。

特性Spark StreamingFlink
本质微批处理(micro‑batch)真正的事件驱动
延迟秒级(通常1~5秒)毫秒级
事件时间支持依赖watermark,略复杂原生支持,精确一次
状态管理RDD/DataFrame状态内置状态后端(RocksDB等)
上手难度跟Spark batch无缝切换概念多(算子、侧输出等)

我个人建议:如果你团队已有的Spark生态比较完善,而且延迟容忍度在秒级,选Spark Streaming没什么问题。但如果你要做的是高频交易风控或者毫秒级告警,Flink的「一次处理一条」模式更适合——它不会像Spark那样为了凑批次而等。

避坑指南:我曾经在生产环境踩过一个坑——Spark Streaming的批次间隔设成2秒,结果遇到突发流量时批次堆积,导致背压,Kafka消费位移迟迟提交不了。后来改成Flink,它的反压机制自动调节消费速度,稳多了。

实时数据管道搭建

一条典型的实时异常检测管道长这样:

  • 数据源 – 业务日志、埋点、数据库CDC(例如Kafka Connect读取MySQL binlog)
  • 消息队列 – Kafka几乎成了标准选择。我习惯把topic按事件类型分,方便下游订阅
  • 流计算引擎 – Spark Streaming或Flink做窗口聚合、特征提取、异常打分
  • 结果存储 – 告警结果写入Elasticsearch或时序数据库,方便查历史
  • 告警通道 – 通过Webhook或消息队列推送至钉钉、Slack或自研平台

下面这张图是我平时给新人讲的管道架构,你看一眼就明白了:

实时异常检测数据管道 数据源 日志 / CDC / 埋点 Kafka 消息队列 Flink / Spark ES / TSDB 告警通道 钉钉 / Slack 监控 & 回溯 说明: 1. 数据源 → Kafka 2. Flink 消费计算 3. 告警 & 落库

你可能要问:为什么中间非得加个Kafka?嗯,有两个原因:一是解耦,生产端和消费端可以独立扩缩容;二是缓冲,应对流量洪峰。我见过有人直接把日志打到Flink算子里,结果业务洪峰来了,Flink背压把算子内存撑爆了,那叫一个惨。

⚠️ 重要提醒:Kafka的topic分区数最好跟下游并行度对等。比如Flink设置8个并行度,Kafka的topic就设8个分区。否则会出现数据倾斜——我亲身体会过,用了16个分区但Flink只有4个并行度,结果一半分区没消费到。

说回代码。下面是一个使用Flink SQL做滑动窗口计数的例子,用来检测“每分钟登录失败超过5次”的异常:

-- Flink SQL 示例:每分钟滑动窗口,统计每个用户失败登录次数
CREATE TABLE login_events (
    user_id STRING,
    status   STRING,
    ts       TIMESTAMP(3),
    WATERMARK FOR ts AS ts - INTERVAL '10' SECOND
) WITH (
    'connector' = 'kafka',
    'topic' = 'login_log',
    'properties.bootstrap.servers' = 'kafka:9092',
    'format' = 'json'
);

CREATE TABLE anomaly_sink (
    user_id   STRING,
    fail_cnt  BIGINT,
    window_start TIMESTAMP(3),
    PRIMARY KEY (user_id, window_start) NOT ENFORCED
) WITH (
    'connector' = 'upsert-kafka',
    'topic' = 'anomaly_alerts'
);

INSERT INTO anomaly_sink
SELECT
    user_id,
    COUNT(*) AS fail_cnt,
    TUMBLE_START(ts, INTERVAL '1' MINUTE) AS window_start
FROM login_events
WHERE status = 'FAILED'
GROUP BY
    TUMBLE(ts, INTERVAL '1' MINUTE),
    user_id
HAVING COUNT(*) > 5;  -- 每分钟超过5次才触发

这段SQL看起来简单吧?但实际上还有不少细节:watermark延迟10秒是为了处理乱序数据;sink用的upsert-kafka,这样可以覆盖之前告警状态。我个人建议在生产环境加一个去重逻辑,避免同一条告警重复推送到钉钉。

💡 经验之谈:刚开始学流处理,别急着上复杂CEP。先确保数据正确落库、窗口不乱,再去搞模式匹配。我曾经为了炫技用了Flink CEP,结果模式定义错了一个状态,排查了整整两天——后来还是老老实实SQL+State搞定。

好了,实时数据处理的基础就说到这儿。搞懂了流的概念、选对了引擎、搭稳了管道,后面讲异常检测算法才有用武之地。你想想看,如果数据管道都在丢数据、延迟不稳定,再牛的模型也白搭。