订阅更新

Flink CDC读Binlog导致MySQL主库IO飙高——怎么优化不影响线上?

数据采集手册 第 5 题 约 9 分钟读完

本文目录
  1. 先说结论
  2. 业务场景:DBA凌晨告警事件
  3. 方案一:走只读从库 + 复制账号隔离
  4. 方案二:snapshot限流 + chunk配置
  5. 方案三:用Canal/中转层共享Binlog流
  6. 方案对比
  7. 面试高频追问
  8. 实战踩坑:Buffer Pool被snapshot冲掉导致业务慢查询
  9. 总结

先说结论

源库压力来自三处:① 全量snapshot时的SELECT扫表(最重);② Binlog dump线程持续读binlog文件;③ 元数据查询(SHOW CREATE TABLE等)。

最有效的三招:① 走只读从库不连主库;② 全量snapshot分chunk小并发慢慢跑;③ Binlog dump走专门的复制账号+限速。

终极方案:用专门的Binlog采集中转服务(比如Canal)做共享,避免每个CDC作业都直连源库。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

CDC采集对源库的压力是DBA最关心的事情,没有之一。第一次接Flink CDC时,DBA第一反应就是"你们别把主库搞挂了"。这道题面试官不光要听你怎么优化,还要听你懂不懂源库的运行机制——为什么会高、高在哪里、怎么测、怎么降。

业务场景:DBA凌晨告警事件

接入新业务时,启动了一个Flink CDC作业同步orders表(约5亿行)。凌晨2点启动,3点DBA电话报警:MySQL主库IO util 95%,binlog磁盘IO飙到400MB/s,业务慢查询激增。复盘发现:

故障复盘时间线:
  02:00  Flink CDC作业启动,scan.startup.mode=initial
  02:00  启动snapshot:8并发chunk,每chunk扫表
  02:15  IO util从30%涨到80%(snapshot还没完)
  02:30  Buffer Pool命中率从99%掉到92%(snapshot把热数据冲掉)
  03:00  IO util 95%,业务接口p99从100ms涨到800ms
  03:05  DBA强制kill掉Flink CDC的session
 
教训:
  - snapshot连了主库
  - 并发太大没节流
  - 没跟DBA同步评估窗口

方案一:走只读从库 + 复制账号隔离

最重要的一条规则:CDC永远不连主库。专门搭只读从库给数据团队用,权限和连接都跟业务隔离。

-- 1. 在只读从库上建专用CDC账号
CREATE USER 'flink_cdc'@'%' IDENTIFIED BY 'xxx';
GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT
  ON *.* TO 'flink_cdc'@'%';
FLUSH PRIVILEGES;
 
-- 2. MySQL限制此账号的连接数和资源
ALTER USER 'flink_cdc'@'%' WITH MAX_USER_CONNECTIONS 20;
 
-- 3. Flink CDC配置只连readonly slave
'hostname' = 'mysql-cdc-readonly.internal'  -- 专门的从库域名
'username' = 'flink_cdc'
 
-- 4. 给从库配额外的硬件,跟业务从库分开
-- (业务从库给后端服务读,CDC从库给数据团队用)

适用场景:生产环境必须;中大型公司有DBA团队的标配。

注意:从库有复制延迟,如果业务对秒级一致性敏感(比如订单状态变化要立刻反映到风控),需要特别评估这个延迟能否接受。一般延迟在1-3秒内。

方案二:snapshot限流 + chunk配置

Flink CDC 2.x的snapshot算法虽然无锁,但并发太大照样把磁盘IO打满。关键参数有三个,必须根据表大小和源库IO配置仔细调。

CREATE TABLE orders_cdc (...) WITH (
  'connector' = 'mysql-cdc',
  'hostname' = 'mysql-readonly',
  -- chunk大小:默认8096,大表调到32000-50000减少chunk数量
  'scan.incremental.snapshot.chunk.size' = '50000',
  -- 并发snapshot reader:默认1(顺序扫),多并发用server-id范围
  'server-id' = '5400-5403',  -- 4个并发reader
  -- chunk key列:必须有索引(一般是主键)
  'scan.incremental.snapshot.chunk.key-column' = 'id',
  -- 关键:snapshot fetch大小(一次fetch多少行)
  'scan.snapshot.fetch.size' = '1024',  -- 默认1024,IO敏感时调小到256
  -- 是否在snapshot时连续扫binlog(2.4+)
  'scan.incremental.close-idle-reader.enabled' = 'true'
);
 
-- Flink作业层限流:source的并行度=server-id范围
SET 'parallelism.default' = '4';
-- 限制snapshot吞吐(自定义,通过RateLimiter算子)

适用场景:大表初始化(千万行以上);源库性能敏感。

不适用:小表(几十万行),多并发反而是overhead。

方案三:用Canal/中转层共享Binlog流

如果有10个Flink CDC作业都要消费同一个MySQL实例,每个都直接dump binlog,等于10倍的binlog读IO。最佳实践是用Canal统一拉一份Binlog到Kafka,所有下游消费方从Kafka消费。

❌ 每个CDC作业直连(N个作业=N倍binlog IO):
 
  MySQL ←──── Flink CDC Job 1 (订单实时大盘)
        ←──── Flink CDC Job 2 (用户画像更新)
        ←──── Flink CDC Job 3 (风控特征)
        ←──── ...10个作业
 
✓ 共享Binlog层(只1份binlog IO):
 
  MySQL ←─ Canal/Debezium ─→ Kafka
                                ├─→ Flink Job 1
                                ├─→ Flink Job 2
                                ├─→ Flink Job 3
                                └─→ ...任意多个

Kafka做缓冲层还有额外好处:① 多消费方互相不影响;② 下游作业重启不影响源库;③ 历史回溯方便(Kafka保留3-7天)。

方案对比

维度直连主库走只读从库Canal+Kafka中转
源库压力极高中(从库)极低(只1份)
延迟最低(<1s)+复制延迟(1-3s)+Kafka延迟(2-5s)
扩展性差中极好
运维成本低低高(多组件)
适用规模禁止生产<5个CDC作业>5个CDC作业

面试高频追问

追问一:Flink CDC的snapshot会不会锁表?

1.x版本会用FLUSH TABLES WITH READ LOCK锁全库(生产严禁)。2.x引入Chunk-based算法,借鉴Netflix DBLog论文:把表按主键切chunk,每个chunk内部用"select+binlog对齐"的方式快照,整个过程不需要任何锁。这是Flink CDC 2.x能在生产大规模落地的关键。

追问二:怎么测算CDC对源库的真实压力?

一般做三组对比:① 启动CDC前后对比QPS、IOPS、Buffer Pool命中率;② 用sysbench模拟业务负载,CDC启动前后对比tpmC;③ 监控slow query log,看慢查询数量变化。如果Buffer Pool命中率掉了2%以上,或者业务p99上涨了20%以上,就要立刻调整。

追问三:snapshot跑了一半作业挂了,重启会从头来吗?

不会,这正是Chunk-based算法的优势。每个chunk完成后会触发Flink的Checkpoint,记录已完成的chunk列表。作业重启时从Checkpoint恢复,已完成的chunk跳过,未完成的chunk重新跑。生产上一定要把checkpoint间隔配合理(snapshot时建议5分钟一次),不然挂了重启代价太大。

实战踩坑:Buffer Pool被snapshot冲掉导致业务慢查询

还有一个隐藏坑:snapshot会大量SELECT全表,把InnoDB Buffer Pool里的业务热数据冲掉。业务SQL本来都走Buffer Pool(命中率99%),snapshot一跑命中率掉到80%,所有业务查询都变成磁盘IO,p99暴涨。这个比直接IO高更致命,因为问题持续到snapshot结束很久。

-- 监控Buffer Pool命中率
SELECT
  ROUND((1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)) * 100, 2)
  AS hit_rate_pct
FROM (SELECT VARIABLE_VALUE AS Innodb_buffer_pool_reads
      FROM performance_schema.global_status
      WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') a,
     (SELECT VARIABLE_VALUE AS Innodb_buffer_pool_read_requests
      FROM performance_schema.global_status
      WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests') b;
 
-- 解决:在专门的从库上跑snapshot,业务从库不受影响
-- 同时snapshot时段尽量选业务低峰(凌晨2-5点)
-- snapshot结束后业务从库的Buffer Pool不会被冲

解决方案:后来把CDC从库做了物理隔离,专门给数据团队用,同时大表snapshot强制选业务低峰。从那之后再没收到DBA的电话告警。

总结

CDC对源库的影响要从"全量snapshot+持续binlog dump+元数据查询"三方面看。最佳实践三件套:走只读从库、合理配snapshot chunk参数、多消费方用Canal+Kafka共享。面试时把"为什么会高+怎么测+怎么降"讲清楚,比单纯背参数分高得多——DBA和数据团队的协作能力是这道题的隐藏考点。

下一篇聊埋点采集的痛点:埋点上报数据丢了30%,前端SDK的可靠性怎么保证?

你们公司CDC作业是直连主库还是走从库?踩过DBA的雷吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

数据采集 面试通关手册

第6-10篇:埋点与日志采集体系

Wood聊大数据

9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器