4、同步触发机制:定时批量同步、实时增量同步(CDC)、事件驱动同步、API调用同步、消息队列触发

同步触发机制,说白了就是「什么时候干活」的问题。

我见过不少团队,一上来就纠结用 CDC 还是用消息队列,结果连业务对数据时效性的要求都没搞清楚。你想想看,一个每天凌晨跑一次的财务汇总,跟一个需要秒级响应的库存变更,触发方式能一样吗?

这一章,我把五种主流触发机制掰开揉碎了讲。每种机制我都会结合项目里的真实场景,告诉你什么时候该用,什么时候千万别碰。

核心观点:没有最好的触发机制,只有最合适的。选型之前,先回答三个问题——数据能等多久?数据量有多大?下游系统扛得住吗?

主数据同步触发机制全景图 主数据同步触发机制全景图 同步触发机制 定时批量同步 实时增量(CDC) 事件驱动同步 API调用同步 消息队列 适用场景 批量报表、日结 非实时数据仓库 低频率变更 适用场景 数据库变更捕获 秒级实时同步 高一致性要求 适用场景 业务状态变更 工作流触发 解耦上下游 适用场景 按需查询同步 系统间接口 低频/小数据量 适用场景 削峰填谷 异步解耦 高吞吐 选型口诀 低频批量用定时,高频实时上CDC 事件驱动解耦好,API按需最灵活 消息队列扛峰值,组合使用更稳健

4.1 定时批量同步

这是最古老、也最稳妥的方式。说白了就是定个闹钟,到点干活。

我习惯用 Cron 表达式来定义调度周期。比如每天凌晨 2 点跑一次全量同步,或者每 15 分钟拉一次增量。

// 定时任务配置示例(Spring Boot + @Scheduled)
@Scheduled(cron = "0 0 2 * * ?")   // 每天凌晨2点执行全量同步
public void fullSync() {
    log.info("开始全量同步主数据...");
    // 1. 查询源系统全量数据
    // 2. 对比目标系统差异
    // 3. 执行 upsert 操作
    // 4. 记录同步日志
}

@Scheduled(cron = "0 */15 * * * ?") // 每15分钟执行增量同步
public void incrementalSync() {
    log.info("开始增量同步...");
    // 只同步上次同步时间戳之后变更的数据
}

我的经验:定时批量同步最适合「数据一致性要求不那么苛刻」的场景。比如客户主数据每天同步一次,财务数据每天凌晨跑批。我曾经在一个项目中,硬要用定时批量去做库存实时扣减,结果数据延迟导致超卖——嗯,那是选型失误的典型。

注意:定时批量同步有两个坑:一是大表全量扫描会锁表,建议分批读取;二是如果同步任务失败,要有重试和告警机制。我曾经因为没加重试,某个定时任务挂了三天没人发现,数据差了三天。

4.2 实时增量同步(CDC)

CDC(Change Data Capture),说白了就是「盯着数据库的变更日志干活」。源数据库里每发生一次 insert、update、delete,CDC 组件都能捕获到,然后实时推给目标系统。

我常用的 CDC 工具有 Debezium、Canal、Maxwell。底层原理都是读取数据库的 binlog(MySQL)或 WAL(PostgreSQL)。

// Debezium 连接器配置示例(JSON)
{
  "name": "customer-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": "customer_master",
    "database.include.list": "customer_db",
    "table.include.list": "customer_db.t_customer",
    "database.history.kafka.bootstrap.servers": "kafka:9092",
    "database.history.kafka.topic": "schema-changes.customer"
  }
}

CDC 的延迟通常在秒级甚至毫秒级。我见过一个电商项目,用 CDC 把订单变更实时同步到搜索引擎,用户下单后 3 秒内就能在搜索结果里看到。

关键点:CDC 不是银弹。它要求源数据库开启 binlog,会增加一定的 IO 开销。另外,如果源库做了主从切换或版本升级,CDC 连接器可能会断连,需要设计自动恢复机制。

4.3 事件驱动同步

事件驱动跟 CDC 有点像,但触发源不一样。CDC 盯着数据库日志,事件驱动盯着「业务事件」。

举个例子:当客户状态从「正常」变为「冻结」时,业务系统会发布一个 CustomerFrozenEvent。订阅了这个事件的同步服务收到后,立刻把冻结状态同步到所有下游系统。

// 事件驱动同步示例(伪代码)
public class CustomerEventConsumer {

    @EventListener
    public void handleCustomerFrozen(CustomerFrozenEvent event) {
        String customerId = event.getCustomerId();
        // 1. 查询客户最新数据
        Customer customer = customerService.getById(customerId);
        // 2. 同步到CRM系统
        crmClient.syncCustomerStatus(customer);
        // 3. 同步到订单系统
        orderClient.syncCustomerStatus(customer);
        // 4. 同步到风控系统
        riskClient.syncCustomerStatus(customer);
        log.info("客户 {} 冻结状态已同步", customerId);
    }
}

我的建议:事件驱动同步特别适合「业务语义明确」的场景。比如订单状态变更、客户审核通过、合同签署完成。我在做供应链主数据项目时,就用事件驱动来处理「供应商资质变更」——资质一更新,采购、财务、质检三个系统同时收到通知。

4.4 API 调用同步

API 调用同步是最直接的方式。源系统主动调用目标系统的接口,把数据传过去。

这种方式通常用于「按需同步」。比如用户在前台修改了个人信息,点击保存后,前端直接调用后端接口,后端同步更新主数据平台。

// API 调用同步示例(RESTful)
POST /api/v1/sync/customer
Content-Type: application/json

{
  "customerId": "CUST2024001",
  "customerName": "张三",
  "phone": "13800138000",
  "syncType": "UPDATE",
  "timestamp": "2024-01-15T10:30:00Z"
}

// 目标系统响应
HTTP/1.1 200 OK
{
  "code": "SUCCESS",
  "message": "同步成功",
  "syncId": "SYNC202401151030001"
}

避坑指南:API 同步最怕「超时」和「重试风暴」。我曾经遇到过一个场景:下游系统响应慢,上游不断重试,结果把下游打挂了。后来我加了「熔断」和「限流」——如果连续 5 次超时,直接熔断 30 秒,同时把请求转存到消息队列异步处理。

4.5 消息队列触发

消息队列触发,本质上是一种「异步解耦」的同步方式。源系统把数据变更发布到消息队列,目标系统从队列里消费。

常用的消息中间件有 Kafka、RocketMQ、RabbitMQ。我个人偏爱 Kafka,吞吐量高,持久化好,适合主数据同步这种「不能丢消息」的场景。

// 消息队列生产者(源系统)
public void publishCustomerChange(Customer customer) {
    String message = JSON.toJSONString(customer);
    kafkaTemplate.send("topic_customer_sync", customer.getId(), message);
    log.info("客户变更消息已发送: {}", customer.getId());
}

// 消息队列消费者(目标系统)
@KafkaListener(topics = "topic_customer_sync", groupId = "customer-sync-group")
public void consumeCustomerChange(String message) {
    Customer customer = JSON.parseObject(message, Customer.class);
    // 1. 幂等性检查(防止重复消费)
    if (idempotentService.isProcessed(customer.getId(), customer.getVersion())) {
        log.info("消息已处理,跳过: {}", customer.getId());
        return;
    }
    // 2. 执行同步
    targetSystemClient.syncCustomer(customer);
    // 3. 记录处理状态
    idempotentService.markProcessed(customer.getId(), customer.getVersion());
}

核心要点:消息队列触发的最大优势是「削峰填谷」。源系统每秒发 1000 条变更,下游系统每秒只能处理 200 条,没关系——消息队列帮你缓冲,下游慢慢消费。我做过一个项目,双十一期间订单主数据同步就是靠 Kafka 扛住的,峰值 5000 TPS,下游稳稳的。

4.6 五种机制对比与选型建议

说了这么多,到底怎么选?我整理了一张对比表,你直接拿去用。

触发机制 实时性 数据一致性 实现复杂度 典型延迟 推荐场景
定时批量同步 低 最终一致 低 分钟~小时级 日结报表、数据仓库
实时增量(CDC) 高 强一致 中 秒级 交易数据、库存同步
事件驱动同步 高 最终一致 中 秒级 业务状态变更、工作流
API调用同步 中 强一致 低 毫秒~秒级 按需查询、低频接口
消息队列触发 高 最终一致 高 毫秒~秒级 高吞吐、削峰填谷

我的选型习惯:先看业务对实时性的要求。能等 1 小时以上的,用定时批量;需要秒级响应的,上 CDC 或事件驱动;如果下游系统扛不住突发流量,中间加一层消息队列。记住一个原则——能用简单的就别搞复杂,定时批量能解决的问题,别硬上 CDC。

4.7 组合使用:混合触发策略

实际项目中,很少只用一种触发机制。我常用的组合方式有两种:

  • CDC + 消息队列:CDC 捕获变更,写入消息队列,下游系统从队列消费。既保证了实时性,又实现了削峰填谷。
  • 定时批量 + API 补偿:日常用定时批量同步,如果发现数据不一致,通过 API 触发单条数据的补偿同步。

举个例子。我在做一个大型零售集团的主数据平台时,用了三层触发:

  1. CDC 实时捕获门店、商品、供应商的变更,推送到 Kafka
  2. Kafka 消费者把数据同步到各个业务系统
  3. 每天凌晨跑一次定时全量对账,发现差异后通过 API 触发补偿同步

这套组合拳跑了两年,数据一致率保持在 99.99% 以上。

最后提醒一句:不管用哪种触发机制,一定要加「监控」和「告警」。同步延迟超过阈值要报警,同步失败要自动重试,重试还失败就要人工介入。我见过太多项目,同步机制设计得花里胡哨,结果出了问题没人知道——数据差了半个月才发现,那才叫灾难。