You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
flink-cdc-connectors/docs/content/快速上手/build-real-time-data-lake-t...

284 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 基于 Flink CDC 同步 MySQL 分库分表构建实时数据湖
在 OLTP 系统中,为了解决单表数据量大的问题,通常采用分库分表的方式将单个大表进行拆分以提高系统的吞吐量。
但是为了方便数据分析,通常需要将分库分表拆分出的表在同步到数据仓库、数据湖时,再合并成一个大表。
这篇教程将展示如何使用 Flink CDC 构建实时数据湖来应对这种场景,本教程的演示基于 Docker只涉及 SQL无需一行 Java/Scala 代码,也无需安装 IDE你可以很方便地在自己的电脑上完成本教程的全部内容。
接下来将以数据从 MySQL 同步到 [Iceberg](https://iceberg.apache.org/) 为例展示整个流程,架构图如下所示:
![Architecture of Real-Time Data Lake](/_static/fig/real-time-data-lake-tutorial/real-time-data-lake-tutorial.png "architecture of real-time data lake")
你也可以使用不同的 source 比如 Oracle/Postgres 和 sink 比如 Hudi 来构建自己的 ETL 流程。
## 准备阶段
准备一台已经安装了 Docker 的 Linux 或者 MacOS 电脑。
### 准备教程所需要的组件
接下来的教程将以 `docker-compose` 的方式准备所需要的组件。
使用下面的内容创建一个 `docker-compose.yml` 文件:
```
version: '2.1'
services:
sql-client:
user: flink:flink
image: yuxialuo/flink-sql-client:1.13.2.v1
depends_on:
- jobmanager
- mysql
environment:
FLINK_JOBMANAGER_HOST: jobmanager
MYSQL_HOST: mysql
volumes:
- shared-tmpfs:/tmp/iceberg
jobmanager:
user: flink:flink
image: flink:1.13.2-scala_2.11
ports:
- "8081:8081"
command: jobmanager
environment:
- |
FLINK_PROPERTIES=
jobmanager.rpc.address: jobmanager
volumes:
- shared-tmpfs:/tmp/iceberg
taskmanager:
user: flink:flink
image: flink:1.13.2-scala_2.11
depends_on:
- jobmanager
command: taskmanager
environment:
- |
FLINK_PROPERTIES=
jobmanager.rpc.address: jobmanager
taskmanager.numberOfTaskSlots: 2
volumes:
- shared-tmpfs:/tmp/iceberg
mysql:
image: debezium/example-mysql:1.1
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_USER=mysqluser
- MYSQL_PASSWORD=mysqlpw
volumes:
shared-tmpfs:
driver: local
driver_opts:
type: "tmpfs"
device: "tmpfs"
```
该 Docker Compose 中包含的容器有:
- SQL-Client: Flink SQL Client, 用来提交 SQL 查询和查看 SQL 的执行结果
- Flink Cluster包含 Flink JobManager 和 Flink TaskManager用来执行 Flink SQL
- MySQL作为分库分表的数据源存储本教程的 `user`
***注意:***
1. 为了简化整个教程,本教程需要的 jar 包都已经被打包进 SQL-Client 容器中了,镜像的构建脚本可以在 [GitHub](https://github.com/luoyuxia/flink-cdc-tutorial/tree/main/flink-cdc-iceberg-demo/sql-client) 上找到。
如果你想要在自己的 Flink 环境运行本教程,需要下载下面列出的包并且把它们放在 Flink 所在目录的 lib 目录下,即 `FLINK_HOME/lib/`
**下载链接只对已发布的版本有效, SNAPSHOT 版本需要本地编译**
- [flink-sql-connector-mysql-cdc-2.3.0.jar](https://repo1.maven.org/maven2/com/ververica/flink-sql-connector-mysql-cdc/2.3.0/flink-sql-connector-mysql-cdc-2.3.0.jar)
- [flink-shaded-hadoop-2-uber-2.7.5-10.0.jar](https://repo.maven.apache.org/maven2/org/apache/flink/flink-shaded-hadoop-2-uber/2.7.5-10.0/flink-shaded-hadoop-2-uber-2.7.5-10.0.jar)
- [iceberg-flink-1.13-runtime-0.13.0-SNAPSHOT.jar](https://raw.githubusercontent.com/luoyuxia/flink-cdc-tutorial/main/flink-cdc-iceberg-demo/sql-client/lib/iceberg-flink-1.13-runtime-0.13.0-SNAPSHOT.jar)
目前支持 Flink 1.13 的 `iceberg-flink-runtime` jar 包还没有发布,所以我们在这里提供了一个支持 Flink 1.13 的 `iceberg-flink-runtime` jar 包,这个 jar 包是基于 Iceberg 的 master 分支打包的。
当 Iceberg 0.13.0 版本发布后,你也可以在 [apache official repository](https://repo.maven.apache.org/maven2/org/apache/iceberg/iceberg-flink-runtime/) 下载到支持 Flink 1.13 的 `iceberg-flink-runtime` jar 包。
2. 本教程接下来用到的容器相关的命令都需要在 `docker-compose.yml` 所在目录下执行
`docker-compose.yml` 所在目录下执行下面的命令来启动本教程需要的组件:
```shell
docker-compose up -d
```
该命令将以 detached 模式自动启动 Docker Compose 配置中定义的所有容器。你可以通过 `docker ps` 来观察上述的容器是否正常启动了,也可以通过访问 [http://localhost:8081/](http://localhost:8081//) 来查看 Flink 是否运行正常。
![Flink UI](/_static/fig/real-time-data-lake-tutorial/flink-ui.png "Flink UI")
### 准备数据
1. 进入 MySQL 容器中
```shell
docker-compose exec mysql mysql -uroot -p123456
```
2. 创建数据和表,并填充数据
创建两个不同的数据库,并在每个数据库中创建两个表,作为 `user` 表分库分表下拆分出的表。
```sql
CREATE DATABASE db_1;
USE db_1;
CREATE TABLE user_1 (
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL DEFAULT 'flink',
address VARCHAR(1024),
phone_number VARCHAR(512),
email VARCHAR(255)
);
INSERT INTO user_1 VALUES (110,"user_110","Shanghai","123567891234","user_110@foo.com");
CREATE TABLE user_2 (
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL DEFAULT 'flink',
address VARCHAR(1024),
phone_number VARCHAR(512),
email VARCHAR(255)
);
INSERT INTO user_2 VALUES (120,"user_120","Shanghai","123567891234","user_120@foo.com");
```
```sql
CREATE DATABASE db_2;
USE db_2;
CREATE TABLE user_1 (
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL DEFAULT 'flink',
address VARCHAR(1024),
phone_number VARCHAR(512),
email VARCHAR(255)
);
INSERT INTO user_1 VALUES (110,"user_110","Shanghai","123567891234", NULL);
CREATE TABLE user_2 (
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL DEFAULT 'flink',
address VARCHAR(1024),
phone_number VARCHAR(512),
email VARCHAR(255)
);
INSERT INTO user_2 VALUES (220,"user_220","Shanghai","123567891234","user_220@foo.com");
```
## 在 Flink SQL CLI 中使用 Flink DDL 创建表
首先,使用如下的命令进入 Flink SQL CLI 容器中:
```shell
docker-compose exec sql-client ./sql-client
```
我们可以看到如下界面:
![Flink SQL Client](/_static/fig/real-time-data-lake-tutorial/flink-sql-client.png "Flink SQL Client" )
然后,进行如下步骤:
1. 开启 checkpoint每隔3秒做一次 checkpoint
Checkpoint 默认是不开启的,我们需要开启 Checkpoint 来让 Iceberg 可以提交事务。
并且mysql-cdc 在 binlog 读取阶段开始前,需要等待一个完整的 checkpoint 来避免 binlog 记录乱序的情况。
```sql
-- Flink SQL
Flink SQL> SET execution.checkpointing.interval = 3s;
```
2. 创建 MySQL 分库分表 source 表
创建 source 表 `user_source` 来捕获MySQL中所有 `user` 表的数据,在表的配置项 `database-name` , `table-name` 使用正则表达式来匹配这些表。
并且,`user_source` 表也定义了 metadata 列来区分数据是来自哪个数据库和表。
```sql
-- Flink SQL
Flink SQL> CREATE TABLE user_source (
database_name STRING METADATA VIRTUAL,
table_name STRING METADATA VIRTUAL,
`id` DECIMAL(20, 0) NOT NULL,
name STRING,
address STRING,
phone_number STRING,
email STRING,
PRIMARY KEY (`id`) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
'hostname' = 'mysql',
'port' = '3306',
'username' = 'root',
'password' = '123456',
'database-name' = 'db_[0-9]+',
'table-name' = 'user_[0-9]+'
);
```
3. 创建 Iceberg sink 表
创建 sink 表 `all_users_sink`,用来将数据加载至 Iceberg 中。
在这个 sink 表,考虑到不同的 MySQL 数据库表的 `id` 字段的值可能相同,我们定义了复合主键 (`database_name`, `table_name`, `id`)。
```sql
-- Flink SQL
Flink SQL> CREATE TABLE all_users_sink (
database_name STRING,
table_name STRING,
`id` DECIMAL(20, 0) NOT NULL,
name STRING,
address STRING,
phone_number STRING,
email STRING,
PRIMARY KEY (database_name, table_name, `id`) NOT ENFORCED
) WITH (
'connector'='iceberg',
'catalog-name'='iceberg_catalog',
'catalog-type'='hadoop',
'warehouse'='file:///tmp/iceberg/warehouse',
'format-version'='2'
);
```
## 流式写入 Iceberg
1. 使用下面的 Flink SQL 语句将数据从 MySQL 写入 Iceberg 中
```sql
-- Flink SQL
Flink SQL> INSERT INTO all_users_sink select * from user_source;
```
上述命令将会启动一个流式作业,源源不断将 MySQL 数据库中的全量和增量数据同步到 Iceberg 中。
在 [Flink UI](http://localhost:8081/#/job/running) 上可以看到这个运行的作业:
![CDC to Iceberg Running Job](/_static/fig/real-time-data-lake-tutorial/flink-cdc-iceberg-running-job.png "CDC to Iceberg Running Job")
然后我们就可以使用如下的命令看到 Iceberg 中的写入的文件:
```shell
docker-compose exec sql-client tree /tmp/iceberg/warehouse/default_database/
```
如下所示:
![Files in Iceberg](/_static/fig/real-time-data-lake-tutorial/files-in-iceberg.png "Files in Iceberg")
在你的运行环境中,实际的文件可能与上面的截图不相同,但是整体的目录结构应该相似。
2. 使用下面的 Flink SQL 语句查询表 `all_users_sink` 中的数据
```sql
-- Flink SQL
Flink SQL> SELECT * FROM all_users_sink;
```
在 Flink SQL CLI 中我们可以看到如下查询结果:
![Data in Iceberg](/_static/fig/real-time-data-lake-tutorial/data_in_iceberg.png "Data in Iceberg")
3. 修改 MySQL 中表的数据Iceberg 中的表 `all_users_sink` 中的数据也将实时更新:
(3.1) 在 `db_1.user_1` 表中插入新的一行
```sql
--- db_1
INSERT INTO db_1.user_1 VALUES (111,"user_111","Shanghai","123567891234","user_111@foo.com");
```
(3.2) 更新 `db_1.user_2` 表的数据
```sql
--- db_1
UPDATE db_1.user_2 SET address='Beijing' WHERE id=120;
```
(3.3) 在 `db_2.user_2` 表中删除一行
```sql
--- db_2
DELETE FROM db_2.user_2 WHERE id=220;
```
每执行一步,我们就可以在 Flink Client CLI 中使用 `SELECT * FROM all_users_sink` 查询表 `all_users_sink` 来看到数据的变化。
最后的查询结果如下所示:
![Final Data in Iceberg](/_static/fig/real-time-data-lake-tutorial/final-data-in-iceberg.png "Final Data in Iceberg")
从 Iceberg 的最新结果中可以看到新增了`(db_1, user_1, 111)`的记录,`(db_1, user_2, 120)`的地址更新成了 `Beijing`,且`(db_2, user_2, 220)`的记录被删除了,与我们在 MySQL 做的数据更新完全一致。
## 环境清理
本教程结束后,在 `docker-compose.yml` 文件所在的目录下执行如下命令停止所有容器:
```shell
docker-compose down
```