3. Debezium入门:它到底是什么?

Debezium,说白了就是一个能「偷看」数据库变更日志的开源工具。我最早接触它是在2018年,当时公司要做实时数仓,每天全量同步MySQL数据,凌晨跑批跑到天亮。后来我发现了Debezium——嗯,这玩意儿确实改变了我的工作方式。

它的核心能力很简单:把数据库的INSERT、UPDATE、DELETE操作,实时转化成事件流。你想想看,以前我们要监听数据变更,要么写触发器,要么轮询查表,要么改业务代码埋点。这些方式要么侵入性强,要么延迟高,要么维护成本大。Debezium直接读取数据库的binlog(MySQL)、WAL(PostgreSQL)或oplog(MongoDB),不碰业务表,不写一行代码。

一句话总结:Debezium = 数据库的「日志窃听器」 + 「事件转换器」 + 「Kafka生产者」。

3.1 支持的数据源

Debezium目前支持三大主流数据库,我按使用频率排个序:

数据库 读取的日志 典型场景 我的经验
MySQL binlog(ROW格式) 电商订单、用户中心 最成熟,坑最少
PostgreSQL WAL(逻辑复制槽) 金融、物联网时序数据 需要提前创建复制槽
MongoDB oplog(副本集) 文档型数据、日志存储 注意副本集必须启用

我个人习惯,新项目首选MySQL + Debezium。为什么?因为MySQL的binlog机制最成熟,社区文档最全,遇到问题基本都能搜到答案。PostgreSQL的WAL逻辑复制槽需要手动管理,我曾经在生产环境忘记清理复制槽,导致WAL文件堆积,磁盘爆了——嗯,那次教训挺深刻的。

避坑指南:我曾经在MongoDB副本集上踩过坑——如果副本集成员少于3个,oplog可能被自动清理,导致Debezium丢失变更事件。生产环境至少部署3个节点。

3.2 架构组件:三大核心模块

Debezium的架构不复杂,就三个核心组件。我画了一张图帮你理解它们的关系:

Debezium 核心架构 MySQL / PG / Mongo binlog / WAL / oplog Connector 连接器(配置驱动) Debezium Engine Snapshot(快照) Streaming(流式) Kafka Topic 变更事件流 工作流程: 1. Connector 读取数据库日志位置(如MySQL binlog文件名+偏移量) 2. 首次启动执行 Snapshot,将全量数据快照写入Kafka 3. Snapshot完成后自动切换 Streaming,持续捕获增量变更

3.3 Connector:配置驱动的连接器

Connector是Debezium的「入口」。你只需要写一个JSON配置文件,告诉它:

  • 连接哪个数据库(host、port、用户名、密码)
  • 监听哪些表(table.include.list)
  • 把事件发到哪个Kafka Topic(topic.prefix)

举个例子,一个MySQL Connector的配置长这样:

{
  "name": "my-connector",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",
    "database.hostname": "192.168.1.100",
    "database.port": "3306",
    "database.user": "debezium",
    "database.password": "debezium_pwd",
    "database.server.id": "184054",
    "database.server.name": "myapp",
    "database.include.list": "mydb",
    "table.include.list": "mydb.orders,mydb.users",
    "database.history.kafka.bootstrap.servers": "kafka:9092",
    "database.history.kafka.topic": "schema-changes.myapp"
  }
}

这里有个关键点——database.server.name。我刚开始用的时候,以为这只是个随便填的标识符。后来发现,它直接决定了Kafka Topic的前缀。比如你填了myapp,那么orders表的变更事件就会发到myapp.mydb.orders这个Topic。嗯,命名规范很重要,不然Topic一多就乱了。

小技巧:我个人习惯把database.server.name设为应用名+环境,比如order-prod、user-staging。这样在Kafka里一眼就能看出Topic属于哪个系统。

3.4 Snapshot:全量快照

第一次启动Connector时,Debezium会先做一次全量快照。为什么需要这一步?

你想想看,如果数据库里已经有100万条订单数据,而Debezium只从当前时刻开始监听,那之前的100万条就永远丢了。Snapshot的作用就是:先把存量数据全部读出来,发到Kafka,然后再切换到增量监听。

Snapshot的执行模式有四种:

模式 行为 适用场景
initial 先快照,后流式 默认模式,首次部署
when_needed 只在需要时触发 恢复丢失数据
never 跳过快照,直接流式 已有全量数据,只需增量
schema_only 只捕获表结构,不读数据 只关心未来变更

我在项目中遇到过一个问题:一张表有5000万行数据,Snapshot跑了3个小时还没跑完,Kafka的磁盘快满了。后来我改用schema_only模式,先只同步表结构,然后用一个离线脚本把全量数据批量导入Kafka,最后再切回流式模式。嗯,有时候「不按套路出牌」反而更高效。

注意:Snapshot期间,Debezium会锁表吗?默认情况下,MySQL Connector的Snapshot会加READ LOCK,防止数据不一致。如果你的表很大,建议在低峰期执行,或者使用snapshot.locking.mode=none(但要确保数据一致性)。

3.5 Streaming:增量流式捕获

Snapshot完成后,Debezium自动进入Streaming模式。这时候它就像个「永不休息的哨兵」,持续读取数据库的变更日志。

Streaming的核心机制:

  • 轮询日志:每隔poll.interval.ms(默认500ms)检查一次日志是否有新变更
  • 解析事件:把binlog/WAL/oplog的二进制格式,解析成结构化的JSON事件
  • 发送到Kafka:每个变更事件包含before(旧值)和after(新值)两个字段

一个典型的变更事件长这样:

{
  "schema": { ... },
  "payload": {
    "before": null,
    "after": {
      "id": 1001,
      "name": "张三",
      "status": "ACTIVE"
    },
    "source": {
      "db": "mydb",
      "table": "users",
      "ts_ms": 1700000000000
    },
    "op": "c",
    "ts_ms": 1700000001000
  }
}

注意op字段:c表示创建(Create)、u表示更新(Update)、d表示删除(Delete)、r表示快照读取(Read)。这个字段在后续做增量计算时非常有用——你可以根据操作类型决定怎么处理。

我曾经遇到一个坑:Streaming模式下,如果Kafka挂了,Debezium会不断重试。默认重试间隔是1秒,重试次数不限。结果有一次Kafka集群重启花了10分钟,Debezium在这10分钟里产生了大量重试日志,把磁盘打满了。后来我加了max.retries和retry.backoff.ms参数,限制重试次数和间隔。

我的建议:生产环境一定要配置offset.storage.file.filename(文件存储)或offset.storage.topic(Kafka存储)。这样即使Debezium重启,也能从上次中断的位置继续消费,不会重复也不会丢失。

3.6 小结:Debezium到底解决了什么问题?

回到最开始的问题——我们为什么要用Debezium?

  • 无侵入:不碰业务代码,不改表结构
  • 低延迟:毫秒级捕获变更,比轮询快得多
  • 全量+增量:Snapshot搞定存量,Streaming搞定增量
  • 生态好:输出到Kafka,下游随便接Flink、Spark、Elasticsearch