使用 Flink CDC 搭建跨库 Streaming ETLMySQL + Postgres → Elasticsearch 实战

一、整体架构:一条典型的 Streaming ETL Pipeline

本教程的数据流非常清晰:

  1. MySQL

    • mydb.products:商品维表
    • mydb.orders:订单事实表
  2. Postgres

    • public.shipments:发货/物流表,记录订单发货、到达情况
  3. Flink 集群

    • 通过 mysql-cdc 捕获 MySQL 变更;
    • 通过 postgres-cdc 捕获 Postgres 变更;
    • 在 Flink SQL 中对三张表进行实时 Left Join;
    • 将结果写入 Elasticsearch。
  4. Elasticsearch + Kibana

    • 存储富化后的订单数据;
    • 在 Kibana 里通过 Index Pattern 展示实时变化。

用一句话概括就是:

多源 CDC → Flink SQL 实时 Join → 下游 ES 实时同步

二、环境准备

1. 前提条件

  • 一台 Linux 或 MacOS 机器;

  • 已安装 Docker & Docker Compose

  • 端口没有被占用:

    • 3306(MySQL)、5432(Postgres)、
    • 9200/9300(ES)、5601(Kibana)、
    • 8081(Flink Web UI)。

2. 使用 docker-compose 启动核心组件

在任意目录创建 docker-compose.yml

yaml 复制代码
version: '2.1'
services:
  postgres:
    image: debezium/example-postgres:1.1
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres

  mysql:
    image: debezium/example-mysql:1.1
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=123456
      - MYSQL_USER=mysqluser
      - MYSQL_PASSWORD=mysqlpw

  elasticsearch:
    image: elastic/elasticsearch:7.6.0
    environment:
      - cluster.name=docker-cluster
      - bootstrap.memory_lock=true
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - discovery.type=single-node
    ports:
      - "9200:9200"
      - "9300:9300"
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536

  kibana:
    image: elastic/kibana:7.6.0
    ports:
      - "5601:5601"

说明一下:

  • MySQLPostgres 都用的是 Debezium 提供的 Example 镜像,已经开好了 binlog / wal,适合做 CDC。
  • Elasticsearch + Kibana 用于结果展示。

在该目录执行:

bash 复制代码
docker-compose up -d

启动完成后用:

bash 复制代码
docker ps

确认容器都在运行。

浏览器打开 http://localhost:5601,能看到 Kibana 登陆页就 OK。

下载 Flink 1.18.0 ,解压到 flink-1.18.0 目录:

bash 复制代码
tar -xzf flink-1.18.0-bin-scala_2.12.tgz
cd flink-1.18.0

将以下 JAR 放到 flink-1.18.0/lib/ 下:

  • flink-sql-connector-elasticsearch7-3.0.1-1.17.jar
  • flink-sql-connector-mysql-cdc-3.0-SNAPSHOT.jar
  • flink-sql-connector-postgres-cdc-3.0-SNAPSHOT.jar

⚠️ SNAPSHOT 版本往往需要从对应项目(比如 Flink CDC 仓库)源码自行打包。

四、构造业务数据:MySQL + Postgres

1. 在 MySQL 中创建产品 & 订单表

进入 MySQL 容器:

bash 复制代码
docker-compose exec mysql mysql -uroot -p123456

创建数据库和表:

sql 复制代码
-- MySQL
CREATE DATABASE mydb;
USE mydb;

CREATE TABLE products (
  id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  description VARCHAR(512)
);
ALTER TABLE products AUTO_INCREMENT = 101;

INSERT INTO products
VALUES (default,"scooter","Small 2-wheel scooter"),
       (default,"car battery","12V car battery"),
       (default,"12-pack drill bits","12-pack of drill bits with sizes ranging from #40 to #3"),
       (default,"hammer","12oz carpenter's hammer"),
       (default,"hammer","14oz carpenter's hammer"),
       (default,"hammer","16oz carpenter's hammer"),
       (default,"rocks","box of assorted rocks"),
       (default,"jacket","water resistent black wind breaker"),
       (default,"spare tire","24 inch spare tire");

CREATE TABLE orders (
  order_id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
  order_date DATETIME NOT NULL,
  customer_name VARCHAR(255) NOT NULL,
  price DECIMAL(10, 5) NOT NULL,
  product_id INTEGER NOT NULL,
  order_status BOOLEAN NOT NULL -- Whether order has been placed
) AUTO_INCREMENT = 10001;

INSERT INTO orders
VALUES (default, '2020-07-30 10:08:22', 'Jark',   50.50, 102, false),
       (default, '2020-07-30 10:11:09', 'Sally',  15.00, 105, false),
       (default, '2020-07-30 12:00:30', 'Edward', 25.25, 106, false);

MySQL 里现在有:

  • 商品表 products
  • 订单表 orders

2. 在 Postgres 中创建 shipments 物流表

进入 Postgres 容器:

bash 复制代码
docker-compose exec postgres psql -h localhost -U postgres

执行 SQL:

sql 复制代码
-- PG
CREATE TABLE shipments (
  shipment_id SERIAL NOT NULL PRIMARY KEY,
  order_id    SERIAL NOT NULL,
  origin      VARCHAR(255) NOT NULL,
  destination VARCHAR(255) NOT NULL,
  is_arrived  BOOLEAN NOT NULL
);

ALTER SEQUENCE public.shipments_shipment_id_seq RESTART WITH 1001;

-- 为了 CDC 能拿到完整变更,打开 REPLICA IDENTITY FULL
ALTER TABLE public.shipments REPLICA IDENTITY FULL;

INSERT INTO shipments
VALUES (default,10001,'Beijing',  'Shanghai', false),
       (default,10002,'Hangzhou', 'Shanghai', false),
       (default,10003,'Shanghai', 'Hangzhou', false);

这里的设计逻辑:

  • shipments.order_id 对应 MySQL 的 orders.order_id
  • origin/destination/is_arrived 提供物流维度信息;
  • 打开 REPLICA IDENTITY FULL 是为了保证更新/删除时 CDC 能获取旧值。

进入 flink-1.18.0 目录:

bash 复制代码
cd flink-1.18.0

启动 Flink 集群:

bash 复制代码
./bin/start-cluster.sh

打开浏览器访问:http://localhost:8081,确认 Flink 正常运行。

启动 Flink SQL CLI:

bash 复制代码
./bin/sql-client.sh

如果进入了交互式 SQL 客户端,说明一切就绪。

1. 开启 checkpoint(Streaming ETL 的"心跳")

sql 复制代码
-- Flink SQL
SET execution.checkpointing.interval = 3s;

checkpoint 用来:

  • 保证 Flink 任务的容错;
  • 对 sink(比如 Elasticsearch)起到"提交点"的作用;
  • 对 CDC 源,在全量 → 增量阶段的切换上,保证顺序性。

2. MySQL CDC - products 源表

sql 复制代码
CREATE TABLE products (
    id INT,
    name STRING,
    description STRING,
    PRIMARY KEY (id) NOT ENFORCED
) WITH (
    'connector' = 'mysql-cdc',
    'hostname' = 'localhost',
    'port' = '3306',
    'username' = 'root',
    'password' = '123456',
    'database-name' = 'mydb',
    'table-name' = 'products'
);

3. MySQL CDC - orders 源表

sql 复制代码
CREATE TABLE orders (
   order_id INT,
   order_date TIMESTAMP(0),
   customer_name STRING,
   price DECIMAL(10, 5),
   product_id INT,
   order_status BOOLEAN,
   PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
   'connector' = 'mysql-cdc',
   'hostname' = 'localhost',
   'port' = '3306',
   'username' = 'root',
   'password' = '123456',
   'database-name' = 'mydb',
   'table-name' = 'orders'
);

两张 MySQL 源表都是通过 mysql-cdc connector 捕获 binlog,实现快照+实时变更同步。

4. Postgres CDC - shipments 源表

sql 复制代码
CREATE TABLE shipments (
   shipment_id INT,
   order_id INT,
   origin STRING,
   destination STRING,
   is_arrived BOOLEAN,
   PRIMARY KEY (shipment_id) NOT ENFORCED
) WITH (
   'connector' = 'postgres-cdc',
   'hostname' = 'localhost',
   'port' = '5432',
   'username' = 'postgres',
   'password' = 'postgres',
   'database-name' = 'postgres',
   'schema-name' = 'public',
   'table-name' = 'shipments',
   'slot.name' = 'flink'
);

这里有几个点值得注意:

  • postgres-cdc 会使用逻辑复制槽(logical replication slot)读取 WAL;
  • slot.name = 'flink' 指定复制槽名,方便管理;
  • 前面我们在 PG 里已经设置了 REPLICA IDENTITY FULL,保证更新/删除事件内容完整。

5. Elasticsearch Sink - enriched_orders 目标表

这张表用于存储富化后的订单数据。

sql 复制代码
CREATE TABLE enriched_orders (
   order_id INT,
   order_date TIMESTAMP(0),
   customer_name STRING,
   price DECIMAL(10, 5),
   product_id INT,
   order_status BOOLEAN,
   product_name STRING,
   product_description STRING,
   shipment_id INT,
   origin STRING,
   destination STRING,
   is_arrived BOOLEAN,
   PRIMARY KEY (order_id) NOT ENFORCED
) WITH (
     'connector' = 'elasticsearch-7',
     'hosts' = 'http://localhost:9200',
     'index' = 'enriched_orders'
);
  • connector = 'elasticsearch-7':对应 ES 7.x;
  • index = 'enriched_orders':ES 中的索引名;
  • order_id 作为主键,是因为我们希望"每个订单一行",更新时覆盖原数据。

七、用一条 SQL 完成 Streaming ETL:多源 Join → ES

真正酷的地方来了:

整条 Streaming ETL pipeline,实际上就一条 INSERT INTO ... SELECT ...

sql 复制代码
INSERT INTO enriched_orders
SELECT
  o.*,
  p.name,
  p.description,
  s.shipment_id,
  s.origin,
  s.destination,
  s.is_arrived
FROM orders AS o
LEFT JOIN products  AS p ON o.product_id = p.id
LEFT JOIN shipments AS s ON o.order_id   = s.order_id;

这条 SQL 做了几件事:

  1. orders 流中读取订单变更;
  2. 左连接 products,拿到商品名和描述;
  3. 再左连接 shipments,拿到物流信息;
  4. 把所有字段合在一起写入 Elasticsearch 的 enriched_orders 索引。

此时:

  • Flink 会启动一个持续运行的流任务;
  • 任务会自动拉取 MySQL + Postgres 的数据变更并实时 Join;
  • 结果持续推送到 ES。

八、在 Kibana 里查看富化后的订单

  1. 打开 Kibana:http://localhost:5601

  2. 进入 Index Pattern 创建页面:

    • 地址类似:
      http://localhost:5601/app/kibana#/management/kibana/index_pattern
    • 创建一个 index pattern:enriched_orders*
  3. 进入 Discover 页面:

    • 地址类似:
      http://localhost:5601/app/kibana#/discover
    • 选择刚才创建的 index pattern,就能看到富化后的订单记录了:

    每条记录会包含:

    • 订单基础信息:order_id, order_date, customer_name, price, order_status ...
    • 商品信息:product_name, product_description
    • 物流信息:shipment_id, origin, destination, is_arrived

这就是你要的实时富化订单视图。

九、验证"实时性":动一动上游,看 ES 怎么跟着跑

下面我们通过一组操作,来观察 Streaming ETL 链路的实时效果。

1. 在 MySQL 中插入新订单

sql 复制代码
-- MySQL
INSERT INTO orders
VALUES (default, '2020-07-30 15:22:00', 'Jark', 29.71, 104, false);

这会生成一个新订单(假设自动生成的 order_id = 10004)。

2. 在 Postgres 中为新订单插入一条 shipment

sql 复制代码
-- Postgres
INSERT INTO shipments
VALUES (default,10004,'Shanghai','Beijing',false);

此时你在 Kibana 的 enriched_orders 里应该能看到一条新记录:

  • order_id = 10004
  • is_arrived = false
  • 起始地/目的地为 ShanghaiBeijing

3. 更新订单状态(已下单 → 已完成)

sql 复制代码
-- MySQL
UPDATE orders
SET order_status = true
WHERE order_id = 10004;

4. 更新物流状态(未到达 → 已到达)

sql 复制代码
-- Postgres
UPDATE shipments
SET is_arrived = true
WHERE shipment_id = 1004;

回到 Kibana,你会看到同一条 order_id = 10004 的记录:

  • order_status 变为 true
  • is_arrived 也变为 true

这其实就是:

跨库更新 在 Flink CDC + Flink SQL Streaming ETL 下被正确处理、合流、并实时写入 ES。

5. 删除订单,看下游如何同步删除

sql 复制代码
-- MySQL
DELETE FROM orders
WHERE order_id = 10004;

稍后在 Kibana 中刷新,你会发现:

  • order_id = 10004 的记录从 enriched_orders 中消失。

说明 Flink SQL + ES sink 正确处理了删除事件(通常会变成一个 delete-by-id 请求)。

十、清理环境

实验结束后,可以用以下命令关闭环境,避免资源占用:

bash 复制代码
# 在 docker-compose.yml 所在目录
docker-compose down

停止 Flink 集群,在 flink-1.18.0 目录下执行:

bash 复制代码
./bin/stop-cluster.sh

这个 Demo 其实展示的是一类 非常有代表性 的场景:

上游多库多源(MySQL + Postgres)

通过 Flink CDC 统一变更流

在 Flink SQL 中做实时 Join / 富化

最终写入 ES 做搜索 / 看板。

相比传统"导数 + 定时任务 + 批处理"的模式,它有几个明显优势:

  1. 真正的 Streaming ETL

    • 数据一写入 MySQL/Postgres,
      变更几乎就会在秒级到达 Elasticsearch。
  2. 多源数据统一建模

    • 在 Flink SQL 里用标准 SQL 语法做 Join/清洗/投影;
    • 逻辑清晰、上线快、不依赖 Java/Scala 开发。
  3. 对上游入侵极小

    • MySQL / Postgres 只需开启 binlog / wal;
    • 不需要改业务代码、也不需要额外同步程序。
  4. 下游灵活可拓展

    • 本文示例是 Elasticsearch;
    • 实际中可以拓展为 Kafka、Iceberg/Hudi/Paimon、Doris/StarRocks 等。
相关推荐
用户1997010801842 分钟前
1688图片搜索API | 上传图片秒找同款 | 相似商品精准推荐
大数据·数据挖掘·图片资源
Elasticsearch1 小时前
Elastic AI agent builder 介绍(五)- 备份 tools 及 agents
elasticsearch
武子康2 小时前
大数据-164 Apache Kylin Cuboid 剪枝实战:Derived 维度与膨胀率控制
大数据·后端·apache kylin
梦里不知身是客112 小时前
shuffle过程
大数据
星释3 小时前
Rust 练习册 80:Grains与位运算
大数据·算法·rust
练习时长一年3 小时前
git常用命令总结
大数据·git·elasticsearch
TDengine (老段)4 小时前
TDengine IDMP 重塑智慧水务运营(内附 Step by Step 步骤)
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
萤丰信息8 小时前
智慧园区能源革命:从“耗电黑洞”到零碳样本的蜕变
java·大数据·人工智能·科技·安全·能源·智慧园区
中科岩创14 小时前
河北某铁矿绿色矿山建设二期自动化监测项目
大数据