01Flink 流式 Join 的范式转变:Delta Join 解决了什么问题?
Apache Flink 一直以来都擅长有状态流处理,但传统流式 Join 在面对海量数据和高基数 Key 时却遇到了瓶颈。问题在于为了保证正确性,你必须将所有历史数据永久保存在 Flink 状态中——这显然不可持续。
Delta Join(FLIP-486)彻底改变了这一局面。它不再将所有数据缓存在内部,而是将 Join 转变为一种无状态的查询机制,直接从 Apache Fluss 或 Apache Paimon 等外部表中实时获取所需数据。
02Delta Join 带来的实际影响
Delta Join 的核心思想很简单:将计算与历史数据解耦。算子不再将全部历史数据存于 Flink 状态,而是在需要时才去外部存储查询。从此告别状态爆炸式增长。
效果如何?看一组来自淘宝天猫团队生产环境的真实数据:
- 消除 50TB 的 Join 状态——难以想象吧?
- 成本降低 10 倍:计算资源从 2300 CU 降至 200 CU,吞吐量却保持不变
- CPU 和内存节省超 80%
- 作业恢复速度提升 87%
- Checkpoint 从“等到天荒地老”变为秒级完成
这不仅是渐进式优化,更是超大规模流处理的一次范式革命。
03无界状态危机:为何传统 Join 难以规模化?
传统 Join 为何在规模扩大时失效?
Flink 的常规 Join 功能强大,能完美处理 Insert、Update、Delete 操作。但代价是:你必须将两个流的所有历史数据永久保留在 Flink 状态中。
由于流作业永不停止,状态会无限增长。在高基数场景下,这无异于一场灾难。
问题迅速累积:
- 资源压垮:TaskManager 被庞大的状态压得喘不过气
- Checkpoint 地狱:Checkpoint 耗时极长,作业频繁超时、不稳定
- 恢复噩梦:从存储中恢复上百 TB 状态?准备好泡几壶咖啡吧
Delta Join 之前,我们有什么?
在 Delta Join 出现前,Flink 只有一些有限的替代方案:
- Interval Join:仅适用于带时间窗口的追加流,现实场景大多不满足
- Temporal / Lookup Join:适合流与维表关联,但无法用于双流 Join(双方都需要历史访问)
根本问题在于:传统 Join 迫使 Flink重复存储本已存在于外部的数据——就像为了以防万一,把整个数据库拷贝到内存里,既低效又不可持续。
04Delta Join 架构深度解析
核心理念:计算 vs 历史
Delta Join(FLIP-486)的核心是关注点分离。其原则非常清晰:
“按需查询,最小作业状态,最终一致性”
当事件到达 Join 的任意一侧时,算子不会翻查内部历史,而是实时查询外部索引。不再囤积数据,用时再取。
StreamingDeltaJoinOperator 如何工作?
StreamingDeltaJoinOperator是实现这一切的引擎,关键组件包括:
- 双侧 LRU 缓存:查询前先查缓存,热数据驻留内存,冷数据自动淘汰
- 异步探查(Async Probing):缓存未命中时立即发起查询,不阻塞处理流水线
- AsyncDeltaJoinRunner:每侧一个实例,负责管理缓存与外部 I/O
注意:Delta Join 并非完全无状态,而是一种混合模型。算子仍保留 LRU 缓存和协调状态以保证一致性。性能表现取决于缓存命中率和外部查询延迟。
正确性保障:异步顺序控制
异步查询的一大挑战是:同一 Key 的更新可能乱序到达,破坏结果正确性。
Delta Join 通过FLIP-519 引入的KeyedAsyncWaitOperator解决此问题:
- 同一 Key 的操作严格串行执行
- 不同 Key 仍可并行处理
既保留了高吞吐优势,又确保了结果正确性。
05外部状态存储:与实时湖仓生态集成
为何 Fluss 是 Delta Join 的理想搭档?
Apache Fluss(孵化中)正是为这类场景而生——它是一个专为 Apache Flink 设计的解耦式表存储引擎。
关键特性:
- 分布式架构:Coordinator + 基于 RocksDB 的 Tablet Server
- 双结构设计:KV 存储 + 日志 Tablet = 支持任意时间点查询 + CDC 流输出
- 前缀查询(Prefix Lookups):杀手级功能!支持使用复合主键的部分字段查询(例如仅用
customer_id,而非完整的(customer_id, order_id, item_id))。多数系统要求精确匹配,Fluss 则灵活得多。
未来方向:Apache Paimon 集成
虽然 Fluss 是 Delta Join 的初始载体,但 Flink 社区正积极推动其与开源湖仓格式的融合。Flink SQL 路线图已明确计划支持Apache Paimon,以实现更广泛的近实时 Delta Join 能力。
Paimon 的优势:
- 支持主键表与实时流式更新
- 分钟级可查
- 灵活的 Merge 引擎(去重、部分更新、聚合)
- 与 Spark、Hive、Trino 无缝集成
目标很明确:让 Delta Join 成为整个湖仓生态的通用能力,而不仅限于 Fluss。
06量化收益与运维稳定性提升
数据不会说谎——Delta Join 带来了实实在在的运维改善。
核心收益
- 状态归零:告别上百 TB 状态文件、Checkpoint 超时和作业崩溃
- 资源节省:CPU/内存消耗降低 80%+;某场景 CU 从 2300 降至 200,成本直降 10 倍,吞吐不变
运维稳定性飞跃
- Checkpoint 秒级完成:再也不用苦等
- 恢复提速 87%:故障后快速回血
额外红利:由于 Join 历史存于外部存储,还可复用于其他场景。有团队通过对外部表执行 Sort-Merge Join,将数据重处理时间从 4 小时缩短至 30 分钟。
07实践指南:配置、使用与适用场景
在 SQL 中使用 Delta Join
最棒的是:Delta Join 完全兼容标准 SQL,无需特殊语法。只需像平常一样写 JOIN:
SELECT * FROM orders INNER JOIN Product ON orders.productId = Product.id
只要满足条件,Flink 优化器会自动将其转换为 Delta Join:
- SQL 模式支持 Regular Join → Delta Join 转换
- 已配置合适的外部存储
在较新 Flink 版本中,这一转换通常自动发生。
关键配置参数
table.optimizer.delta-join.strategy='AUTO' # 自动决策是否启用 Delta Join(默认开启) table.exec.delta-join.cache-enabled='true' # 启用缓存(默认开启) table.exec.delta-join.left.cache-size=10000 # 左表缓存大小 table.exec.delta-join.right.cache-size=10000 # 右表缓存大小 table.exec.async-lookup.buffer-capacity=100 # 异步查询并发上限
何时使用 Delta Join?
Delta Join 在以下场景大放异彩:
- 高基数流式 enrichment:将海量事件流(点击、交易)与大型、高频更新的维表(用户画像、商品目录)关联,避免状态爆炸
- 实时可追溯性:所有 Join 历史存于外部存储,可精准审计任意计算所用数据
- 复杂变更追踪:在维表频繁增删改的同时,保持 Flink 内部状态极小化
08总结与展望
Delta Join(FLIP-486)远不止是一个新功能,它是大规模流处理思维方式的根本转变。
权衡非常清晰:用少量外部查询延迟,换取 Flink Checkpoint 域内状态管理的巨大简化。对于企业级实时应用而言,这是显而易见的选择。你将获得:
- 运维稳定性质的飞跃
- 成本降低 10 倍,资源效率提升 80%+
- 作业恢复速度飞升
目前,Delta Join 在 Fluss(尤其是其强大的前缀查询能力)上表现卓越。未来,随着对 Apache Paimon 等湖仓格式的支持落地,Delta Join 将成为整个流处理生态的标准能力。
来源 | Apache Flink公众号
作者 | 钟旭阳