Skip to content

定时任务重复执行 ​

问题现象 ​

同一时间收到两条通知,批处理重复入账,重启后任务再次执行;每台实例的日志都只显示一次。

适用环境 ​

Spring @Scheduled、多实例应用、MySQL 8;任务可能调用外部通知接口。

本文是可复用的排障示例,不代表某次个人生产事故。命令中的名称、账号和数据均为示例,执行前请替换为已确认的目标。

复现或诊断步骤 ​

  1. 日志同时记录实例 ID、任务名、业务时段和执行 ID。
  2. 检查部署副本数、重复 Bean、多个调度器及任务重叠。
  3. 将任务触发与实际副作用拆开,检查重试和人工补发是否共享幂等键。

原因分析 ​

@Scheduled 只负责单个应用进程中的调度,不提供集群唯一执行。即使有分布式锁,执行后响应丢失或锁过期仍可能导致重试,因此业务副作用还需要幂等。

修复示例 ​

sql
-- 在独立迁移中创建,发布前保存数据库备份
CREATE TABLE task_slot (
  task_name VARCHAR(80) NOT NULL,
  slot_key VARCHAR(80) NOT NULL,
  status VARCHAR(20) NOT NULL,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
    ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (task_name, slot_key)
);
-- 尝试占用:重复键表示已有执行记录,需按状态处理
INSERT INTO task_slot(task_name, slot_key, status)
VALUES ('daily_summary', '2026-10-10T08:00+08:00', 'RUNNING');

唯一键保护同一业务时段,执行记录应有成功、失败和超时恢复策略。不能把 RUNNING 永久当作成功;外部发送尽量携带相同幂等键,可靠通知可使用 outbox。变更前先设计失败重试和租约续期,回滚应用时保留执行记录防止重复。

验证方法 ​

启动两个实例同时触发同一 slot,确认只产生一个业务结果。模拟进程在发送前后崩溃,检查恢复策略及重复发送边界;人工补发也必须可追溯。

常见误区 ​

把 cron 秒数错开只降低冲突概率。分布式锁不是 exactly-once 承诺,数据库唯一键也无法独自消除没有幂等能力的外部系统重复副作用。

官方参考链接 ​

相关分类:Java 后端 · 全部笔记