技术选型:Flink vs Spark Streaming vs Kafka Streams

做低延迟 Join,选对引擎就等于成功了一半。

我见过太多团队,上来就拍脑袋选 Flink,结果业务场景其实用 Kafka Streams 就能搞定,白白增加了运维成本。也见过反过来的——硬要用 Spark Streaming 做毫秒级双流 Join,最后延迟根本压不下去。

这一章,咱们就把这三个主流引擎掰开揉碎,看看它们到底适合什么样的 Join 场景。

先看一张总览图

下面这张图,是我自己总结的选型决策框架。你跟着它走,基本不会跑偏。

低延迟 Join 技术选型决策框架 Join 延迟要求 毫秒级(< 100ms) 秒级(100ms-5s) 准实时(5s-60s) Kafka Streams 或 Flink(优化后) Flink (首选) Spark Streaming (微批模式) 关键考量 状态存储、Exactly-Once 运维复杂度 关键考量 窗口语义、状态后端 Checkpoint 间隔 关键考量 批大小、调度延迟 资源弹性 💡 选型不是非此即彼,同一个团队可能同时用 Flink + Kafka Streams 处理不同场景

Flink:低延迟 Join 的王者

Flink 做 Join 的优势,说白了就一句话:它是真正的逐条处理引擎。每条数据来了,立刻触发计算,不需要攒批。

我记得有个金融风控的项目,要求两条交易流必须在 50ms 内完成关联。当时我们试了 Spark Streaming,微批模式下最低延迟也在 200ms 左右。换成 Flink 后,配合 RocksDB 状态后端,延迟直接降到了 30ms 以下。

Flink 低延迟 Join 的核心能力:

  • 事件时间语义:支持乱序数据下的精确 Join,不会因为数据迟到就丢数据
  • 丰富的 Join 类型:Window Join、Interval Join、Temporal Join、Regular Join,基本覆盖所有场景
  • 状态后端可插拔:Heap StateBackend 适合小状态低延迟,RocksDB 适合大状态高吞吐
  • Checkpoint 机制:保证 Exactly-Once 语义,状态一致性有保障

但 Flink 也不是没有缺点。它的学习曲线比较陡,尤其是状态管理和 Checkpoint 调优。我见过不少新手,上来就把 Checkpoint 间隔设成 1 秒,结果集群直接被打挂。

我的经验:如果你的 Join 延迟要求是毫秒级,且状态规模在百 GB 以内,Flink + Heap StateBackend 是最佳组合。状态超过 100GB,果断上 RocksDB。

Spark Streaming:微批模式的取舍

Spark Streaming 的架构决定了它做 Join 的延迟下限。微批模式再怎么优化,也绕不开「攒批 → 调度 → 计算」这个流程。

我做过一个对比测试:同样的双流 Join 逻辑,Flink 延迟 50ms,Spark Streaming 结构化流(Continuous Processing 模式)延迟 300ms,微批模式更是到了 1 秒以上。

那 Spark Streaming 就没用了吗?当然不是。它有几个场景特别适合:

  • 准实时报表:延迟容忍度在 10 秒以上,但吞吐量极大
  • 与批处理混用:同一个团队用 Spark 做离线,顺手用 Structured Streaming 做近实时
  • 复杂 ETL:Join 只是整个 pipeline 的一环,后面还有大量机器学习计算

避坑指南:我曾经在一个 IoT 项目里,用 Spark Streaming 做设备数据流和规则配置流的 Join。微批模式下,配置变更后要等 10 秒才能生效,业务方直接炸了。后来换成了 Flink 的 Interval Join,配置变更秒级生效。

所以,如果你的 Join 结果需要「立竿见影」,别犹豫,直接上 Flink。

Kafka Streams:轻量级选手

Kafka Streams 是个很有意思的选手。它不是一个独立的计算框架,而是 Kafka 客户端库。这意味着:你不需要部署集群,你的应用本身就是计算节点。

它的 Join 能力其实很强:

  • KTable-KTable Join:类似数据库的维表 Join,延迟极低
  • KStream-KStream Join:基于窗口的双流 Join,毫秒级延迟
  • KStream-KTable Join:流表关联,常用于实时补全维度信息

我有个做实时推荐系统的朋友,他们就用 Kafka Streams 做用户行为流和商品特征流的 Join。整个系统就几个 JAR 包,部署在 3 台机器上,延迟稳定在 20ms 以内。

Kafka Streams 的适用场景:

  • 你的数据已经在 Kafka 里了,不想引入额外的计算框架
  • Join 逻辑相对简单,不需要复杂的窗口或时间语义
  • 团队规模小,不想维护 Flink/Spark 集群

但它的短板也很明显:状态存储依赖 RocksDB,大规模状态下的性能不如 Flink;没有原生的 Checkpoint 机制,容错需要靠 Kafka 的日志来恢复。

三张表说清楚差异

下面这张对比表,是我做选型时必看的。你直接拿去用就行。

对比维度 Flink Spark Streaming Kafka Streams
处理模式 逐条处理(原生) 微批(默认)/ Continuous 逐条处理
典型延迟 10-100ms 100ms-10s 10-50ms
Join 类型 Window / Interval / Temporal / Regular Stream-Stream / Stream-Table KStream-KStream / KTable-KTable / KStream-KTable
状态存储 Heap / RocksDB / 自定义 Heap / RocksDB RocksDB(内置)
Exactly-Once 原生支持 支持(需配置) 依赖 Kafka 事务
运维复杂度 高(需集群) 高(需集群) 低(嵌入应用)
学习曲线 陡 中等 平缓
最佳场景 毫秒级复杂 Join 准实时 + 批处理混用 轻量级 Kafka 原生 Join

我的选型建议

说了这么多,到底怎么选?我个人的经验是:

  1. 延迟要求 < 100ms:首选 Flink。如果数据已经在 Kafka 里且逻辑简单,Kafka Streams 也可以。
  2. 延迟要求 100ms-5s:Flink 依然是最稳的选择。Spark Streaming 也能做,但需要仔细调优批大小。
  3. 延迟要求 > 5s:三个都能做。这时候看团队技术栈,Spark 团队就用 Spark,Flink 团队就用 Flink。
  4. 团队规模小,不想运维集群:Kafka Streams 是性价比最高的方案。

一个小技巧:如果你不确定选哪个,先拿 Kafka Streams 做原型。它开发快,部署简单。原型跑通了,发现延迟不够,再迁移到 Flink。我见过好几个团队都是这么干的,成本最低。