FlinkCDC Java DataStream API 的异源双流 JOIN

1 说明

1.1 案例说明

本文使用 Flink CDC 2.2 最新版本及 Flink 1.14 版本通过 DataStream API 做双表(产品表/订单表)流 Join 操作案例。

产品表:MySQL 数据源中。
订单表:PostgreSQL 数据源中。

双流Join大致流程:
异源双流Join流程图

案例具体划分有:

  • 抽取 PostgreSQL 单表案例
    • 抽取订单表 JsonDebezium 格式打印控制台
    • 抽取订单表 JsonObjectDebezium 格式打印控制台
  • 处理时间窗口 Join 联结
    • 处理时间窗口 内联结 案例
    • 处理时间窗口 外联结 案例

案例大致流程如下:
双流 ETL 流程


1.2 软件说明

软件说明如下:

软件版本
Flink1.14.4
Flink CDC2.2
MySQL8.0.24
PostgreSQL12.10

说明:

  • MySQL 服务器必须开启 binlog功能。
  • PostgreSQL 需要开启日志功能。
  • Flink 官网(https://flink.apache.org/)
  • Flink CDC 官网(https://github.com/ververica/flink-cdc-connectors)
  • PostgreSQL 官网(https://www.postgresql.org/)
  • MySQL 官网(https://www.mysql.com/)

1.3 数据表字段说明

产品表(products)字段说明:

[
    id int primary key COMMENT '主键',
    name varchar(255) not null COMMENT '产品名称',
]

订单表(orders)字段说明:

[
    order_id int4 primary key COMMENT '主键',
    order_name varchar(255) not null COMMENT '订单名称',
    order_date timestamp(3) not null COMMENT '订单时间',
    order_price numeric(10,2) not null COMMENT '订单价格',
    product_id int4 not null COMMENT '产品id'
]

1.4 数据流字段说明

产品表(products)数据流字段说明:

[
  id:  int,   // 主键id
  name: string, // 产品名称
  op: string, // 数据操作类型
  ts_ms: long // 毫秒时间戳
]

订单表(orders)数据流字段说明:

[
  order_id:  int,   // 订单主键id
  order_name: string, // 订单名称
  order_date: long,  // 订单时间,毫秒时间戳
  order_price: string,  // 订单价格
  product_id: int,  // 产品id
  op: string, // 数据操作类型
  ts_ms: long // 毫秒时间戳
]

2 准备工作

2.1 MySQL 数据准备

用 Navicat 连接 MySQL,建表及初始化数据 SQL 语句如下:

-- 创建数据库 flinkcdc_product_manage
create database flinkcdc_product_manage;

-- 使用数据库 flinkcdc_product_manage
use flinkcdc_product_manage;

-- 创建产品表
CREATE TABLE products (
  id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '主键' ,
  name VARCHAR(255) NOT NULL COMMENT '产品名称'
)AUTO_INCREMENT = 101 COMMENT = '产品表';

-- 插入两条数据
INSERT INTO products VALUES (default,"篮球"),(default,"乒乒球");

截图:
MySQL 数据准备


2.2 PostgreSQL 数据准备

用 Navicat 连接 PostgreSQL,建表及初始化数据 SQL 语句如下:

-- 创建数据库 flinkcdc_order_manage
create database flinkcdc_order_manage;

-- 使用数据库 flinkcdc_order_manage
use flinkcdc_order_manage;

-- 创建订单表
CREATE TABLE orders (
  order_id SERIAL NOT NULL PRIMARY KEY,
  order_name VARCHAR(255) NOT NULL,
  order_date TIMESTAMP(3) NOT NULL ,
  order_price DECIMAL(10, 2) NOT NULL,
  product_id INTEGER NOT NULL
);

-- 添加注释
COMMENT ON COLUMN "public"."orders"."order_id" IS '订单主键';
COMMENT ON COLUMN "public"."orders"."order_name" IS '订单名称';
COMMENT ON COLUMN "public"."orders"."order_date" IS '订单时间';
COMMENT ON COLUMN "public"."orders"."order_price" IS '订单价格';
COMMENT ON COLUMN "public"."orders"."product_id" IS '产品id';
COMMENT ON TABLE "public"."orders" IS '订单表';

ALTER SEQUENCE public.orders_order_id_seq RESTART WITH 1001;
ALTER TABLE public.orders REPLICA IDENTITY FULL;

-- 插入数据
INSERT INTO orders VALUES (default, '篮球订单', '2022-05-13 17:08:22', 88.88, 101);

截图:
PostgreSQL 数据准备
注意:

  • 这里的 order_date 的数据类型 timestamp(3),目的是为了保证抓取时时间戳的为 毫秒格式,一般而言,基本上都是用毫秒。如果指定 timestamp(6),则代表使用 微秒 格式时间戳。
  • 官网链接说明如下:
https://debezium.io/documentation/reference/1.9/connectors/postgresql.html#postgresql-temporal-values

Debezium 中时间戳问题说明


2.3 项目相关

2.3.1 项目结构

在 IDEA 中创建 maven 项目,项目结构截图如下:
异源双流 JOIN 项目工程图


2.3.2 项目依赖

父工程(datastream-etl-demo)的 pom.xml 文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.mfox</groupId>
    <artifactId>datastream-etl-demo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>telephone-etl-demo</module>
        <module>teacher-course-etl-demo</module>
        <module>datastream-common</module>
        <module>product-orders-etl-demo</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <flink.version>1.14.4</flink.version>
        <scala.binary.version>2.12</scala.binary.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-streaming-java_2.12</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-java</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-clients_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-connector-kafka_${scala.binary.version}</artifactId>
            <version>${flink.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.flink</groupId>
            <artifactId>flink-table-planner-blink_2.12</artifactId>
            <version>1.12.0</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>

        <dependency>
            <groupId>com.ververica</groupId>
            <artifactId>flink-sql-connector-mysql-cdc</artifactId>
            <version>2.2.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

子工程(datastream-etl-demo)的 pom.xml 文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>datastream-etl-demo</artifactId>
        <groupId>cn.mfox</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>product-orders-etl-demo</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>cn.mfox</groupId>
            <artifactId>datastream-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.ververica</groupId>
            <artifactId>flink-sql-connector-postgres-cdc</artifactId>
            <version>2.2.0</version>
        </dependency>
    </dependencies>

</project>

2.3.3 基础类

  • OpEnum.java
  • TransformUtil.java
    common 包相关类
    OpEnum.java 枚举类内容如下:
package cn.mfox.common.enumeration;

/**
 * CDC 中 op类型
 *
 * @author hy
 * @version 1.0
 * @date 2022/5/12 17:28
 */
public enum OpEnum {

    /**
     * 新增
     */
    CREATE("c", "create", "新增"),

    /**
     * 修改
     */
    UPDATA("u", "update", "更新"),

    /**
     * 删除
     */
    DELETE("d", "delete", "删除"),

    /**
     * 读
     */
    READ("r", "read", "读");

    /**
     * 字典码
     */
    private String dictCode;

    /**
     * 字典码翻译值
     */
    private String dictValue;

    /**
     * 字典码描述
     */
    private String description;

    OpEnum(String dictCode, String dictValue, String description) {
        this.dictCode = dictCode;
        this.dictValue = dictValue;
        this.description = description;
    }

    public String getDictCode() {
        return dictCode;
    }

    public String getDictValue() {
        return dictValue;
    }

    public String getDescription() {
        return description;
    }
}

TransformUtil.java 工具类内容如下:

package cn.mfox.common.utils;

import cn.mfox.common.enumeration.OpEnum;
import com.alibaba.fastjson.JSONObject;

/**
 * 转换工具类
 *
 * @author hy
 * @version 1.0
 * @date 2022/5/12 16:25
 */
public class TransformUtil {

    /**
     * 格式化抽取数据格式
     * 去除before、after、source等冗余内容
     *
     * @param extractData 抽取的数据
     * @return
     */
    public static JSONObject formatResult(String extractData) {
        JSONObject formatDataObj = new JSONObject();
        JSONObject rawDataObj = JSONObject.parseObject(extractData);
        formatDataObj.putAll(rawDataObj);
        formatDataObj.remove("before");
        formatDataObj.remove("after");
        formatDataObj.remove("source");
        String op = rawDataObj.getString("op");
        if (OpEnum.DELETE.getDictCode().equals(op)) {
            // 新增取 before结构体数据
            formatDataObj.putAll(rawDataObj.getJSONObject("before"));
        } else {
            // 其余取 after结构体数据
            formatDataObj.putAll(rawDataObj.getJSONObject("after"));
        }
        return formatDataObj;
    }
}

2.3.4 数据流类

数据流类OrdersDataStream.java 内容如下:

package cn.mfox.datastream;

import cn.mfox.common.utils.TransformUtil;
import com.alibaba.fastjson.JSONObject;
import com.ververica.cdc.connectors.postgres.PostgreSQLSource;
import com.ververica.cdc.debezium.DebeziumSourceFunction;
import com.ververica.cdc.debezium.JsonDebeziumDeserializationSchema;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

import java.util.Properties;

/**
 * 抽取 订单表 数据源流
 * 
 * @author hy
 * @version 1.0
 * @date 2022/5/13 14:33
 */
public class OrdersDataStream {

    /**
     * 获取数据流
     *
     * @param env
     * @return
     */
    public static DataStreamSource<String> getJsonDebeziumDataStreamSource(StreamExecutionEnvironment env) {
        // 1.创建Flink-PostgreSQL-CDC的Source
        Properties debeziumPropert = new Properties();
        debeziumPropert.setProperty("decimal.handling.mode", "double");
        DebeziumSourceFunction<String> postgresqlSource = PostgreSQLSource.<String>builder()
                .hostname("192.168.18.102")
                .port(5432)
                .username("postgres")
                .password("123456")
                .database("flinkcdc_order_manage")
                .schemaList("public")
                .tableList("public.orders")
                .deserializer(new JsonDebeziumDeserializationSchema())
                .debeziumProperties(debeziumPropert)
                .slotName("order")
                .build();
        // 2.使用CDC Source 读取数据
        return env.addSource(postgresqlSource, "Orders Source");
    }

    /**
     * 获取 JOSONObject 格式数据
     *
     * @param env
     * @return
     */
    public static DataStream<JSONObject> getJSONObjectDebeziumDataStream(StreamExecutionEnvironment env) {
        // 1. 抽取 Debzium格式 数据流
        DataStreamSource<String> debeziumDataStream = getJsonDebeziumDataStreamSource(env);
        // 2.转换为 JSONObject 格式
        return debeziumDataStream.map(rawData -> {
            return TransformUtil.formatResult(rawData);
        });
    }
}

说明:

  • debeziumPropert.setProperty(“decimal.handling.mode”, “double”) 主要是为了解决数据类型为 numeric 的问题,如果不设置该配置,数据库中的数据为 numeric 的数据,抽取数据后,自动变为 “Irg=”
  • “未设置”“设置” 的对比图如下:
    numeric 数据类型配置ProductDataStream.java 内容如下:
package cn.mfox.datastream;

import cn.mfox.common.utils.TransformUtil;
import com.alibaba.fastjson.JSONObject;
import com.ververica.cdc.connectors.mysql.source.MySqlSource;
import com.ververica.cdc.connectors.mysql.table.StartupOptions;
import com.ververica.cdc.debezium.JsonDebeziumDeserializationSchema;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

import java.time.Duration;

/**
 * 抽取 产品表 数据源流
 * <p>
 * 数据流分为:
 * 有水位线数据流,指定ts_ms为时间戳
 * 无水位线数据流
 *
 * @author hy
 * @version 1.0
 * @date 2022/5/13 10:33
 */
public class ProductDataStream {

    /**
     * 带有水位线 数据流
     *
     * @param env
     * @return
     */
    public static DataStream<JSONObject> getDataStreamWithWatermark(StreamExecutionEnvironment env) {
        WatermarkStrategy<String> watermarkStrategy
                = WatermarkStrategy.<String>forBoundedOutOfOrderness(Duration.ofSeconds(1L))
                .withTimestampAssigner(
                        new SerializableTimestampAssigner<String>() {
                            @Override
                            public long extractTimestamp(String extractData, long l) {
                                return JSONObject.parseObject(extractData.toString()).getLong("ts_ms");
                            }
                        }
                );
        return getDataStream(env, watermarkStrategy);
    }

    /**
     * 无有水位线 数据流
     *
     * @param env
     * @return
     */
    public static DataStream<JSONObject> getDataStreamNoWatermark(StreamExecutionEnvironment env) {
        return getDataStream(env, WatermarkStrategy.noWatermarks());
    }

    /**
     * 获取数据流
     *
     * @param env
     * @return
     */
    public static DataStream<JSONObject> getDataStream(StreamExecutionEnvironment env, WatermarkStrategy watermark) {
        // 1.创建Flink-MySQL-CDC的Source
        MySqlSource<String> mySqlSource = MySqlSource.<String>builder()
                .hostname("192.168.18.101")
                .port(3306)
                .username("root")
                .password("123456")
                .databaseList("flinkcdc_product_manage")
                .tableList("flinkcdc_product_manage.products")
                .startupOptions(StartupOptions.initial())
                .deserializer(new JsonDebeziumDeserializationSchema())
                .serverTimeZone("Asia/Shanghai")
                .build();

        // 2.使用CDC Source 读取数据
        DataStreamSource<String> mysqlDataStreamSource = env.fromSource(
                mySqlSource, watermark,
                "ProductDataStream Source"
        );
        // 3.转换为指定格式
        DataStream<JSONObject> mapTransformDataStream = mysqlDataStreamSource.map(rawData -> {
            return TransformUtil.formatResult(rawData);
        });
        return mapTransformDataStream;
    }
}

3 抽取 PostgreSQL 订单表案例

3.1 抽取订单表表数据

3.1.1 JsonDebezium 序列化返回格式

创建 OrdersJsonDebeziumDataStreamTest.java 类文件,内容如下:

package cn.mfox.etl.v1;

import cn.mfox.datastream.OrdersDataStream;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

/**
 * 抽取订单数据流测试
 *
 * @author hy
 * @version 1.0
 * @date 2022/5/13 14:41
 */
public class OrdersJsonDebeziumDataStreamTest {
    public static void main(String[] args) throws Exception {
        // 1.获取流式执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        // 2.读取 Orders订单表数据流
        DataStream<String> dataStream = OrdersDataStream.getJsonDebeziumDataStreamSource(env);
        // 3.打印
        dataStream.print();
        // 4.启动环境
        env.execute();
    }
}

运行截图:
订单表首次抽取
控制台输出结果:

{
	"before": null,
	"after": {
		"order_id": 1001,
		"order_name": "篮球订单",
		"order_date": 1652461702000,
		"order_price": 88.88,
		"product_id": 101
	},
	"source": {
		"version": "1.5.4.Final",
		"connector": "postgresql",
		"name": "postgres_cdc_source",
		"ts_ms": 1652690846192,
		"snapshot": "last",
		"db": "flinkcdc_order_manage",
		"sequence": "[null,\"39098752\"]",
		"schema": "public",
		"table": "orders",
		"txId": 576,
		"lsn": 39098752,
		"xmin": null
	},
	"op": "r",
	"ts_ms": 1652690846199,
	"transaction": null
}

结论:

  • 默认的 JsonDebezium 序列化返回结果存在很多冗余数据。

3.1.2 JsonDebezium 返回格式优化

创建 OrdersJsonObjectDebeziumDataStreamTest.java 类文件,内容如下:

package cn.mfox.etl.v1;

import cn.mfox.datastream.OrdersDataStream;
import com.alibaba.fastjson.JSONObject;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

/**
 * 抽取订单数据流测试,数据返回格式为 JSONObject
 *
 * @author hy
 * @version 1.0
 * @date 2022/5/16 15:41
 */
public class OrdersJsonObjectDebeziumDataStreamTest {
    public static void main(String[] args) throws Exception {
        // 1.获取流式执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        // 2.读取 Orders订单表数据流
        DataStream<JSONObject> dataStream = OrdersDataStream.getJSONObjectDebeziumDataStream(env);
        // 3.打印
        dataStream.print();
        // 4.启动环境
        env.execute();
    }
}

运行截图:
订单表优化数据返回格式
结论:

  • 格式转换后,可以去除冗余字段,保留想要数据。

3.1.3 数据返回格式对比

两者版本数据返回格式对比:
数据返回格式对比
结论:

  • 删除冗余字段版本较为整洁。

4 产品流 JOIN 订单流

产品与订单 窗口 Join 联结工程截图


4.1 处理时间窗口内联结

创建 ProductInnerJoinOrderByProcessTimeTest.java 类,内容如下:

package cn.mfox.etl.v2.join.window;

import cn.mfox.datastream.OrdersDataStream;
import cn.mfox.datastream.ProductDataStream;
import com.alibaba.fastjson.JSONObject;
import org.apache.flink.api.common.functions.JoinFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;

/**
 * 产品与订单 的内联结
 * 基于 处理时间 的内联结,只输出关联数据
 *
 * @author hy
 * @version 1.0
 * @date 2022/5/16 17:07
 */
public class ProductInnerJoinOrderByProcessTimeTest {

    public static void main(String[] args) throws Exception {
        // 1.创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        // 2.获取两者数据流
        DataStream<JSONObject> productDataStream = ProductDataStream.getDataStreamNoWatermark(env);
        DataStream<JSONObject> orderDataStream = OrdersDataStream.getJSONObjectDebeziumDataStream(env);
        // 3.窗口内联结(产品流 和 订单表)打印输出
        windowInnerJoinAndPrint(productDataStream, orderDataStream);
        // 4.执行任务
        env.execute("ProductInnerJoinOrderTest Job");
    }

    /**
     * 窗口联结并打印输出
     * 只支持 inner join,即窗口内联关联到的才会下发,关联不到的则直接丢掉。
     * 如果想实现Window上的 outer join,需要使用coGroup算子
     *
     * @param productDataStream 产品表 数据流
     * @param orderDataStream   订单表 数据流
     */
    private static void windowInnerJoinAndPrint(DataStream<JSONObject> productDataStream,
                                                DataStream<JSONObject> orderDataStream) {
        DataStream<JSONObject> innerJoinDataStream = productDataStream
                .join(orderDataStream)
                .where(product -> product.getInteger("id"))
                .equalTo(order -> order.getInteger("product_id"))
                .window(TumblingProcessingTimeWindows.of(Time.seconds(3L)))
                .apply(
                        new JoinFunction<JSONObject, JSONObject, JSONObject>() {
                            @Override
                            public JSONObject join(JSONObject jsonObject,
                                                   JSONObject jsonObject2) {
                                // 拼接
                                jsonObject.putAll(jsonObject2);
                                return jsonObject;
                            }
                        }
                );
        innerJoinDataStream.print("Window Inner Join By Process Time");
    }
}

程序运行截图:
处理时间窗口内联结

程序运行截图:

Window Inner Join By Process Time> {"op":"r","order_date":1652461702000,"product_id":101,"name":"篮球","order_price":88.88,"id":101,"order_id":1001,"ts_ms":1652692946143,"order_name":"篮球订单"}

说明:

  • 窗口内联结:只输出关联的数据(产品id=101)。

4.2 处理时间窗口外联结

创建 ProductOuterJoinOrderByProcessTimeTest.java 类,内容如下:

package cn.mfox.etl.v2.join.window;

import cn.mfox.datastream.OrdersDataStream;
import cn.mfox.datastream.ProductDataStream;
import com.alibaba.fastjson.JSONObject;
import org.apache.flink.api.common.functions.CoGroupFunction;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.util.Collector;

/**
 * 产品与订单 的 外联结
 * 基于 处理时间 的外联结,输出关联数据和单边流非关联数据
 *
 * @author hy
 * @version 1.0
 * @date 2022/5/16 17:30
 */
public class ProductOuterJoinOrderByProcessTimeTest {

    public static void main(String[] args) throws Exception {
        // 1.创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        // 2.获取两者数据流
        DataStream<JSONObject> productDataStream = ProductDataStream.getDataStreamNoWatermark(env);
        DataStream<JSONObject> orderDataStream = OrdersDataStream.getJSONObjectDebeziumDataStream(env);
        // 3.窗口外联结(产品表流 和 订单表流)打印输出
        windowOuterJoinAndPrint(productDataStream, orderDataStream);
        // 4.执行任务
        env.execute("WindowOuterJoinByProcessTimeTest Job");
    }

    /**
     * 窗口外联并打印输出
     * Window上的 outer join,使用coGroup算子,关联不到的数据也会下发
     *
     * @param productDataStream 产品表流
     * @param orderDataStream   订单表流
     */
    private static void windowOuterJoinAndPrint(DataStream<JSONObject> productDataStream,
                                                DataStream<JSONObject> orderDataStream) {
        DataStream<JSONObject> outerJoinDataStream = productDataStream
                .coGroup(orderDataStream)
                .where(product -> product.getInteger("id"))
                .equalTo(order -> order.getInteger("product_id"))
                .window(TumblingProcessingTimeWindows.of(Time.seconds(3L)))
                .apply(
                        new CoGroupFunction<JSONObject, JSONObject, JSONObject>() {
                            @Override
                            public void coGroup(Iterable<JSONObject> iterable,
                                                Iterable<JSONObject> iterable1,
                                                Collector<JSONObject> collector) {
                                JSONObject result = new JSONObject();
                                for (JSONObject jsonObject : iterable) {
                                    result.putAll(jsonObject);
                                }
                                for (JSONObject jsonObject : iterable1) {
                                    result.putAll(jsonObject);
                                }
                                collector.collect(result);
                            }
                        }
                );
        outerJoinDataStream.print("Window Outer Join By Process Time");
    }
}

程序运行截图:

Window Outer Join By Process Time> {"op":"r","order_date":1652461702000,"product_id":101,"name":"篮球","order_price":88.88,"id":101,"order_id":1001,"ts_ms":1652693277047,"order_name":"篮球订单"}
Window Outer Join By Process Time> {"op":"r","name":"乒乒球","id":102,"ts_ms":1652693276870}

说明:

  • 窗口外联结:输出关联的数据(产品id=101)和未关联的数据(产品id=102)。

4.3 处理时间窗口内外联结对比

处理时间窗口内外联结对比

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值