Flink mysql-cdc同步主键分布不均匀的mysql表
一、背景
1、遇到问题描述
通过Flink同步mysql到iceberg中,任务一直在运行中,但是在目标表看不到数据。经排查发现job manager一直在做切片工作,切了一小时还没开始同步数据,日志如下:
2023-12-28 16:58:36.251 [snapshot-splitting] INFO com.ververica.cdc.connectors.mysql.source.assigners.ChunkSplitter [] - ChunkSplitter has split 600 chunks for table
![](https://img2023.cnblogs.com/blog/2350829/202312/2350829-20231228170820835-127448278.png)
2023-12-29 14:06:08.754 [snapshot-splitting] INFO com.ververica.cdc.connectors.mysql.source.assigners.ChunkSplitter [] -
The distribution factor of table sn_source.leads_data is 3.894905163297E8 according to the min split key 14415117481467904, max split key 200015469389810688 and approximate row count 476520850
二、Flink全量阶段核心设计
1、数据切片划分
全量阶段数据读取方式为分布式读取,会先对当前表数据按主键划分成多个Chunk,后续子任务读取Chunk 区间内的数据。根据主键列是否为自增整数类型,对表数据划分为均匀分布的Chunk及非均匀分布的Chunk。
(1)均匀分布方式(数值类型)
满足主键列自增且类型为整数类型(int,bigint,decimal)。查询出主键列的最小值,最大值,按 chunkSize 大小将数据均匀划分,因为主键为整数类型,根据当前chunk 起始位置、chunkSize 大小,直接计算 chunk 的结束位置。
注意:最新版本均匀分布的触发条件不再依赖主键列是否自增,要求主键列卫整数类型且根据 max(id) - min(id)/rowcount 计算出数据分布系数,只有分布系数 <= 配置的分布系数 (evenly-distribution.factor 默认为 1000.0d) 才会进行数据均匀划分。
// 计算主键列数据区间
select min(`order_id`), max(`order_id`) from demo_orders;
// 将数据划分为 chunkSize 大小的切片
chunk-0: [min,start + chunkSize)
chunk-1: [start + chunkSize, start + 2chunkSize)
.......
chunk-last: [max,null)
(2)非均匀分布方式(字符串或时间戳等其它类型)
主键列非自增或者类型为非整数类型。主键为非数值类型,每次划分需要对未划分的数据按主键进行升序排列,取出前 chunkSize 的最大值为当前 chunk 的结束位置。
注意:最新版本非均匀分布触发条件为主键列为非整数类型,或者计算出的分布系数 (distributionFactor) > 配置的分布系数 (evenly-distribution.factor)。
// 未拆分的数据排序后,取 chunkSize 条数据取最大值,作为切片的终止位置。
chunkend = SELECT MAX(`order_id`) FROM (
SELECT `order_id` FROM `demo_orders`
WHERE `order_id` >= [前一个切片的起始位置]
ORDER BY `order_id` ASC
LIMIT [chunkSize]
) AS T
真实执行sql
SELECT MAX(`leads_id`) FROM (SELECT `leads_id` FROM `sn_source`.`leads_data`
WHERE `leads_id` >= 152117209080009728 ORDER BY `leads_id` ASC LIMIT 50000) AS T
2、全量切片数据读取
Flink 将表数据划分为多个 Chunk,子任务在不加锁的情况下,并行读取 Chunk 数据。因为全程无锁在数据分片读取过程中,可能有其他事务对切片范围内的数据进行修改,此时无法保证数据一致性。因此,在全量阶段 Flink 使用快照记录读取 + Binlog 数据修正的方式来保证数据的一致性。
三、mysql-cdc连接器参数
参数
|
说明
|
是否必填
|
备注
|
connector
|
源表类型
|
是
|
固定值为mysql-cdc
|
hostname
|
MySQL 数据库的 IP 地址或者 Hostname
|
是
|
-
|
port
|
MySQL 数据库服务的端口号
|
否
|
默认值为3306
|
username
|
MySQL 数据库服务的用户名
|
是
|
有特定权限(包括 SELECT、RELOAD、SHOW DATABASES、REPLICATION SLAVE 和 REPLICATION CLIENT)的 MySQL 用户
|
password
|
MySQL 数据库服务的密码
|
是
|
-
|
database-name
|
MySQL 数据库名称
|
是
|
数据库名称支持正则表达式以读取多个数据库的数据
|
table-name
|
MySQL 表名
|
是
|
表名支持正则表达式以读取多个表的数据
|
server-id
|
数据库客户端的一个 ID
|
否
|
该 ID 必须是 MySQL 集群中全局唯一的。建议针对同一个数据库的每个作业都设置不同的 ID 范围值,例如
5400-5405 。默认会随机生成一个6400 - Integer.MAX_VALUE 的值 |
server-time-zone
|
数据库在使用的会话时区
|
否
|
例如 Asia/Shanghai,该参数控制了 MySQL 中的 TIMESTAMP 类型如何转成 STRING 类型
|
append-mode
|
开启 append 流模式
|
否
|
Flink1.13及以上版本支持, 例如:将 mysql-cdc 数据以 append 的方式同步到 hive
|
filter-duplicate-pair-records
|
过滤未在 Flink DDL 语句中定义的源表字段变更记录
|
否
|
例如 MySQL 源表有 a, b, c, d 四个字段,而用户在 Flink SQL 建表时只定义了 a, b 两个字段;开启该参数后,仅涉及 c 或 d 字段的变更记录会被忽略,不会输出到下游,可减少计算量和处理压力
|
scan.lastchunk.optimize.enable
|
对全量阶段的最后一个分片做重划分
|
否
|
如果全量同步期间,源表持续有大量写入和变更,则可能导致最后一个分片过大,引起 TaskManager OOM 崩溃重启。 开启本功能后(值设置为 true),Flink 会自动将过大的最后一个分片分成若干的小分片,提升作业的稳定性
|
debezium.min.row.count.to.stream.results
|
当表的条数大于该值时,会使用分批读取模式
|
否
|
默认值为1000。Flink 采用以下方式读取 MySQL 源表数据:
全量读取:直接将整个表的数据读取到内存里。优点是速度快,缺点是会消耗对应大小的内存,如果源表数据量非常大,可能会有 OOM 风险
分批读取:分多次读取,每次读取一定数量的行数,直到读取完所有数据。优点是读取数据量比较大的表没有 OOM 风险,缺点是读取速度相对较慢
|
debezium.snapshot.fetch.size
|
在 Snapshot 阶段,每次读取 MySQL 源表数据行数的最大值
|
否
|
仅当分批读取模式时,该参数生效
|
debezium.skipped.operations
|
需要过滤的 oplog 操作。操作包括 c 表示插入,u 表示更新,d 表示删除。默认情况下,不跳过任何操作,以逗号分隔
|
否
|
-
|
scan.incremental.snapshot.enabled
|
增量快照
|
否
|
默认为 true
增量快照是一种读取表快照的新机制,与旧的快照机制相比, 增量快照有许多优点,包括: (1)在快照读取期间,Source 支持并发读取, (2)在快照读取期间,Source 支持进行 chunk 粒度的 checkpoint, (3)在快照读取之前,Source 不需要数据库锁权限。 如果希望 Source 并行运行,则每个并行 Readers 都应该具有唯一的 Server id,所以 Server id 必须是类似 `5400-6400` 的范围,并且该范围必须大于并行度。 请查阅 增量快照读取 章节了解更多详细信息。
|
scan.incremental.snapshot.chunk.size
|
当读取表的快照时,表快照捕获的表的块大小(行数)
|
否
|
默认为 8096
|
scan.lazy-calculate-splits.enabled
|
全量阶段JM中数据分片懒加载避免数据量太大,分片数据太多导致JM OOM
|
否
|
默认为 true
|
scan.snapshot.fetch.size
|
读取表快照时每次读取数据的最大条数。 |
否
|
默认1024
|
scan.newly-added-table.enabled
|
否
|
默认为 false
|
|
scan.split-key.mode
|
联合主键作为 splitkey 的模式
|
否
|
取值为 default / specific;其中 default 为默认逻辑,采用联合主键的第一个字段作为 split key;设置为 specific 需要设置 scan.split-key.specific-column 指定联合主键中的某个字段
|
scan.split-key.specific-column
|
指定联合主键中某个字段作为 splitkey
|
否
|
当 scan.split-key.mode 为 specific 时必填。取值为联合主键中某个字段名
|
scan.startup.mode
|
MySQL CDC 消费者可选的启动模式
|
否
|
合法的模式为 "initial"(默认),"earliest-offset","latest-offset","specific-offset" 和 "timestamp"
|
scan.startup.specific-offset.file
|
在 "specific-offset" 启动模式下,启动位点的 binlog 文件位置
|
否
|
-
|
scan.startup.specific-offset.pos
|
在 "specific-offset" 启动模式下,启动位点的 binlog 文件位置
|
否
|
-
|
scan.startup.specific-offset.gtid-set
|
在 "specific-offset" 启动模式下,启动位点的 GTID 集合
|
否
|
-
|
scan.startup.timestamp-millis
|
在 "timestamp" 启动模式下,启动时间的毫秒时间戳
|
否
|
-
|
scan.startup.specific-offset.skip-events
|
在指定的启动位点后需要跳过的事件数量
|
否
|
-
|
scan.startup.specific-offset.skip-rows
|
在指定的启动位点后需要跳过的数据行数量
|
否
|
-
|
connect.timeout
|
尝试连接到 MySQL 数据库服务器后在超时之前等待的最长时间
|
否
|
默认 30s
|
connect.max-retries
|
建立MySQL连接尝试最大的次数
|
否
|
默认 3
|
connection.pool.size
|
连接池大小
|
否
|
默认 20
|
jdbc.properties.*
|
自定义JDBC URL参数,例如:
'jdbc.properties.useSSL' = 'false' |
否
|
默认 20
|
heartbeat.interval
|
发送心跳事件的时间间隔,用于跟踪最新可用的binlog偏移量, 一般用于解决慢表的问题(更新缓慢的数据表)
|
否
|
默认 20
|
debezium.*
|
Debezium 属性参数
|
否
|
|
四、调整后效果
1、调整内容
块大小调为20万:'scan.incremental.snapshot.chunk.size'='200000'
因job manager扫描是顺序执行,探查一个chunk大小1-4秒,总体特别费时。checkpoint失败固定次数后job manager会重启,调整检查点超时时间为5小时,这样最多可探查时间5小时。
2023-12-29 14:06:08.754 [snapshot-splitting] INFO com.ververica.cdc.connectors.mysql.source.assigners.ChunkSplitter [] -
The distribution factor of table database.test is 3.894905163297E8 according to the min split key 14415117481467904, max split key 200015469389810688 and approximate row count 476520850
2023-12-29 17:42:39.784 [snapshot-splitting] INFO com.ververica.cdc.connectors.mysql.source.assigners.ChunkSplitter [] -
ChunkSplitter has split 2580 chunks for table database.test
2023-12-29 17:43:19.462 [snapshot-splitting] INFO com.ververica.cdc.connectors.mysql.source.assigners.LazyChunkSplitter [] -
Split table database.test into 2591 chunks, time cost: 13032688ms.
开始抽取数据
2023-12-29 17:43:19.464 [SourceCoordinator-Source: source_ods_sn_source_leads_data[1]] INFO com.ververica.cdc.connectors.mysql.source.assigners.MySqlSnapshotSplitAssigner [] -
Snapshot split MySqlSnapshotSplit{tableId=database.test, splitId='database.test:0', splitKeyType=[`leads_id` BIGINT NOT NULL], splitStart=null, splitEnd=[14415117481467904], highWatermark=null} is going to be assigned. Current assigned splits: 1/2591
3、业务库负载
4核 16GB 阿里polardb数据库,在切片阶段,业务库无明显负载,抽数阶段开10*1CU,负载波动不大,可适当调大并发
数据抽取速度4-5万条/s
参考博客:https://developer.aliyun.com/article/801797
https://ververica.github.io/flink-cdc-connectors/release-2.2/content/connectors/mysql-cdc%28ZH%29.html#a-name-id-001-a