Skip to content

数据同步任务执行到一半失败了,如何保证数据的一致性? ​

  • 标签:后端、场景题

🏛 业务架构师 ​

同步任务执行到一半失败——设计目标是**"任务可重跑"(rerunnable)**:任何一个失败点重启,要么接着跑,要么从头跑,结果都正确。我从设计原则、失败处理、兜底三层答。

核心设计原则

  1. 幂等写入:任务每一步的写入可重复执行无副作用——数据库用 upsert(ON DUPLICATE KEY UPDATE)、按业务主键去重,重跑 N 遍结果一致。没有幂等,一切断点恢复都是空谈
  2. 断点续传(checkpoint):进度持久化(游标/批次号落库),重启后从断点继续——适合长任务、大批量
  3. 小事务分批提交:每批一个事务(如 1000 条),绝不整任务一个大事务——大事务回滚慢、锁持有久、undo log 爆炸;批粒度让"一半失败"的损失天然只有半批
  4. 失败隔离:单条数据的问题(格式、约束冲突)不能拖死整批——坏数据进错误表,主流程继续,任务的完成度不被 1% 的脏数据绑架

失败后的处理策略(按任务性质选)

  • 可重跑任务(同步、刷新类):自动重试(退避 + 上限),从断点续跑
  • 部分成功任务(导入类):已完成部分保留,失败部分导出错误清单人工修复
  • 强原子任务(资金类初始化):全量回滚重来,或走人工核对流程

兜底与观测

  • 任务状态机:RUNNING / SUCCESS / FAILED / PARTIAL,进度百分比落库可查——运营要看得到任务状态,"黑盒跑批失败三天没人知道"是真实事故
  • 告警分级:重试 3 次失败 → 群告警;影响次日业务 → 电话
  • 对账闭环:任务级对账(源 vs 目标 count/sum),重跑后核对,差异清零才算恢复

收尾:我的验收标准一句话——任何同步任务,在任意一行之后被 kill -9,重启后数据最终正确。设计时按这个标准写测试用例,就杜绝了"跑一半"的恐惧。

🏗 系统架构师 ​

同步任务执行到一半失败,一致性问题的本质是断点状态的原子性与幂等性。我按设计原则和恢复机制讲。

1. 事务边界设计:任务分片 + 批次原子

  • 大任务先切分片(shard),每个分片内按批次(batch)推进,批次是事务与提交的最小单位
  • 每批处理完原子地完成两件事:写入目标数据 + 更新进度位点(断点续传的 checkpoint)。进度可以落同步任务表(shard_id、last_cursor、status),与数据写入同事务,保证"数据与进度一致"
  • 失败恢复从位点重放,绝不从头重跑全量

2. 幂等性:重放的 safety net 位点回退或消息重复不可避免,所有写操作幂等:

  • 数据写入:主键 upsert / ON DUPLICATE KEY UPDATE
  • 业务动作:唯一业务单号去重(处理记录表唯一索引)
  • 副作用补偿:发过通知的不再发(通知记录表)

3. 失败分类处理(不是所有失败都一样):

  • 瞬时失败(网络、锁超时):指数退避重试 N 次,批次内自愈
  • 数据失败(格式、约束冲突):该条隔离到错误表,批次跳过继续,不能让一条脏数据卡死整个任务
  • 系统失败(依赖宕机):任务挂起,依赖恢复后自动续跑 + 告警

4. 一致性校验与补偿闭环

  • 任务完成后对账:源与目标的分片 count/checksum 比对
  • 差异自动触发局部重放(按差异区间重跑),仍失败进人工队列
  • 关键场景配"最终一致检查点":例如订单同步以订单状态机为准,核对超时未同步的订单主动补拉

5. 调度层保障:任务实例故障由调度平台(如 XXL-Job 分片广播、K8s Job 重启)接管续跑;任务表用分布式锁防双实例并发跑同一分片。

一句话:分片+批次划小事务边界,位点与数据同事务提交,幂等支撑安全重放,对账兜底最终一致。

💻 全栈开发专家 ​

核心思路:把"执行一半"变成不可能出错的状态——分批 + 断点 + 幂等 + 对账,四层防护。

第一层:缩小事务边界

不要把整个任务包成一个巨型事务(长事务锁表、undo log 膨胀、可能超时回滚到怀疑人生)。拆成小批次,每批一个独立事务:

java
while (hasNext()) {
    List<Row> batch = fetchNext(1000);
    transactionTemplate.execute(st -> {
        writeBatch(batch);
        saveCheckpoint(batchId, batch.getLastCursor()); // 位点和数据同事务
        return null;
    });
}

关键点:check point 和数据同库同事务落盘——要么都成功要么都回滚,重启后从断点继续,绝不重跑已成功批次。

第二层:幂等设计

重试难免重放,目标端写入必须幂等:主键 upsert、或唯一键 + INSERT IGNORE / 冲突更新。这样即使某批"实际成功但 ack 丢失导致重推",结果依然正确。

第三层:失败隔离

单批失败重试 N 次(指数退避)仍失败,跳过并记录异常表,任务继续跑完,最后汇总异常批人工或自动修复——比整体卡死好得多。不可跳过的场景就暂停任务,保留现场。

第四层:对账修复

任务级最终一致性靠对账:任务完成后比对源和目标的 count/sum/抽样明细,不一致的按主键重新推送。这是兜底,也是面试官最想听的"闭环"。

常见坑

  • 位点存 Redis 而不是和业务同事务的 DB:Redis 写成功业务回滚(或反过来)照样不一致,位点必须与数据同事务。
  • 半成品中间表方案:写临时表最后原子 rename,适合全量替换型任务。