基于 Debezium Server 与 Redis 的高可用 CDC 架构实践

在 Canal 社区日益沉寂的背景下,Debezium 已成为业界事实上的 CDC 标准方案。它能实时捕获数据库变更并投递至 Kafka、Redis、Pulsar 等下游系统,广泛应用于:

  • 搜索索引同步
  • 缓存更新
  • 实时数据管道
  • 其他增量数据场景

然而,在生产环境中,单点部署的 Debezium Server 存在致命风险

  • 进程崩溃 → 数据同步中断
  • 主机宕机 → 变更丢失(offset 未持久化)
  • 升级维护 → 服务不可用,影响下游链路

这在对 7×24 小时数据连续性 有要求的系统中,是不可接受的。

Debezium 本质上是一个 CDC 引擎 ,负责解析数据库日志并生成变更事件。而 Debezium Server 则是 Debezium 官方提供的 即用型运行程序

  • 无需 Kafka Connect
  • 无需复杂集群
  • 通过配置即可直接投递数据
  • 支持的 Sink 包括:Kafka、Redis、Pulsar、Kinesis、自定义 Sink(SPI)

这使得 Debezium Server 非常适合中小规模、轻量级 CDC 场景。

在 Debezium 的官方推荐架构中,Kafka 几乎是标配组件:

  • Kafka 负责消息缓冲
  • Kafka Connect 负责 HA
  • Consumer Group 负责负载均衡

但 Kafka 运维复杂度高(ZK / KRaft、磁盘、监控),对资源要求高(CPU / IO),对小规模 CDC 场景性来说价比低。

在我们的业务中,CDC 任务数量有限,更关注 稳定性而非吞吐量,因此我们希望在不引入 Kafka 的前提下,实现 Debezium Server 的高可用。

为保障数据管道的 7×24 小时连续性 ,我们结合 Redis 设计了 Active-Standby 高可用架构

  • 多个 Debezium 实例同时运行
  • 仅一个处于 ACTIVE 状态处理任务
  • 其余为 STANDBY 状态实时待命
  • ACTIVE 故障时,STANDBY 自动秒级接管

Redis 成为理想选择,原因如下

  • 分布式锁:实现 Active 节点选举
  • 接收器:存储偏移量 和 内部模式历史记录
  • 团队已具备 Redis 运维能力

配置文件(application.properties)

properties 复制代码
# --- Sink: 输出到 Redis Stream ---
debezium.sink.type=redis
debezium.sink.redis.address=192.168.0.205:6379
debezium.sink.redis.password=HBcjy@1303
debezium.sink.redis.stream.name=dbz-events

# --- Source:Mysql 连接
debezium.source.connector.class=io.debezium.connector.mysql.MySqlConnector
debezium.source.database.hostname=localhost
debezium.source.database.port=3310
debezium.source.database.user=root
debezium.source.database.password=123456
debezium.source.database.server.name=test
debezium.source.database.include.list=safety_check
debezium.source.table.include.list=safety_check.device_track
debezium.source.database.server.id=2013306
debezium.source.topic.prefix=mysql-prod

# --- Offset 存储 ---
debezium.source.offset.storage=io.debezium.storage.redis.offset.RedisOffsetBackingStore
debezium.source.offset.storage.redis.key=dbz-offsets

# --- Schema History ---
debezium.source.schema.history.internal=io.debezium.storage.redis.history.RedisSchemaHistory
debezium.source.schema.history.internal.redis.key=dbz-history

# --- 其他 ---
debezium.format.value.converter.schemas.enable=false
quarkus.log.level=INFO

启动脚本(run-with-lock.sh

sh 复制代码
#!/bin/bash
set -e
export JAVA_HOME="/usr/lib/jvm/java-11-openjdk"
export PATH="$JAVA_HOME/bin:$PATH"

# ===== 配置区 =====
REDIS_HOST="192.168.0.101"
REDIS_PORT=6379
REDIS_PASSWORD="YOUR_REDIS_PASSWORD"
LOCK_KEY="debezium-lock"
LOCK_TTL=30  # 锁过期时间(秒)

DEBEZIUM_HOME="/data/debezium/debezium-server"
CONFIG_DIR="/data/debezium/debezium-server/conf"
LOG_DIR="/data/debezium/debezium-server/logs"

# 自动生成唯一实例 ID(hostname + PID)
INSTANCE_ID="$(hostname)-$$"
LOG_FILE="$LOG_DIR/debezium-$INSTANCE_ID.log"

# Redis 连接选项
REDIS_OPTS="-h $REDIS_HOST -p $REDIS_PORT"
if [[ -n "$REDIS_PASSWORD" ]]; then
    REDIS_OPTS="$REDIS_OPTS -a $REDIS_PASSWORD"
fi

# 日志函数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

# 尝试获取 Redis 锁
acquire_lock() {
    local result
    result=$(redis-cli $REDIS_OPTS SET "$LOCK_KEY" "$INSTANCE_ID" NX EX "$LOCK_TTL" 2>/dev/null)
    [[ "$result" == "OK" ]]
}

# 后台心跳续期
renew_lock() {
    while true; do
        sleep $((LOCK_TTL / 3))
        redis-cli $REDIS_OPTS \
            EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) else return 0 end" \
            1 "$LOCK_KEY" "$INSTANCE_ID" "$((LOCK_TTL * 1000))" >/dev/null 2>&1
    done
}

# 释放锁
release_lock() {
    redis-cli $REDIS_OPTS \
        EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" \
        1 "$LOCK_KEY" "$INSTANCE_ID" >/dev/null 2>&1
    log "Released lock."
}

# ===== 主逻辑 =====
log "Starting Debezium HA node with ID: $INSTANCE_ID"

while true; do
    if acquire_lock; then
        log "Acquired lock! Starting Debezium Server as ACTIVE"
        trap release_lock EXIT

        # 启动心跳
        renew_lock &
        RENEW_PID=$!

        # 启动 Debezium(前台)
        cd "$DEBEZIUM_HOME"
        ./run.sh --config-dir="$CONFIG_DIR" >> "$LOG_FILE" 2>&1 &

        DEBEZIUM_PID=$!
        log "Debezium Server started (PID: $DEBEZIUM_PID)"

        # 等待进程退出
        wait $DEBEZIUM_PID
        kill $RENEW_PID 2>/dev/null || true

        log "Debezium stopped. Releasing lock and retrying in 5s..."
        sleep 5
    else
        log "Could not acquire lock. Retrying in 10s..."
        sleep 10
    fi
done
  • TTL + 心跳续期:防止进程僵死导致锁永久持有
  • Lua 脚本原子操作:确保只有持有者能续期/释放
  • 自动重试机制:STANDBY 节点持续抢锁

正常数据同步

  • 操作 :向 safety_check.device_track 表持续插入 1000 条记录
  • 每条变更事件写入 Redis Stream dbz-events
  • 结果 :Redis 中 XRANGE dbz-events - + COUNT 5 可见完整事件

ACTIVE 节点主动停止

  • 操作kill -15 主动终止 Instance A(ACTIVE)
  • Instance B 或 C 在 ≤30 秒内接管,继续同步
  • 结果
    • 第 28 秒,Instance B 获取 debezium-lock
    • 新变更事件继续写入 dbz-events
    • 无事件丢失(对比 MySQL 最终行数 vs Stream 总数)

ACTIVE 节点异常崩溃

  • 操作kill -9 强制杀死 Instance A
  • 锁因 TTL 过期自动释放,STANDBY 接管
  • 结果
    • 第 31 秒,Instance C 成功抢锁并启动 connector
    • 从 Redis 中 dbz-offsets 读取最新位点,精确续传
    • 无重复消费(检查主键去重)

网络分区模拟(Redis 短暂不可达)

  • 操作 :使用 iptables 阻断 Debezium 到 Redis 的连接 15 秒
  • ACTIVE 节点暂停消费,恢复后继续
  • 结果:
    • Debezium 日志报 RedisConnectionException,进入重试
    • 连接恢复后自动续传,未丢数据
    • 锁未被抢占(因未超 TTL)

Redis 宕机

  • 操作:停止 Redis 服务
  • 结果:
    • 所有 Debezium 实例无法读写 offset/schema,暂停消费
    • Redis 恢复后,ACTIVE 实例自动重连并继续
    • 数据最终一致,但存在中断窗口

该高可用方案在绝大多数生产场景下可靠有效 ,利用 Redis 统一实现 协调、存储、传输 ,架构简洁;Redis 本身也需高可用,否则成为单点故障;不适用于多 ACTIVE 并行处理场景(如分库分表);锁 TTL 需根据网络稳定性权衡(太短易误切,太长恢复慢)。

相关推荐
GateWorld4 小时前
跨时钟域同步(CDC)握手协议
fpga开发·cdc·asic·跨时钟域同步·握手协议
愤怒的苹果ext10 天前
Flink CDC MySQL同步到Elasticsearch
mysql·elasticsearch·flink·cdc·同步数据
逐云者12318 天前
构建高效任务中心:CDC 数据同步的工程实践与架构权衡
人工智能·架构·大模型·数据中心·cdc·任务中心·大数据同步
有想法的py工程师1 个月前
PostgreSQL × Debezium × Kafka 时间戳机制 [附对照表]
postgresql·kafka·debezium
最笨的羊羊1 个月前
Debezium日常分享系列之:认识debezium operator、debezium server yaml格式、部署debezium server
debezium日常分享系列·debezium·yaml·operator·debezium server
RestCloud1 个月前
SQL Server到Oracle:不同事务机制下的数据一致性挑战
数据库·oracle·sqlserver·etl·cdc·数据处理·数据传输
RestCloud1 个月前
实时 vs 批处理:ETL在混合架构下的实践
数据仓库·etl·cdc·数据处理·批处理·数据传输·数据同步
hong_fpgaer1 个月前
spyglass获得工程信号的路径
cdc·lint·spyglass
最笨的羊羊1 个月前
Flink CDC系列之:Kafka Debezium JSON 序列化器的实现DebeziumJsonSerializationSchema
kafka·debezium·schema·flink cdc系列·serialization·序列化器·debezium json