不再轮询数据库:用 cdc-go 把 MySQL 变更实时送到消息队列

chihqiang chihqiang 2026-11-11 约 10 分钟 3729 字

订单刚刚更新,搜索索引却还停留在几分钟前;缓存、统计服务和业务库之间,也常常靠定时任务反复查询来“追数据”。

如果你也遇到过这些问题,可以看看 cdc-go:一个 Go 编写的轻量级 MySQL CDC(Change Data Capture)服务。它读取 MySQL 的 ROW 格式 binlog,把行级变更转换成统一的 JSON 事件,再发送到 stdout、Redis、Kafka、RabbitMQ、RocketMQ 或 Pulsar。

简单说,业务库负责产生变更,cdc-go 负责把变更搬出来,后续服务通过消息中间件各自处理。业务代码不必为了“数据变了”而不停扫表。

它是怎么工作的?

1
2
3
4
5
6
7
MySQL ROW binlog
│
▼
cdc-go ──────► Kafka / Redis / RabbitMQ / RocketMQ / Pulsar
│ │
└── 文件或 Redis 保存位点 ▼
搜索索引、缓存、报表等消费者

cdc-go 以 MySQL replication client 的身份读取 binlog,识别每行记录的新增、更新和删除,再将事件投递到配置的输出端。它本身不负责替你更新 ES 或刷新缓存;它提供的是一条可供下游订阅的变更流。

输出的事件结构保持一致。下面是一次更新的示意:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"time": "2026-10-21T10:31:15+08:00",
"server_id": 1,
"pos": 194512512,
"row": {
"time": 1792549875,
"database": "app",
"table": "user",
"type": "update",
"data": {
"id": 42,
"name": "Alice",
"balance": "20.00"
},
"old": {
"id": 42,
"name": "alice",
"balance": "12.34"
}
}
}

row.type 为 insert、update 或 delete。新增事件的 data 是新增后的行,删除事件的 data 是被删除的行;更新事件则同时包含变更后的 data 和变更前的 old。因此,下游不需要为每一种消息队列重新设计一套数据格式。

为什么值得一试?

不用定时扫表

传统轮询往往意味着固定间隔、额外查询负载和“多久同步一次”的取舍。直接读取 binlog,可以按行变更获取事件,更适合把数据库变化及时传递给下游系统。

输出端选择多

开发时可以用 stdout 直接观察 JSON;接入现有基础设施时,可以选择 Redis、Kafka、RabbitMQ、RocketMQ 或 Pulsar。位点则可以保存在本地文件或 Redis 中。单机验证可以从文件存储开始;容器环境要确保位点存储具备持久性,并根据实际运行拓扑选择合适的方式。

过滤不需要的表

支持通过正则表达式排除系统库,或只订阅指定的数据库和表。把采集范围先收窄,能减少无关消息,也让后续消费者更容易管理。

配置适合部署环境

服务使用 YAML 配置,并支持在配置值中引用 ${VAR} 和 ${VAR:-fallback}。连接密码等环境相关参数不必写死在配置文件里;同一份配置也更容易在本机、容器和 Kubernetes 环境之间复用。

快速体验:MySQL 到 Kafka

先确认 MySQL 开启了 ROW 格式 binlog:

1
2
3
4
[mysqld]
log-bin = mysql-bin
binlog_format = ROW
server-id = 1

CDC 连接账号需要有读取复制日志的权限;如果运行过程中需要查询表元数据,还需要相应的 SELECT 权限。生产环境建议创建专用账号,并为每个 replication client 使用唯一的 server_id,不要和 MySQL 副本或其他 CDC 服务冲突。

从源码构建:

1
2
3
git clone https://github.com/chihqiang/cdc-go.git
cd cdc-go
make build

将 config.yml 复制为自己的配置文件,然后把需要的段落设为 MySQL、Redis 位点存储和 Kafka 输出,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
store:
type: redis
redis:
addr: "127.0.0.1:6379"
db: 0

source:
type: mysql
mysql:
addr: "127.0.0.1:3306"
user: "cdcgo"
password: "${SOURCE_MYSQL_PASSWORD}"
server_id: 12345
include_table_regex:
- "app.*"

producer:
type: kafka
kafka:
brokers:
- "127.0.0.1:9092"
topic: "cdc-go-events"

这里的 server_id 要按环境选择一个未被其他 replication client 使用的值。启动时提供密码环境变量:

1
SOURCE_MYSQL_PASSWORD='你的数据库密码' ./cdc-go -c config.local.yml

第一次尝试也可以把 producer.type 设成 stdout,不接消息队列,先观察事件内容和表过滤结果。完整配置项、Docker 示例和 MySQL 权限说明可以查看项目的 README。

上线前,别忽略这几件事

位点恢复不等于端到端 exactly-once。 cdc-go 支持将 binlog 位点写入文件或 Redis,并在重启后从已保存的位置继续读取。但这不意味着业务消费者绝不会重复处理,或任意故障下都能自动完成端到端恢复。建议下游处理具备幂等性,并针对消息队列、位点存储和进程重启做故障演练。

位点存储要跟部署方式匹配。 如果用文件位点,容器重建后要确保保存目录仍然挂载在持久化卷上;否则本地文件随容器消失。多实例部署时也要明确位点的共享方式和运行拓扑,不要仅凭“支持 Redis”就直接启动多个读取者。

CDC 主要解决增量,不是全量迁移。 新服务首次上线时,通常还需要单独处理历史数据的全量初始化,并设计好初始化期间与增量事件的衔接。

消费者应当可重复执行。 重试和消息队列的投递语义可能让同一条变更被处理不止一次;更新缓存、写搜索索引或生成下游记录时,尽量使用业务主键和事件信息实现幂等。

另外,项目中的 cdc-go-task 是用于演示和参考的消费者,会读取消息并记录事件,并不是开箱即用的生产业务任务框架。接入实际业务时,应由自己的消费者实现数据校验、幂等、失败处理和监控。

适合什么场景?

如果你想把 MySQL 的增量变化分发给搜索索引、缓存刷新、异步业务处理或数据分析服务,又不希望每个服务都直接轮询数据库,cdc-go 值得作为一个轻量的候选方案。

它的价值不在于替代消息队列或完整的数据集成平台,而在于把“读取 MySQL 变更并输出统一事件”这件事做成一个配置清晰、容易试跑的独立服务。先用 stdout 看懂事件,再接入团队已有的消息中间件,往往是最省力的验证方式。

项目地址:github.com/chihqiang/cdc-go

分享: Twitter Facebook
chihqiang

chihqiang

代码与生活的点滴记录