为了账号安全,请及时绑定邮箱和手机立即绑定

揭秘Fluss核心功能 - Delta Join

标签:
大数据

背景:双流 Join 的状态问题

在 Flink 中,双流 Join 是流式数据处理中常见的需求,用于将两条流的数据基于某些关联条件合并起来。例如,在电商场景中,可以将用户的曝光流与订单流 Join,用于分析广告效果。然而,双流 Join 通常需要在 State(状态) 中保存全量的历史数据,以便为未来的 Join 操作提供上下文。这种设计在大规模数据处理中存在以下问题:

状态过大

  • 假设淘宝的一个应用场景,有两个数据流:
    • 曝光流:记录广告曝光事件,每天产生 10 亿条记录。
    • 订单流:记录用户下单事件,每天产生 1 亿条记录。
  • 双流 Join 需要在状态中存储曝光流的所有记录(50 亿条,约 10TB)以及订单流的所有记录(1 亿条,约 1TB)。因此,状态总量可能达到 50TB
  • 这导致 Checkpoint 非常耗时(可能需要数小时),并增加作业失败后的恢复时间。

高成本

  • 状态需要存储在内存或外部状态后端(如 RocksDB)中,这大大增加了集群的资源消耗。
  • 淘宝某 Flink 作业消耗的资源达到 2300 CU,成本高昂。

作业不稳定

  • 在高并发和高吞吐量下,状态的膨胀可能导致 Checkpoint 超时或任务失败,进一步增加了系统不稳定性。

我们先来解释一下,为什么在Flink 双流 Join 的时候通常需要有大量数据需要保存在 State 里面,看下面一个数据场景:

流数据的无界性

  • 流数据是无界的:流式数据是持续不断的,数据源不会终止。因此,Flink 必须在内存(或持久化到外部存储)中维护一部分历史数据,以便在未来的数据到达时能够完成 Join。
  • 无法预先确定 Join 的匹配时间:例如,在广告曝光与订单的 Join 中,可能一个用户今天看到广告,明天才下单。如果不保存过去的广告记录,就无法将订单与正确的广告关联。
例子:电商场景

假设我们有两个数据流:

  • 曝光流:记录用户的广告曝光,例如:

    曝光流:
    {"ad_id": 1, "user_id": 101, "time": "2025-01-08T10:00:00"}
    {"ad_id": 2, "user_id": 102, "time": "2025-01-08T11:00:00"}
    
  • 订单流:记录用户的下单,例如:

    订单流:
    {"order_id": 1001, "ad_id": 1, "user_id": 101, "time": "2025-01-08T12:00:00"}
    

在这个场景中:

  • 曝光事件和订单事件之间存在时间延迟(2小时)。

  • 为了完成 Join,Flink 必须将曝光流的所有记录保存起来,直到订单流中匹配的数据到达。

Join 的对称性

  • 双流 Join 是对称的:无论是哪一条流先到,Flink 都需要找到另一条流中是否有匹配的记录。
  • 保存上下文:为了满足这种对称性,无论是左流还是右流的记录到达,Flink 都需要保存所有的历史记录以供匹配。
例子
  • 曝光流到达时:Flink 必须检查当前是否有订单流中匹配的数据。
  • 订单流到达时:Flink 必须检查之前的曝光流中是否有匹配的数据。
  • 因此,必须保存两条流的历史数据。

窗口的限制

  • 非窗口化的 Join:如果 Join 没有时间范围限制(如窗口),Flink 必须保存所有的历史数据。
  • 窗口化的 Join:即使有窗口(如过去 1 小时的数据),Flink 仍需要保存窗口内的数据,直到窗口关闭。
例子:无窗口的 Join

假设没有时间窗口限制,用户的订单可能在一天甚至一周后才到达,那么所有的曝光数据都必须保存。

例子:有窗口的 Join

如果设置窗口为过去 1 小时:

  • Flink 只需要保存最近 1 小时内的曝光数据。
  • 但窗口过期之前,这些数据仍然需要保存在状态中。

上面这些例子可能就是我们在使用 Flink Join 的时候会使用State 保存大量数据,从而使得 Flink State 会膨胀很多倍。

Fluss 解决办法 -Delta Join

Fluss 的 CDC 流读+索引点查的能力研发了一套新的 Flink 的 Join 算子实现,叫 Delta Join。Delta Join 可以简单理解成“双边驱动的维表Join”,就是左边来了数据,就根据Join Key去点查右表;右边来了数据,就根据 Join Key 去点查左表。全程就像维表Join一样不需要state,但是实现了双流Join一样的语义,即任何一边有数据更新,都会触发对关联结果的更新

CDC 流读:Fluss 通过 CDC(Change Data Capture)实时捕获表的变更事件,并将这些事件作为流输入 Flink 作业。

  • 示例:假设有一个订单表,包含以下数据:

file

当订单状态更新时,例如订单 1001 的状态从 “pending” 变为 “completed”,这条数据的变更会先存储到 Fluss 的LogStore 和KvStore 然后Fluss 通过CDC 会捕获这一变更,并将其作为事件发送到 Flink。

索引点查:Fluss 将数据存储在其内部的高效存储引擎中,支持基于主键的快速点查。当任意一边有新数据到达时,通过 Join Key(如 user_id)实时点查另一边的数据。

  • 示例:假设有一个用户表,包含以下数据:

file

当订单流中出现新的订单,例如:

file

Flink 会根据 user_id=101 到 Fluss 中点查用户信息,获取对应的用户数据:

file

双边驱动:无论是左流(如订单流)还是右流(如用户流)有变更,都会通过实时点查实现双流 Join 的更新,而无需将全量数据加载到内存中。

  • 示例:如果用户表中新增一条记录:

file

当订单流中出现对应的订单时:

file

Flink 会根据 user_id=103 点查用户信息,获取 “Charlie” 的数据,实现实时 Join。

通过这种方式,Delta Join 避免了在 Flink 状态中维护全量数据的问题,利用 Fluss 的存储和点查能力,实现了高效、低延迟的双流 Join。

写在最后

下一篇文章会详细说一下为什么 Fluss 通过这样的点查会如此高效,会深入 Fluss 底层存储来说清楚查询和更新的流程。最后欢迎大家关注微信公众号 大圣数据星球 来讨论大数据技术。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消