我第一次接触Flink CDC是在一个电商公司的用户行为分析项目里。当时需要把MySQL中的用户订单数据实时同步到Elasticsearch做搜索和推荐,传统的定时批处理方式总是有15-30分钟的延迟,导致促销活动时用户看到的推荐商品总是慢半拍。技术总监拍着桌子说:"必须找到毫秒级延迟的同步方案!"这就是我们与Flink CDC结缘的开始。
Flink CDC全称Change Data Capture,是Apache Flink社区基于Debezium开发的变更数据捕获组件。它通过解析数据库的binlog日志,能捕获所有数据的增删改操作。相比其他方案,它有三大杀手锏:
真正的流式处理:不像某些工具伪装成实时同步,实际还是每分钟拉取一次数据。Flink CDC从binlog读取的每条变更都会立即触发处理,实测延迟可以控制在毫秒级。
全量+增量一体化:首次启动时会先做全表扫描(全量同步),之后自动切换到binlog监听(增量同步)。我们不用再写复杂的初始化脚本,这在处理历史数据上千万的表时特别省心。
Exactly-Once语义:通过Checkpoint机制确保数据不丢不重。有次网络闪断导致任务中断,恢复后数据依然精确无误,这个特性在生产环境太重要了。
这里有个实际场景的对比表格:
| 同步方式 | 延迟 | 资源占用 | 数据一致性 | 开发复杂度 |
|---|---|---|---|---|
| 定时批量导出 | 分钟级 | 间歇性高 | 最终一致 | 低 |
| Canal+Kafka | 秒级 | 持续中等 | 至少一次 | 高 |
| Flink CDC | 毫秒级 | 持续平稳 | 精确一次 | 中 |
踩过几次版本兼容的坑后,我总结出一个稳定组合:
这些jar包需要放在Flink的lib目录下。有个容易忽略的点:Elasticsearch连接器版本要匹配集群版本,我们有一次用了7.x的连接器连8.x的ES集群,报的错误信息非常隐晦,排查了大半天。
对于本地测试,用Docker compose最方便。这里分享一个我优化过的docker-compose.yml,增加了资源限制避免机器卡死:
yaml复制version: '2.1'
services:
mysql:
image: debezium/example-mysql:1.1
ports: ["3306:3306"]
environment:
- MYSQL_ROOT_PASSWORD=123456
deploy:
resources:
limits:
memory: 512M
elasticsearch:
image: elastic/elasticsearch:7.6.0
environment:
- discovery.type=single-node
- ES_JAVA_OPTS=-Xms512m -Xmx512m
ports: ["9200:9200"]
ulimits:
memlock: -1:-1
kibana:
image: elastic/kibana:7.6.0
ports: ["5601:5601"]
depends_on: ["elasticsearch"]
启动命令建议加上--build参数,确保镜像是最新的:
bash复制docker-compose up -d --build
在Flink SQL中定义CDC源表时,有几个关键参数容易配错:
sql复制CREATE TABLE mysql_user (
id INT,
name STRING,
age INT,
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'mysql-cdc',
'hostname' = 'mysql', -- Docker容器名
'username' = 'root',
'password' = '123456',
'database-name' = 'test',
'table-name' = 'user',
'server-time-zone' = 'Asia/Shanghai', -- 避免时区问题
'scan.incremental.snapshot.enabled' = 'true' -- 增量快照
);
特别提醒:如果MySQL表有datetime字段,务必设置正确的时区。我们曾经因为时区配置错误,导致所有时间戳都差了8小时。
同步到ES时,建议预先创建好索引并设置合理的mapping:
sql复制CREATE TABLE es_user (
id INT,
name STRING,
age INT,
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'elasticsearch-7',
'hosts' = 'http://elasticsearch:9200',
'index' = 'user_index',
'sink.bulk-flush.max-actions' = '50', -- 批量写入条数
'sink.bulk-flush.interval' = '1s' -- 刷新间隔
);
生产环境建议调整这两个参数:
max-actions(如500)可以提高吞吐interval(如200ms)可以降低延迟在Flink Web UI中要重点观察这些指标:
我们曾遇到一个典型问题:Checkpoint经常超时失败。最终发现是ES集群写入性能不足,调整了这些参数后稳定运行:
sql复制ALTER TABLE es_user SET (
'sink.bulk-flush.max-actions' = '100',
'sink.bulk-flush.interval' = '2s'
);
案例1:同步任务突然停止,日志显示"binlog位置不存在"
binlog_expire_logs_seconds=604800(保留7天)案例2:ES中出现重复文档
PRIMARY KEY正确定义案例3:同步延迟越来越高
实际项目中经常需要把多个关联表合并到一个ES索引。比如用户表和用户地址表合并:
sql复制-- 创建视图
CREATE VIEW user_with_address AS
SELECT u.id, u.name, a.province, a.city
FROM mysql_user AS u
JOIN mysql_address AS a ON u.id = a.user_id;
-- 同步到ES
INSERT INTO es_composite_index
SELECT * FROM user_with_address;
可以在同步过程中进行简单的ETL:
sql复制INSERT INTO es_user
SELECT
id,
UPPER(name) AS name, -- 姓名转大写
CASE
WHEN age < 0 THEN 0 -- 年龄纠错
ELSE age
END AS age
FROM mysql_user;
对于复杂转换,建议使用DataStream API实现,灵活性更高。
在我们的测试环境中(8核16G服务器),同步性能表现如下:
| 数据量 | 写入QPS | 平均延迟 | CPU占用 |
|---|---|---|---|
| 10万条 | 5,000 | 200ms | 45% |
| 50万条 | 8,000 | 500ms | 68% |
| 100万条 | 12,000 | 1.2s | 85% |
当QPS超过1万时,建议: