4. 流处理引擎选型:Flink vs Spark Structured Streaming

选流处理引擎,说白了就是选「命」。

我在好几个项目里都见过这种场景:架构师拍脑袋定了引擎,结果上线后延迟压不下去,或者吞吐上不来。改?成本高得吓人。所以这一章,我想跟你聊聊我这些年用 Flink 和 Spark Structured Streaming 的真实感受。

核心观点:没有最好的引擎,只有最合适的场景。Flink 擅长低延迟、复杂事件处理;Spark Structured Streaming 擅长高吞吐、与批处理一体化的场景。

4.1 两大引擎的「基因」差异

先看一张对比图,帮你快速建立整体认知。

流处理引擎选型对比 Apache Flink 核心:真正的流处理 事件驱动、逐条处理 延迟:毫秒级 Exactly-Once 语义 状态管理:RocksDB / Heap 大状态场景首选 适用:实时风控、特征计算 复杂事件处理 (CEP) Spark Structured Streaming 核心:微批处理 连续处理 (Continuous) 实验性 延迟:秒级 At-Least-Once / Exactly-Once 状态管理:StateStore 基于 HDFS 的增量 checkpoint 适用:ETL、批流一体 与 Spark SQL/MLlib 无缝集成 选型关键:延迟要求 < 100ms 选 Flink;可接受秒级延迟且已有 Spark 生态选 Structured Streaming

4.2 低延迟与高吞吐的权衡

这是个老生常谈的问题,但真正做对的人不多。

Flink 的「真流」哲学

Flink 是真正的逐条处理引擎。每条数据来了,立即触发计算。我做过一个实时特征计算项目,要求延迟在 50ms 以内。Flink 配合 Kafka,端到端延迟稳定在 30ms 左右。但代价是什么?吞吐量受限于单条处理能力。

Spark Structured Streaming 的「微批」哲学

Spark 把流切成一个个小批次,默认 100ms 一个批次。你想想看,如果批次大小是 100ms,那延迟至少 100ms。但好处是吞吐量极高——我见过单节点每秒处理百万条数据的场景。

我的经验:如果业务要求延迟 < 100ms,别犹豫,选 Flink。如果延迟容忍度在 1-5 秒,且你已经在用 Spark 做批处理,Structured Streaming 能省下大量运维成本。

来看一个具体的对比表格:

维度 Apache Flink Spark Structured Streaming
处理模型 真正的流处理(逐条) 微批处理(默认 100ms)
端到端延迟 毫秒级(10-100ms) 秒级(100ms-10s)
吞吐量 中等(受状态大小影响) 高(批量处理优势)
状态管理 RocksDB / Heap,支持大状态 StateStore + HDFS checkpoint
事件时间语义 原生支持,精确 支持,但 watermark 机制较简单
运维复杂度 较高(需要调优参数多) 较低(与 Spark 生态一致)

4.3 状态后端的选择:RocksDB vs Heap

这是 Flink 用户最常纠结的问题。我踩过不少坑,跟你分享下。

Heap StateBackend

说白了就是把状态存在 JVM 堆内存里。优点是快——直接从内存读写,延迟极低。但缺点也很明显:受限于堆大小,状态一多就容易 OOM。

我曾经在一个项目里用 Heap 存用户画像特征,每个用户 1KB 状态,1000 万用户就是 10GB。JVM 堆设了 16GB,结果 Full GC 频繁,延迟飙升到秒级。嗯,那次教训很深刻。

RocksDB StateBackend

RocksDB 是 Facebook 开源的嵌入式 KV 存储,基于 LSM-Tree。它把数据存在本地磁盘,但利用内存做缓存。好处是状态可以很大——理论上只受限于磁盘空间。我见过存 100TB 状态的生产环境。

但 RocksDB 不是银弹。它的读写性能比 Heap 慢一个数量级,而且需要调优参数。比如 rocksdb.block.cache-size、rocksdb.writebuffer.size,调不好反而更慢。

避坑指南:我曾经遇到一个案例,RocksDB 的 compaction 导致 CPU 飙到 100%,延迟从 50ms 变成 5 秒。后来发现是默认的 compaction 策略不适合写密集场景。改成 Level Compaction + 增大 write buffer 才解决。

来看一个代码示例,展示如何在 Flink 中配置状态后端:

// 配置 RocksDB 状态后端
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

RocksDBStateBackend rocksDBStateBackend = new RocksDBStateBackend(
    "hdfs://namenode:8020/flink-checkpoints",  // checkpoint 路径
    true  // 增量 checkpoint
);

// 调优 RocksDB 参数
RocksDBOptions.TIMER_SERVICE_FACTORY.defaultValue();
rocksDBStateBackend.setPredefinedOptions(
    PredefinedOptions.SPINNING_DISK_OPTIMIZED_HIGH_MEM
);

env.setStateBackend(rocksDBStateBackend);
env.enableCheckpointing(5000); // 5 秒一次 checkpoint

选型建议:

  • 状态 < 10GB,延迟敏感:用 Heap。比如实时风控中的计数器、简单聚合。
  • 状态 > 10GB,或不确定:用 RocksDB。比如用户画像、特征存储、长时间窗口。
  • 注意:RocksDB 需要 SSD 磁盘,机械硬盘会严重拖慢性能。

4.4 实战中的选型决策树

我总结了一个简单的决策流程,帮你快速判断:

流处理引擎选型决策树 延迟要求 < 100ms? 是 → Flink 否 → 已有 Spark? 状态大小 > 10GB? 是→RocksDB 否→Heap 是 → Spark Structured Streaming 否 → 考虑 Flink 或 Kafka Streams 关键提醒 • Flink 适合复杂事件处理、大状态、低延迟 • Spark 适合批流一体、高吞吐、已有 Spark 生态 • 不要为了「新技术」而选 Flink,要为了「业务需求」

4.5 我的最终建议

做了这么多年实时计算,我越来越觉得:选型不是技术问题,是业务问题。

  • 如果你做实时特征工程,需要毫秒级延迟、复杂状态管理、事件时间处理——Flink + RocksDB 是标配。
  • 如果你做数据 ETL、报表、对延迟不敏感,但需要高吞吐、与批处理无缝切换——Spark Structured Streaming 更省心。
  • 如果你不确定,从 Flink 开始。因为 Flink 能向下兼容 Spark 的场景,但反过来不行。

一个小技巧:在项目初期,用 Flink 的 MiniCluster 模式做原型验证,快速试错。等业务逻辑稳定了,再决定生产环境的部署方案。

记住一句话:流处理引擎选型,选的是未来三年的运维成本。别只看 POC 阶段的性能数据,多想想团队能不能 hold 住。