4、Debezium深度实践(上):架构、核心概念与嵌入式模式

好,咱们进入 Debezium 的实战环节。说实话,CDC 工具不少,Canal、Maxwell、DataBus 我都用过,但 Debezium 是我个人最推荐的一个。为什么?因为它跟 Kafka 生态结合得太紧密了,而且社区活跃,文档也全。

这一章我们先打基础,把 Debezium 的架构、核心概念讲透,再上手配置 MySQL Binlog 监听,最后聊聊嵌入式模式——这个模式很多人不知道,但其实特别适合轻量级场景。

4.1 Debezium 架构总览

先看整体架构。Debezium 本质上是一个 CDC 连接器框架,它基于 Kafka Connect 构建。你想想看,它做的事情很简单:监听数据库的变更日志,然后把变更事件转换成结构化的消息,推送到 Kafka。

核心流程:

  • 数据库产生变更(INSERT/UPDATE/DELETE)
  • Debezium Connector 读取 Binlog(或 WAL)
  • 生成 Change Event,写入 Kafka Topic
  • 下游消费者消费这些事件

我画了一张架构图,你看一眼就明白了:

Debezium 核心架构 MySQL / PostgreSQL Binlog / WAL Debezium Connector Snapshot + Stream Kafka Topic db.server.table Connector 内部组件 Snapshotter 初始快照 Streaming 实时流读取 Offset Manager 偏移量管理 Event Converter 事件转换器 Snapshot 完成后自动切换到 Streaming 模式,Offset 记录断点位置,保证 Exactly-Once 语义 Event Converter 将 Binlog 事件转为 Avro / JSON / Protobuf 格式

4.2 三大核心概念:Connector、Snapshot、Offset

这三个概念你搞懂了,Debezium 就掌握了一半。

4.2.1 Connector(连接器)

Connector 是 Debezium 的“心脏”。每个数据库类型对应一个 Connector,比如 MySqlConnector、PostgresConnector、MongoDbConnector。它的职责就两件事:

  • 配置管理:告诉 Debezium 连哪个数据库、监听哪些表、用什么格式输出
  • 生命周期管理:启动 Snapshot、切换到 Streaming、处理故障恢复

我记得第一次配置 MySQL Connector 时,漏了一个 database.include.list 参数,结果把整个实例的表都监听了,Kafka 里瞬间多了几百个 Topic……嗯,教训深刻。

配置示例:

{
  "name": "mysql-connector-orders",
  "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": "223344",
    "database.server.name": "prod_orders",
    "database.include.list": "order_db",
    "table.include.list": "order_db.orders,order_db.order_items",
    "database.history.kafka.bootstrap.servers": "kafka:9092",
    "database.history.kafka.topic": "schema-changes.orders",
    "include.schema.changes": "true"
  }
}

4.2.2 Snapshot(快照)

Snapshot 是 Debezium 启动时的“第一件事”。它会把当前数据库中的存量数据全部读出来,写入 Kafka。为什么要做这个?因为 Binlog 只记录增量变更,存量数据你得先捞一遍。

Snapshot 有三种模式:

模式 说明 适用场景
initial 启动时执行完整快照,完成后切到流模式 默认模式,首次部署
when_needed 只在需要时触发快照(比如表结构变更后) 已有快照,增量维护
never 跳过快照,直接开始流读取 只关心增量数据

注意:Snapshot 期间会锁表吗?默认情况下,MySQL Connector 使用 snapshot.locking.mode 参数控制锁行为。我建议生产环境设置为 minimal,只锁结构不锁数据,避免影响业务。

4.2.3 Offset(偏移量)

Offset 是 Debezium 的“记忆”。它记录了当前已经读到 Binlog 的哪个位置。一旦进程重启,Debezium 会从 Offset 记录的位置继续读取,不会重复也不会丢失。

Offset 存储在哪里?默认存在 Kafka 的 connect-offsets Topic 里。你也可以配置成文件存储或数据库存储。

我曾经遇到过一个坑:Kafka 集群故障导致 Offset Topic 数据丢失,重启后 Debezium 从最开始的 Binlog 位置重新读取,结果把过去三个月的数据又发了一遍……下游数仓直接炸了。所以,Offset 的持久化一定要做好,建议开启 Kafka 的日志压缩和副本机制。

4.3 MySQL Binlog 监听配置实战

好,理论讲完了,咱们动手配一个 MySQL 的 Binlog 监听。我会把每一步拆开讲,你跟着做就行。

4.3.1 MySQL 端准备

首先,MySQL 必须开启 Binlog。检查一下你的 MySQL 配置文件:

[mysqld]
server-id = 1
log_bin = mysql-bin
binlog_format = ROW
binlog_row_image = FULL
expire_logs_days = 7

几个关键点:

  • binlog_format 必须为 ROW:只有行模式才能记录每一行的变更细节
  • binlog_row_image 建议 FULL:这样 Debezium 能拿到变更前后的完整数据
  • expire_logs_days:根据你的磁盘空间设置,太短会导致 Offset 过期无法回溯

然后创建一个 Debezium 专用用户:

CREATE USER 'debezium'@'%' IDENTIFIED BY 'debezium_pwd';
GRANT SELECT, RELOAD, SHOW DATABASES, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'debezium'@'%';
FLUSH PRIVILEGES;

注意,REPLICATION SLAVE 和 REPLICATION CLIENT 是必须的,否则 Debezium 读不了 Binlog。

4.3.2 Kafka Connect 配置

Debezium 通常以 Kafka Connect 插件的形式运行。把 Debezium 的 JAR 包放到 Kafka Connect 的插件目录,然后启动 Connect:

# config/connect-distributed.properties
bootstrap.servers=localhost:9092
group.id=connect-cluster
key.converter=org.apache.kafka.connect.json.JsonConverter
value.converter=org.apache.kafka.connect.json.JsonConverter
key.converter.schemas.enable=false
value.converter.schemas.enable=false
offset.storage.topic=connect-offsets
config.storage.topic=connect-configs
status.storage.topic=connect-status
plugin.path=/kafka/plugins

启动后,通过 REST API 提交 Connector 配置:

curl -X POST http://localhost:8083/connectors \
  -H "Content-Type: application/json" \
  -d '{
    "name": "mysql-connector-orders",
    "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": "223344",
      "database.server.name": "prod_orders",
      "database.include.list": "order_db",
      "table.include.list": "order_db.orders",
      "database.history.kafka.bootstrap.servers": "kafka:9092",
      "database.history.kafka.topic": "schema-changes.orders",
      "include.schema.changes": "true"
    }
  }'

配置成功后,你会在 Kafka 里看到 prod_orders.order_db.orders 这个 Topic,里面就是实时变更事件。

4.4 Debezium Engine 嵌入式模式

最后聊聊嵌入式模式。这个模式很多人不知道,但我觉得特别实用。

传统模式下,Debezium 依赖 Kafka Connect 和 Kafka 集群。但有些场景你不想引入 Kafka,比如:

  • 只想把数据库变更直接推送到下游应用
  • 测试环境不想搭一套 Kafka
  • 轻量级的数据同步任务

这时候,Debezium Engine 就派上用场了。它把 Debezium 的核心能力封装成一个 Java 库,你可以在自己的应用里直接启动一个 CDC 引擎,不需要 Kafka。

4.4.1 嵌入式模式架构

说白了,嵌入式模式就是把 Connector、Snapshot、Offset 管理全部内嵌到你的 JVM 进程里。你只需要提供一个 Consumer 接口,Debezium 会把变更事件直接推给你。

嵌入式模式 vs 标准模式:

对比项 标准模式(Kafka Connect) 嵌入式模式(Engine)
依赖 Kafka + Kafka Connect 仅 Debezium 库
部署 独立集群 内嵌到应用
扩展性 高,支持多 Connector 低,适合单实例
适用场景 生产级数据管道 轻量级同步、测试

4.4.2 代码实战

来看一个完整的嵌入式模式示例。我用的是 Debezium 1.9+ 版本,Maven 依赖如下:

<dependency>
    <groupId>io.debezium</groupId>
    <artifactId>debezium-api</artifactId>
    <version>1.9.8.Final</version>
</dependency>
<dependency>
    <groupId>io.debezium</groupId>
    <artifactId>debezium-embedded</artifactId>
    <version>1.9.8.Final</version>
</dependency>
<dependency>
    <groupId>io.debezium</groupId>
    <artifactId>debezium-connector-mysql</artifactId>
    <version>1.9.8.Final</version>
</dependency>

然后写一个主类:

import io.debezium.engine.ChangeEvent;
import io.debezium.engine.DebeziumEngine;
import io.debezium.engine.format.Json;

import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EmbeddedEngineDemo {

    public static void main(String[] args) {

        Properties props = new Properties();
        props.setProperty("name", "engine-mysql-orders");
        props.setProperty("connector.class", "io.debezium.connector.mysql.MySqlConnector");
        props.setProperty("offset.storage", "org.apache.kafka.connect.storage.FileOffsetBackingStore");
        props.setProperty("offset.storage.file.filename", "/tmp/offsets.dat");
        props.setProperty("offset.flush.interval.ms", "60000");
        props.setProperty("database.hostname", "192.168.1.100");
        props.setProperty("database.port", "3306");
        props.setProperty("database.user", "debezium");
        props.setProperty("database.password", "debezium_pwd");
        props.setProperty("database.server.id", "223344");
        props.setProperty("database.server.name", "embedded_orders");
        props.setProperty("database.include.list", "order_db");
        props.setProperty("table.include.list", "order_db.orders");
        props.setProperty("database.history", "io.debezium.relational.history.FileDatabaseHistory");
        props.setProperty("database.history.file.filename", "/tmp/dbhistory.dat");

        try (DebeziumEngine<ChangeEvent<String, String>> engine =
                 DebeziumEngine.create(Json.class)
                     .using(props)
                     .notifying(records -> {
                         for (ChangeEvent<String, String> r : records) {
                             System.out.println("Key: " + r.key());
                             System.out.println("Value: " + r.value());
                             System.out.println("---");
                         }
                     })
                     .build()) {

            ExecutorService executor = Executors.newSingleThreadExecutor();
            executor.execute(engine);

            // 让引擎运行 60 秒
            Thread.sleep(60000);
            engine.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这段代码做了几件事:

  • 配置 MySQL 连接信息和监听表
  • 使用文件存储 Offset 和 Schema 历史
  • 启动引擎,把变更事件打印到控制台

个人建议:嵌入式模式非常适合做“数据库变更 -> 实时通知”的场景。我之前在一个订单系统中,用嵌入式模式监听订单状态变更,直接推送到 WebSocket 给前端做实时展示,省掉了 Kafka 这一层,延迟在 50ms 以内。

注意:嵌入式模式是单进程的,如果进程挂了,CDC 就停了。生产环境建议配合进程守护工具(如 Supervisor)或容器编排(K8s)来保证高可用。

4.5 本章小结

这一章我们覆盖了 Debezium 的核心内容:

  • 架构:Connector 内部有 Snapshotter、Streaming、Offset Manager、Event Converter 四大组件
  • 三大概念:Connector 负责连接、Snapshot 负责存量、Offset 负责断点
  • MySQL 配置:Binlog 必须为 ROW 模式,用户需要 REPLICATION 权限
  • 嵌入式模式:轻量级 CDC,适合不需要 Kafka 的场景