前言
在实时数仓场景中,维表(dimension table)的 Join 是常见需求。本文总结几种常见方案,便于开发人员在实际场景中进行权衡选择:
查找关联(同步、异步)、状态编程,将数据预加载到状态中并按需取用,以及热/冷数据的管理、广播维表、TemPortal Table Join、Lookup Table Join 等思路的要点与适用范围。
文中留有两个思考点,欢迎留言讨论。
查找关联
查找关联指在主数据流中直接访问外部数据源(如 MySQL、Redis、Impala 等),通过主键或其他条件进行关联取值。
适用场景:维表数据量较大但主数据量较小的实时计算场景。
缺点:数据量大时会对外部数据源造成压力,因为每条主数据都需要进行外部查询。
同步
通过同步调用访问数据库,可能导致 SubTask 线程阻塞,吞吐量下降。

示例代码与描述性文本在此处保留为片段式说明,用以说明如何在实际处理流程中引入外部数据的同步访问。
优点:外部数据在事件处理前加载到本地状态,便于后续离线或近似实时查询,减少对外部服务的频繁访问。
缺点:不利于大规模维表或高变动维表的场景,需要更多的缓存/本地存储来维持一致性。
下面给出一个简要说明的伪代码结构,帮助理解该方案的工作思路:
(伪代码略,用于描述数据流中的同步查询点与状态交互)

进一步思考与实现要点
思考:直接使用 Map 集合来缓存维表数据有哪些利弊?读写性能、数据一致性、容量控制等方面需要权衡。
冷热数据
思路:先从状态中取值;若未命中,再向外部数据源查询,并将结果缓存到状态中。StateTTL 的过期时间可视具体场景设置得更短一些,以保持数据的新鲜度。

优点:折中取值策略,热备数据可快速命中,减少外部查询。向外部拉取的数据也会被持久化到状态中,便于后续处理。
缺点:不能一劳永逸解决所有问题;热备数据过多、或冷数据过大时,仍会对状态和外部数据库造成压力。
相关实现要点(示意)
.filter(_._1 != null) .keyBy(_._1) .Process(new KeyedProcessFunction[StRing, (StRing, StRing, StRing, StRing, StRing), StRing] {
// 省略具体实现,核心是将维表数据初始化到状态,定期更新、清理与查询逻辑
})

广播维表
当每个 Task 需要同一份字典表数据时,可以将维表通过广播流方式广播到主数据流中进行 Join。

广播维表的基本思路:通过广播状态描述符存储维表数据,在主数据流中进行查找匹配;广播状态与主数据流的结合可能采用 KeyedBroadcastProcessFunction 等组合方式进行实现。
示例性描述:广播数据流与主数据流连接,进行 Keyed 处理后输出结果。
“思考:若将维表流也通过实时监控日志(如变更日志)并写回 Kafka,再以广播方式更新广播状态,是否能提升时效性?”
(1) 将变更日志经 Canal 等组件写入 Kafka;(2) 数据流定义为广播流,广播更新广播状态;(3) 在主流进行查找匹配,满足条件则完成 Join。
TemPortal Table Join(时态表 Join,Flink SQL 与 Flink Table API)
维表是持续变化的表;若使用传统 JOIN 语法对维表进行 Join,无法确定关联的是维表的哪个时刻的快照。因此,Flink SQL 引入时态表(Temporal Table)的概念,用以表达对维表某个时刻的快照进行关联。
普通关联会保留双边数据,导致数据膨胀,易造成内存压力;时态 Join 能定期清理过期数据,在合适的内存配置下避免内存溢出。

事件时间与时态 Join 的语法与要点,适用于需要对随时间更新的维表进行一致性且带有时效性的 Join 场景。
总结:在实际场景中,需结合数据量、变动频率、延迟要求等因素,综合考虑以上方案的性能、复杂度与维护成本,选择最合适的维表 Join 方案。
