4. Debezium 架构与核心概念:Connector、Snapshot、CDC、Offset、Schema History

好,咱们进入正题。Debezium 这东西,说白了就是一个 CDC(Change Data Capture)平台。它盯着数据库的 Binlog,一有变化就往外推。我刚开始接触它的时候,觉得概念挺多,有点绕。但用熟了就会发现,核心就五个东西:Connector、Snapshot、CDC、Offset、Schema History。

这五个概念,你搞懂了,Debezium 就算入门了。咱们一个一个拆开聊。

4.1 Connector:连接器的角色

Connector 是 Debezium 的“眼睛”。它负责跟数据库建立连接,读取 Binlog。每个数据库实例,或者每张表,都可以配一个 Connector。

我个人习惯,一个 Connector 只监控一个数据库实例。别图省事,把多个实例塞到一个 Connector 里。出了问题,排查起来很痛苦。

Connector 的核心职责:

  • 连接数据库,拉取 Binlog 事件
  • 将 Binlog 事件转换成 Debezium 内部的消息结构
  • 管理 Offset,记录消费位置
  • 管理 Schema History,维护表结构变更

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

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

这里有个坑,我提醒一下。database.server.id 这个值,在 MySQL 集群里必须是唯一的。不能跟其他从库或者主库的 server_id 重复。我曾经见过有人直接复制配置,忘了改这个值,结果 Connector 启动后,MySQL 主库直接报错,说检测到重复的 server_id。嗯,排查了半天。

4.2 Snapshot:初始快照机制

Snapshot,就是“快照”。当 Connector 第一次启动时,它需要知道数据库的当前状态。这时候,Debezium 会执行一次 Snapshot,把表里的现有数据全部读出来,发到 Kafka。

为什么要做 Snapshot?因为 Binlog 只记录变更,不记录历史全量数据。如果你从头开始消费,Binlog 可能早就被清理了。所以,Debezium 先做一次全量同步,然后再切到增量 CDC。

Snapshot 的两种模式:

  • initial(默认): 启动时执行一次全量快照,然后切换到 CDC。适合新部署的场景。
  • when_needed: 只在需要时执行快照。比如表结构变了,或者 Offset 丢了。
  • never: 不执行快照,直接开始 CDC。前提是你确定 Binlog 里还有你需要的数据。
  • initial_only: 只做快照,不做 CDC。适合一次性数据迁移。

我记得有一次,线上一个表有 5000 万行数据。默认的 Snapshot 是单线程的,跑了 3 个小时还没完。后来我改成 snapshot.mode: "initial" 配合 snapshot.fetch.size: 10000,分批读取,速度才上来。嗯,这里要注意,Snapshot 期间,数据库的 IO 压力会比较大,最好在业务低峰期做。

4.3 CDC:增量变更捕获

CDC 是 Debezium 的核心价值。Snapshot 做完之后,Connector 就进入 CDC 模式。它持续监听 Binlog,把 INSERT、UPDATE、DELETE 事件转换成消息,推送到 Kafka。

每条 CDC 消息,都包含三个部分:

  • Key: 主键值,用于标识是哪一行数据变了。
  • Value: 变更前后的数据,以及操作类型(c = create, u = update, d = delete)。
  • Headers: 元数据,比如数据库名、表名、操作时间戳。

举个例子,一条 UPDATE 消息的 Value 结构:

{
  "schema": { ... },
  "payload": {
    "before": {
      "id": 101,
      "name": "旧名称",
      "price": 29.99
    },
    "after": {
      "id": 101,
      "name": "新名称",
      "price": 39.99
    },
    "source": {
      "version": "1.9.7.Final",
      "connector": "mysql",
      "name": "myapp",
      "ts_ms": 1690000000000,
      "snapshot": "false",
      "db": "inventory",
      "table": "products",
      "server_id": 223344,
      "gtid": null,
      "file": "mysql-bin.000003",
      "pos": 12345,
      "row": 0,
      "thread": 7
    },
    "op": "u",
    "ts_ms": 1690000001000,
    "transaction": null
  }
}

你想想看,有了这个结构,下游系统就能精确知道:哪张表、哪一行、什么时候、发生了什么变化。这就是 CDC 的魅力。

4.4 Offset:消费位置管理

Offset 是 Debezium 的“书签”。它记录了当前消费到了 Binlog 的哪个位置。如果 Connector 重启了,它会根据 Offset 从断点处继续消费,不会丢数据,也不会重复消费。

Offset 存储在 Kafka 的 __consumer_offsets 主题里,或者你也可以配置成存到文件里。我个人建议用 Kafka 存储,因为文件存储容易丢失,而且不好管理。

Offset 丢失的后果:

如果 Offset 丢了,Debezium 会重新执行 Snapshot。对于大表来说,这可能是灾难性的。我曾经遇到过,Kafka 集群磁盘满了,导致 Offset 主题被清理。结果 Connector 重启后,直接重新快照,把数据库压垮了。所以,一定要监控 Offset 主题的保留策略,别让它被自动清理掉。

Offset 的结构大概长这样:

{
  "sourcePartition": {
    "server": "myapp"
  },
  "sourceOffset": {
    "file": "mysql-bin.000003",
    "pos": 12345,
    "gtid": null,
    "row": 0,
    "snapshot": false,
    "last_snapshot_record": false
  }
}

这里 file 和 pos 就是 Binlog 的文件名和位置。每次消费完一条消息,Debezium 都会更新这个 Offset。嗯,这里要注意,Offset 的提交是异步的,所以极端情况下可能会有少量重复消费。但至少不会丢数据。

4.5 Schema History:表结构历史

Schema History 是 Debezium 的“记忆”。它记录了表结构的历史变更。为什么需要这个?因为 Binlog 事件里只包含数据,不包含表结构。如果表结构变了,比如新增了一列,Debezium 需要知道新列的定义,才能正确解析后续的 Binlog 事件。

Schema History 存储在 Kafka 的一个独立主题里。每次表结构发生变化,Debezium 都会把 DDL 语句记录到这个主题。Connector 启动时,会从这个主题里读取历史 DDL,重建表结构。

Schema History 的配置:

database.history.kafka.bootstrap.servers: "kafka:9092"
database.history.kafka.topic: "schema-changes.inventory"

这个主题的保留策略建议设置为 compact,只保留最新的 DDL 记录。否则,时间长了,主题会变得很大。

我遇到过一个问题:有一次,DBA 直接在主库上执行了 ALTER TABLE,但是 Debezium 的 Schema History 主题因为网络问题,没有成功写入。结果 Connector 重启后,无法解析新的 Binlog 事件,直接报错。后来我加了一个监控,检查 Schema History 主题的写入延迟。一旦延迟超过 5 秒,就报警。嗯,这个坑,你们可以提前规避。

4.6 核心概念关系图

下面这张图,展示了 Connector、Snapshot、CDC、Offset、Schema History 之间的关系。我画得比较直观,你一看就懂。

Debezium 核心概念关系图 MySQL 数据库 Connector Snapshot CDC Offset Schema History Kafka 消息队列 读取 Binlog 启动时触发 Snapshot 完成后 全量数据 增量事件 记录位置 记录 DDL 存储 Offset 存储 DDL Snapshot 只执行一次 CDC 持续运行

从图里可以看出来,Connector 是核心调度器。它先触发 Snapshot,把全量数据推到 Kafka。Snapshot 完成后,自动切换到 CDC,持续监听 Binlog。CDC 每消费一条消息,都会更新 Offset,记录消费位置。同时,如果表结构变了,CDC 会把 DDL 记录到 Schema History 主题。这样,即使 Connector 重启,也能从 Offset 恢复位置,从 Schema History 恢复表结构。

4.7 核心概念对比表

最后,我用一张表总结一下这五个概念的区别。方便你以后查阅。

概念 作用 触发时机 存储位置 丢失后果
Connector 连接数据库,管理整个流程 手动启动 / 自动重启 Kafka Connect 集群 无法捕获变更
Snapshot 全量数据同步 首次启动 / Offset 丢失 Kafka 主题 数据不一致
CDC 增量变更捕获 Snapshot 完成后持续运行 Kafka 主题 无法捕获增量变更
Offset 记录消费位置 每次消费后更新 Kafka / 文件 重新 Snapshot
Schema History 记录表结构变更 DDL 执行时 Kafka 主题 无法解析新结构

嗯,这五个概念,你记住了,Debezium 的架构就清晰了。后面咱们会深入每个环节的配置和调优。到时候,你再回头看这张表,会有更深的理解。