实时数据处理基础
在做实时异常检测之前,得先把数据怎么“流”进来这件事搞清楚。我刚开始接触这块的时候,总觉得批处理和流处理差不多嘛,后来被线上延迟打脸才明白——流数据它不等人。
流数据概念
流数据就是一条条不停产生的、有先后顺序的数据记录。比如用户的每次点击、每笔交易、每台服务器的CPU打点。它有三个特征我几乎天天都要跟团队强调:
- 无界 – 不知道什么时候结束,可能永远来下去
- 持续 – 每分钟、每秒都有新记录
- 时序敏感 – 事件时间比处理时间更重要
一个典型的例子:用户连续输错三次密码,你得在5秒内检测出来。你要是等一天跑一次批处理,黄花菜都凉了。
实时计算引擎:Spark Streaming vs Flink
目前主流的实时计算引擎就两个:Spark Streaming和Flink。我两个都用过,说说我的真实感受。
| 特性 | Spark Streaming | Flink |
|---|---|---|
| 本质 | 微批处理(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或自研平台
下面这张图是我平时给新人讲的管道架构,你看一眼就明白了:
你可能要问:为什么中间非得加个Kafka?嗯,有两个原因:一是解耦,生产端和消费端可以独立扩缩容;二是缓冲,应对流量洪峰。我见过有人直接把日志打到Flink算子里,结果业务洪峰来了,Flink背压把算子内存撑爆了,那叫一个惨。
说回代码。下面是一个使用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,这样可以覆盖之前告警状态。我个人建议在生产环境加一个去重逻辑,避免同一条告警重复推送到钉钉。
好了,实时数据处理的基础就说到这儿。搞懂了流的概念、选对了引擎、搭稳了管道,后面讲异常检测算法才有用武之地。你想想看,如果数据管道都在丢数据、延迟不稳定,再牛的模型也白搭。