Flink SQL 双表 JOIN 介绍与原理简析

基于Flink1.11.2 多表join与维表join 1,业务场景:之前离线的业务场景,想通过实时来实现。离线业务是多个表join,因为接入的myslqbinlog的数据,所以数据存在修改跟删除,这个是跟日志分析最大的区别,因为日志分析啥的数据只有新增,相对来说考虑的东西就少了很多。 2,首先我们要实现的是多表join,这个相对还是比较简单的,比如下面的SQL: 3,但是3个表join可是全量join的,也就是说表的state是一直存在的,总会存在OOM爆炸的时候,那怎么办呢。 在我们使用window窗口的时候,一个窗口结束之后会帮我们... 阅读详情

作者:董伟柯,腾讯 CSIG 高级工程师

综述

Flink 作为流式数据处理框架的领跑者,在吞吐量、时延、准确型、容错性等方面都有优异的表现。在 API 方面,它为用户提供了较底层的 DataStream API,也推出了 Table API 和 SQL 等编程接口。特别来看,SQL 以其易用、易迁移的特点,深受广大用户的欢迎。

在常见的数据分析场景中,JOIN(关联)操作是一项很有挑战性的工作,因为它涉及到左右两个表(流)的状态匹配,对内存的压力较大;而相比恒定的批数据而言,流数据更加难以预测,例如数据可能乱序、可能晚到,甚至可能丢失,因此需要缓存的状态量更加庞大,甚至会严重拖慢整体的数据处理进度。由此可见,流的 JOIN 并没有一个全能且通用的方案,我们必须在 低时延 和 高精准 等维度间进行取舍。

考虑到不同业务场景的时效性、准确型要求不同,Flink 提供了多种流式的 JOIN 操作,用户可以根据实际情况选择最适合自己的类型。下面我们对它们逐一进行介绍。

常规 JOIN(Regular JOIN)

常规 JOIN(Regular JOIN)是语法最简单的一类 JOIN,和传统数据库的 JOIN 语法完全一致。对于左表和右表的任何变动,都会触发实时计算和更新,因此它的结果是“逐步逼近”最终的精确值,也就是下游可能看到变来变去的结果。为了支持结果的更新,下游目的表需要 定义主键 (PRIMARY KEY NOT ENFORCED)。

常规 JOIN 支持 INNER、LEFT、RIGHT 等多种 JOIN 类型。其中 INNER JOIN 只会下发 Upsert 数据流(即只有更新和插入,没有删除操作),而 LEFT 和 RIGHT JOIN 则会下发更多类型的 Changelog 数据流(包含了插入、更新、删除等各种类型)。对于各类数据流的区别和转化,请参见 Flink 官方文档:动态表。

常规 JOIN 运行时需要保留左表和右表的状态,且随着时间的推移,状态会无限增长,最终可能导致作业 OOM 崩溃或异常缓慢。因此我们强烈建议用户在 Flink 参数中设置 table.exec.state.ttl 选项,它可以指定 JOIN 状态的保留时间,以便 Flink 及时清理过期的状态。

下面是一个使用常规 JOIN 的示例作业:

CREATE TABLE `Order`
(
  id         INT,
  product_id INT,
  quantity   INT,
  order_time TIMESTAMP,
   PRIMARY KEY (id) NOT ENFORCED
) WITH (
     'connector' = 'datagen',
     'fields.id.kind' = 'sequence',
     'fields.id.start' = '1',
     'fields.id.end' = '100000',
     'fields.product_id.min' = '1',
     'fields.product_id.max' = '100',
     'rows-per-second' = '1'
);


CREATE TABLE `Product`
(
  id    INT,
  name  VARCHAR,
  price DOUBLE,
   PRIMARY KEY (id) NOT ENFORCED
) WITH (
     'connector' = 'datagen',
     'fields.id.min' = '1',
     'fields.id.max' = '100',
     'rows-per-second' = '1'
);


CREATE TABLE `OrderDetails`
(
  id           INT,
  product_name VARCHAR,
  total_price  DOUBLE,
  order_time   TIMESTAMP,
   PRIMARY KEY (id) NOT ENFORCED
) WITH (
     'connector' = 'print'
);


INSERT INTO `OrderDetails`
SELECT o.id, p.name, o.quantity * p.price, o.order_time
FROM `Order` o
INNER JOIN
`Product` p
ON o.product_id = p.id;

我们来看一下这个 SQL 作业生成的物理计划(红框标明的是 JOIN 部分):

b2a455d8b9d49d649f8ed85981964883.png

可以看到,我们的双表 Regular JOIN 语句最终生成了 Join 算子,它从两个数据源里获取数据,且数据根据我们的 JOIN 键来进行哈希分配。

在该 Flink 作业的运行时,实际执行 JOIN 逻辑的是 org.apache.flink.table.runtime.operators.join.stream.StreamingJoinOperator。从类定义上来看,它属于 TwoInputStreamOperator,即接收两个数据输入的算子。左右两表的状态保存在两个类型为 JoinRecordStateView 实例变量(leftRecordStateView、rightRecordStateView),而具体的关联逻辑在它的 processElement 方法中。由于源码注释非常清晰,这里就不再赘述,感兴趣的小伙伴可以阅读 StreamingJoinOperator 的源码。

时间区间 JOIN(Interval JOIN)

时间区间 JOIN 是另一种关联策略,它与上述的常规 JOIN 不同之处在于,左右表仅在某个时间范围(给定上界和下界)内进行关联,且只支持普通 Append 数据流,不支持含 Retract 的动态表。如下图(来自 Flink 官方文档)。它的好处是由于给定了关联的区间,因此只需要保留很少的状态,内存压力较小。但是缺点是如果关联的数据晚到或者早到,导致落不到 JOIN 区间内,就可能导致结果不准确。此外,只有当区间过了以后,JOIN 结果才会输出,因此会有一定的延迟存在。

ae5497fbff1228bd5b53a83e60a48898.png

我们同样来构造一个时间区间 JOIN 的示例作业:

CREATE TABLE `Order`
(
  id         INT,
  product_id INT,
  quantity   INT,
  order_time TIMESTAMP(3),
  WATERMARK FOR order_time AS order_time,
   PRIMARY KEY (id) NOT ENFORCED
) WITH (
     'connector' = 'datagen',
     'fields.id.kind' = 'sequence',
     'fields.id.start' = '1',
     'fields.id.end' = '100000',
     'fields.product_id.min' = '1',
     'fields.product_id.max' = '100',
     'rows-per-second' = '1'
);


CREATE TABLE `Product`
(
  id          INT,
  name        VARCHAR,
  price       DOUBLE,
  record_time TIMESTAMP(3),
  WATERMARK FOR record_time AS record_time,
   PRIMARY KEY (id) NOT ENFORCED
) WITH (
     'connector' = 'datagen',
     'fields.id.min' = '1',
     'fields.id.max' = '100',
     'rows-per-second' = '1'
);


CREATE TABLE `OrderDetails`
(
  id           INT,
  product_name VARCHAR,
  total_price  DOUBLE,
  order_time   TIMESTAMP
) WITH (
     'connector' = 'print'
);


INSERT INTO `OrderDetails`
SELECT o.id, p.name, o.quantity * p.price, o.order_time
FROM `Order` o,
    `Product` p
WHERE o.product_id = p.id
 AND o.order_time BETWEEN p.record_time - INTERVAL '5' MINUTE AND p.record_time;

可以看到,时间区间 JOIN 是在 SQL 的 WHERE 条件里限定了关联的时间区间,因此要求输入的两个表都必须有 时间戳字段 且将该时间戳字段用作 WATERMARK FOR 语句指定的时间字段。如果表实在没有时间戳字段,则可以使用 PROCTIME() 函数来生成一个处理时间戳。

特别注意:请不要直接使用未定义 WATERMARK 或 PROCTIME() 的原始 TIMESTAMP 类型字段,否则可能会退回到上述的 “常规 JOIN”。

它的语法树、优化后的物理计划,以及最终执行计划(红框标明的是 JOIN 部分)如下,可以看到算子已经由之前的 Join 变成了 IntervalJoin:

b5d6d0e98a55e3ec9a2cc2c1788a4663.png

在运行时,Flink 会调用  org.apache.flink.table.runtime.operators.join.interval.TimeIntervalJoin 执行具体的关联操作,具体的 JOIN 逻辑在 org.apache.flink.table.runtime.operators.join.interval.IntervalJoinFunction。同样地,我们可以阅读 IntervalJoinFunction 的源码来查看它的细节。

窗口 JOIN

窗口 JOIN 也是用法非常简单的一种 JOIN 类型。它以窗口为界,对窗口里面的左表、右表数据进行关联操作。由于 Flink 支持滑动(TUMBLE)、滚动(HOP 也叫做 SLIDING)、会话(SESSION)等不同窗口类型,因此可以根据业务需求进行选择。

窗口 JOIN 不强制要求左右表必须包含时间戳字段,但是如果您使用时间相关窗口的话,也需要提供相关的时间戳来划分窗口。

和上述 时间区间 JOIN 类似,窗口 JOIN 的输出也是最终值,也就是说不会出现 常规 JOIN 那样不断变动的结果。但是缺点也一样,它只能在窗口结束后输出关联结果,且对于早到或者晚到等不在窗口内的数据是无法参与计算的,因此实时性和准确性方面都相对较差。

下面是窗口 JOIN 的一个示例程序:

CREATE TABLE `Order`
(
  id         INT,
  product_id INT,
  quantity   INT,
  order_time TIMESTAMP(3),
   PRIMARY KEY (id) NOT ENFORCED,
  WATERMARK FOR order_time AS order_time
) WITH (
     'connector' = 'datagen',
     'fields.id.kind' = 'sequence',
     'fields.id.start' = '1',
     'fields.id.end' = '100000',
     'fields.product_id.min' = '1',
     'fields.product_id.max' = '100',
     'rows-per-second' = '1'
);


CREATE TABLE `Product`
(
  id          INT,
  name        VARCHAR,
  price       DOUBLE,
  record_time TIMESTAMP(3),
   PRIMARY KEY (id) NOT ENFORCED,
  WATERMARK FOR record_time AS record_time
) WITH (
     'connector' = 'datagen',
     'fields.id.min' = '1',
     'fields.id.max' = '100',
     'rows-per-second' = '1'
);


CREATE TABLE `OrderDetails`
(
  id           INT,
  product_name VARCHAR,
  total_price  DOUBLE,
  order_time   TIMESTAMP,
   PRIMARY KEY (id) NOT ENFORCED
) WITH (
     'connector' = 'print'
);


INSERT INTO `OrderDetails`
SELECT o.id, p.name, o.quantity * p.price, o.order_time
FROM ( SELECT * FROM TABLE(TUMBLE(TABLE `Order`, DESCRIPTOR(order_time), INTERVAL '5' SECOND))) AS o,
    ( SELECT * FROM TABLE(TUMBLE(TABLE `Product`, DESCRIPTOR(record_time), INTERVAL '5' SECOND))) AS p
WHERE o.product_id = p.id AND o.window_start = p.window_start AND o.window_end = p.window_end;

下面是它的物理计划,可以看到它的算子已经变成了 WindowJoin:

7823b493cce78c7ceace44431e7fc5f1.png

在运行时,Flink 调用的是 org.apache.flink.table.runtime.operators.join.window.WindowJoinOperator。点击阅读 WindowJoinOperator 的源码。

时态表 JOIN(Temporal JOIN)

时态表 JOIN 是一类特殊的关联操作:本文前半部分介绍的各种 JOIN 类型都是基于最新的数据进行关联,而 时态表 JOIN 则可以根据左表记录中的时间戳,在右表的历史版本中进行查询和关联。例如我们的商品价格表会随时间不断变动,左表来了一条时间戳为 10:00 的订单记录,那么它会对右表在 10:00 的商品价格快照(当时的价格)进行关联并输出结果;如果随后左表来了一条 10:30 的订单记录,那么它会对右表在 10:30 时的商品价格进行后续的关联。这种特性对于统计不断变动的时序数据非常有用。

时态表 JOIN 分为 事件时间(Event Time) 和 处理时间(Processing Time) 两种类型,且只支持 INNER 和 LEFT JOIN。由于基于处理时间的时态表 JOIN 存在 Bug(参见 FLINK-19830),因此在最新的 Flink 版本中已被禁用。我们这里主要介绍基于事件时间的时态表 JOIN。

由于时态表 JOIN 需要得知不同时刻下右表的不同版本,因此它的右表必须是 Changelog 动态表(即 Upsert、Retract 数据流,而非 Append 数据流),且两侧的源表都必须定义 WATERMARK FOR。随着 Watermark 水位推进,Flink 可以逐步清理失效的数据,因此时态表 JOIN 的内存压力相对也不大。此外,还要求时态表的主键必须包含在 JOIN 等值条件中。

下面是时态表 JOIN 的一个 SQL 示例程序,它的语法特点是 FOR SYSTEM_TIME AS OF 语句:

CREATE TABLE `Order`
(
  id         INT,
  product_id INT,
  quantity   INT,
  order_time TIMESTAMP(3),
   PRIMARY KEY (id) NOT ENFORCED,
  WATERMARK FOR order_time AS order_time
) WITH (
     'connector' = 'datagen',
     'fields.id.kind' = 'sequence',
     'fields.id.start' = '1',
     'fields.id.end' = '100000',
     'fields.product_id.min' = '1',
     'fields.product_id.max' = '100',
     'rows-per-second' = '1'
);


CREATE TABLE `Product`
(
  id          INT,
  name        VARCHAR,
  price       DOUBLE,
  record_time TIMESTAMP(3),
   PRIMARY KEY (id) NOT ENFORCED,
  WATERMARK FOR record_time AS record_time
) WITH (
     'connector' = 'datagen',
     'fields.id.min' = '1',
     'fields.id.max' = '100',
     'rows-per-second' = '1'
);


CREATE TABLE `OrderDetails`
(
  id           INT,
  product_name VARCHAR,
  total_price  DOUBLE,
  order_time   TIMESTAMP,
   PRIMARY KEY (id) NOT ENFORCED
) WITH (
     'connector' = 'print'
);


INSERT INTO `OrderDetails`
SELECT o.id, p.name, o.quantity * p.price, o.order_time
FROM `Order` o
        JOIN `Product` FOR SYSTEM_TIME AS OF o.order_time p
             ON o.product_id = p.id;

它的物理计划如下,可以看到生成的是 TemporalJoin 算子:

4abee12b22e5ab7c9f927f1d6606d834.png

我们照例看一下运行时,Flink 调用的是 org.apache.flink.table.runtime.operators.join.temporal.TemporalRowTimeJoinOperator。通过阅读 TemporalRowTimeJoinOperator 的源码,可以了解更多详情。

时态表函数 JOIN(Temporal Table Function JOIN)

Flink 很早就支持一种名为 时态表函数 JOIN 的关联操作,它允许用户对一个自定义表函数 UDTF 执行关联操作。换句话说,UDTF 可以返回一张虚拟表,它可以是从外部系统实时查到的,也可以是动态生成的,非常灵活。在没有上述各种复杂 JOIN 的上古年代,这是为数不多表关联方法。它只支持 Append 流,且总是保留左右表的关联状态,因此存在一定的内存压力。

下面是一个时态表函数 JOIN 的示例 SQL:

CREATE TEMPORARY SYSTEM FUNCTION ProductFunc
AS 'com.tencent.cloud.test.ProductFunc' LANGUAGE JAVA;


CREATE TABLE `Order`
(
  id         INT,
  product_id INT,
  quantity   INT,
  order_time TIMESTAMP(3),
  proc_time AS PROCTIME(),
   PRIMARY KEY (id) NOT ENFORCED
) WITH (
     'connector' = 'datagen',
     'fields.id.kind' = 'sequence',
     'fields.id.start' = '1',
     'fields.id.end' = '100000',
     'fields.product_id.min' = '1',
     'fields.product_id.max' = '100',
     'rows-per-second' = '1'
);


CREATE TABLE `OrderDetails`
(
  id           INT,
  product_name VARCHAR,
  total_price  DOUBLE,
  order_time   TIMESTAMP(3),
   PRIMARY KEY (id) NOT ENFORCED
) WITH (
     'connector' = 'print'
);


INSERT INTO `OrderDetails`
SELECT o.id, p.name, o.quantity * p.price, o.order_time
FROM `Order` o,
    LATERAL TABLE(`ProductFunc`(o.proc_time)) p
WHERE o.product_id = p.id;

其中的表函数  ProductFunc 的定义如下:

package com.tencent.cloud.test;


import org.apache.flink.table.annotation.DataTypeHint;
import org.apache.flink.table.annotation.FunctionHint;
import org.apache.flink.table.functions.TableFunction;
import org.apache.flink.types.Row;


import java.sql.Timestamp;


@FunctionHint(output = @DataTypeHint("ROW<id INT, name VARCHAR, price DOUBLE, record_time TIMESTAMP(3)>"))
public class ProductFunc extends TableFunction<Row> {
public void eval(Timestamp t) {
Row data = new Row(4);
data.setField(0, 1);// 可自行模拟数据
data.setField(1, "name");
data.setField(2, 1.0);
data.setField(3, t.toLocalDateTime());
collect(data);
}
}

下面是它的物理计划,可以看到被翻译成了 Correlate 算子,和上面其他 JOIN 类型的算子都不一样:

4110ef5b3ce3454eb52cf60daab482cf.png

而在运行时,Flink 调用的是 org.apache.flink.table.planner.plan.nodes.exec.stream.StreamExecCorrelate。同样地,我们可以阅读 StreamExecCorrelate 的源码。

需要注意的是,在处理时间(Processing Time)模式下, FLINK-19830 提到的 Bug 仍然存在,只是考虑到历史兼容性,Flink 没有禁止在 时态表函数 JOIN 使用该模式。然而,数据不准确的问题仍然会出现,因此要多多留意。

总结

本文简述了目前 Flink SQL 所有可用的 JOIN 类型,说明了他们各自的应用场景,并提供了示例 SQL 和执行计划,以及运行时 Flink 调用的相关类。可以看到,Flink 在 JOIN 方面提供了丰富的工具箱,满足了大多数场景下的 JOIN 逻辑。

下表是本文提到的各类 JOIN 的总结:

JOIN 类型实时性准确度支持的时间戳类型
常规 JOIN高先低后高(逐步更新)事件时间、处理时间
时间区间 JOIN中中(取决于区间大小)事件时间、处理时间
窗口 JOIN低低(取决于窗口大小和类型)事件时间、处理时间
时态表 JOIN中高(取决于具体实现)事件时间
时态表函数 JOIN中高(取决于具体实现)事件时间、处理时间(但有 Bug)

如果确实有业务场景不适合 SQL 描述,Flink 还提供了 DataStream API 来实现更灵活的关联操作。例如通过 异步算子 和 状态缓存,我们可以设计出高性能、低时延的关联逻辑。我们会在后续的文章中逐步讲解如何应对这些高要求的 JOIN 场景。

流计算 Oceanus 是大数据产品生态体系的实时化分析利器,是基于 Apache Flink 构建的具备一站开发、无缝连接、亚秒延时、低廉成本、安全稳定等特点的企业级实时大数据分析平台。我们针对常见的 JOIN 场景也有自己独特的性能优化,欢迎大家 1 元试用,也欢迎阅读更多我们的 专栏文章 来学习更多 Flink 知识和技能 :)

Flink双流JOIN 阅读详情

相关推荐

从零开始学Flink:实时数仓与维表时态Join实战

以电商订单实时数仓为例,演示如何在 Flink SQL 中通过维表时态 Join 将事实流与维度数据关联,构建带用户属性的明细宽表,并结合 Kafka 与 MySQL 环境完成一套可落地的实时数仓入门实践。

专注大数据与 Java 全栈技术栈。深耕 Flink 实时计算、Spring Boot 源码解析与高并发系统架构设计。拒绝浅尝辄止,坚持源码级剖析与生产环境实战复盘,致力于为开发者提供有深度的技术干货与架构思维。代码不止,匠心不息。 1743

FlinkSql中的时间语义和TTL

专门用来保存当前的处理时间。在创建表的DDL中定义。

qq_42456324的博客 6326

Flink Windows 多流 Join 和 SQL Join

Flink Windows Join 都是 Inner Join 两个流join时,先做join操作,形成JoinedStream,然后再指定Window,最后接着join后的transform操作。 案例: inputStream1:DataStream[(Long,String,Int)] = ... inputStream2:DataStream[(String,Long,Int)] = ... //通过DataStream Join方法将两个数据流关联 inputStream1.join(input

技术大神之路 3723

【Flink】实时归因场景踩坑

背景 前几天思考了一个问题,在很多业务场景下,需要关注流量的来源或是某个业务哪个入口的流量最大,带来的效益最多,那么就涉及到流量的归因了。比如说,我是一个bilibili up主,那么我想知道我的某个视频到底是首页推荐的流量比较多,还是用户搜索带来的比较多。我觉得得分为两种情况 应用埋点质量非常差的情况下,那么在一些APP或者H5发展之初,是不会太去注重埋点的质量,当流量密码时代到来了,才发现这是一个风口,埋点标准化改造就是一个必不可少的环节。那么在改造之前,就只能靠数据自己去归因,即按照时间窗口,根据用

weixin_43272605的博客 4195

Flink的Look Up Join详解

- 定义主流订单表(从 Kafka/Socket 等)) WITH ('connector' = 'kafka', -- 示例,这里仅做说明-- 定义 MySQL 维表) WITH ('lookup.cache.max-rows' = '5000', -- 启用缓存,最多缓存 5000 条'lookup.cache.ttl' = '60s', -- 缓存条目的 TTL 为 60 秒'lookup.max-retries' = '3' -- 遇到网络或其他错误时最多重试 3 次。

weixin_50227709的博客 2881

从零开始学Flink:Flink SQL四大Join解析

深入解析 Flink SQL 四大 Join(Regular, Interval, Temporal, Lookup),结合电商场景实战,剖析生产环境中乱序数据处理及性能优化策略。

专注大数据与 Java 全栈技术栈。深耕 Flink 实时计算、Spring Boot 源码解析与高并发系统架构设计。拒绝浅尝辄止,坚持源码级剖析与生产环境实战复盘,致力于为开发者提供有深度的技术干货与架构思维。代码不止,匠心不息。 1195

Flink SQL 优化实战 - 维表 JOIN 优化

从源码层面分析维表 Join 原理及调优方案

cloudbigdata的博客 7667

Flink Join 核心解析:类型、原理、实操

Flink Join 核心解析:类型、原理、实操

luohualiushui1的专栏 1155

58、Flink维表的实战-6种实现方式维表的join

一、Flink 专栏Flink 专栏系统介绍某一知识点,并辅以具体的示例进行说明。1、Flink 部署系列 本部分介绍Flink的部署、配置相关基础内容。2、Flink基础系列 本部分介绍Flink 的基础部分,比如术语、架构、编程模型、编程指南、基本的datastream api用法、四大基石等内容。3、Flik Table API和SQL基础系列 本部分介绍Flink Table Api和SQL的基本用法,比如Table API和SQL创建库、表用法、查询、窗口函数、catalog等等内容。4、Flik

alanchanchn的专栏 7万+

FLink 源码分析--(四)Regular Join(普通join)

文章目录一、Flink Join介绍1、Join分类A、Regular Join(普通join)B、Time Window JoinC、Join LETERALD、Join Temporal Table时态表E、维表Join2、Regular Join说明3、Regular Join特性 一、Flink Join介绍 1、Join分类 在上一篇文章FLink 源码分析–(三)Time Window...

LS_ice的专栏 4410

Flink SQL join类型

flink sql join类型讲解

chanyue123的博客 2580

Flink Redis维表:Broadcast Join与Lookup Join对比及SQL示例

本文对比了Flink中Redis维表关联的两种方案:Broadcast Join和Lookup Join。Broadcast Join适合小维表(<1GB),通过广播到所有任务实现本地关联,延迟低但内存消耗高;Lookup Join则适合大维表,通过实时查询Redis获取最新数据,内存占用低但延迟较高。文章详细分析了两者的原理、适用场景和性能差异,并提供了两种方案的SQL实现示例。最后建议根据维表大小、更新频率和资源情况选择合适方案,或采用混合模式平衡性能与资源。

spark_dev的博客 1306

【Flink】FlinkSQL的DataGen连接器(测试利器)

我们在实际开发过程中可以使用FlinkSQL的DataGen连接器实现FlinkSQL的批或者流模拟数据生成,DataGen 连接器允许按数据生成规则进行读取,但注意:DataGen连接器不支持复杂类型: Array,Map,Row。 请用计算列构造这些类型

一杯咖啡半杯糖的博客 1521

Flink SQL 中的 Join 操作:从原理到实践,一篇讲透

Flink SQL中的Join操作是实时数据处理的核心组件,本文深入解析了不同Join类型的特点及应用场景。与传统数据库不同,流式Join需要解决历史数据存储和延迟处理问题。主要Join类型包括:内连接(保留匹配数据)、外连接(保留不匹配数据)、间隔连接(基于时间范围关联)和Lookup Join(流与维表关联)。文章特别强调了性能优化技巧,如合理设置状态TTL、使用Interval Join自动清理状态、优化维表缓存策略等,并提供了常见问题解决方案。

SengMay的专栏 1022

flink实战--双流join之原理解析

JOIN简介 谈flink双流JOIN之前,我们先谈一下大家最熟悉的mysql表join,我们知道表join有如下几种,具体区别就不在介绍了。那么流的join和表的join有什么区别呢?本文我们介绍一下。 CROSS JOIN - 交叉连接,计算笛卡儿积; INNER JOIN - 内连接,返回满足条件的记录; OUTER JOIN LEFT - 返回左表所有行,右表不存...

阿华田的博客 9329

Flink sql join的4种不同类型

1. UnBounded的双流JOIN: https://developer.aliyun.com/article/672760?spm=a2c6h.13262185.0.0.6c007e186Tt5n 2. 单流与UDTF的JOIN操作: https://developer.aliyun.com/article/674345?spm=a2c6h.12873639.0.0.47903da5UARE9V 3. 单流与版本表的JOIN: https://developer.aliyun.com/

litlit023的专栏 2136

Flink join终结者:SQL Join

点击上方蓝 字关注~ SQL是开发人员与数据分析师必备的技能,Flink也提供了Sql方式编写任务,能够很大程度降低开发运维成本,这篇是flink join的终极篇SQL Join, 首先介绍sql join使用方式、然后介绍global join带来的状态存储成本及解决方式、最后从源码角度分析sql join实现。 一、SQL JOIN使...

张普的专栏 3732

CH341的I2C流模式(StreamI2C)到底怎么用?从EEPROM读写到自定义设备,一篇讲清参数配置逻辑

本文详细解析了CH341的I2C流模式(StreamI2C)的使用方法,从EEPROM读写到自定义设备的参数配置逻辑。通过深入分析I2C协议基础、StreamI2C参数映射关系及典型设备配置示例,帮助开发者快速掌握StreamI2C的应用技巧,解决实际开发中的常见问题。

weixin_30932215的博客 445
上一篇: 腾讯云参编两项隐私计算报告发布,着力推动数据有序共享
下一篇: 一份数据满足所有数据场景?腾讯云数据湖解决方案及DLC内核技术介绍
腾讯云大数据
博客等级 码龄6年 348粉丝 217原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值