应将定时触发与重算执行彻底分离:定时器仅轻量投递任务到异步队列,重算逻辑由带超时、重试、降级、状态上报的消费者分片幂等地执行,并配套失败归档与进度可视化。

定时器里直接跑重算任务,风险很高——不是任务算不完,而是整个系统可能被拖垮。
比如用 Java 的 Timer 或 Spring 的 @Scheduled 做每日库存重算,一旦某次计算耗时飙升(查库慢、锁表、数据量突增),它会卡住整个定时线程,后续任务全堆积;更糟的是,如果这是单点部署,连下一轮调度都可能被跳过,业务闭环就断了。
真正稳妥的做法,是把“触发”和“执行”彻底分开:定时器只负责发号施令,重算逻辑交给异步队列慢慢干。
定时器只做轻量触发,不碰业务逻辑
它该做的事只有一件:生成任务并投递到队列。
比如每天凌晨2点,定时器执行:
// Spring 示例:只发消息,不计算
@Scheduled(cron = "0 0 0 2 * ?")
public void triggerInventoryRecalc() {
messageQueue.send(new RecalcJob("all_warehouses"));
}这行代码毫秒级完成,不查库、不加锁、不循环,哪怕队列暂时不可用,也能靠重试机制兜底。
重算任务必须支持分片与幂等
重算往往涉及大量数据,不能一股脑塞进一个任务。要拆成可并行、可追踪、可重入的子任务:
- 按仓库ID或SKU区间分片,每片单独入队
- 每个子任务带唯一 traceId 和版本戳,避免重复执行
- 执行前先检查“上次成功时间戳”,跳过已覆盖时段
例如:
-
RecalcJob{ warehouseId: "WH001", date: "2026-06-08", shard: "0/4" } -
RecalcJob{ warehouseId: "WH001", date: "2026-06-08", shard: "1/4" }
这样即使某片失败,只重试那一片,不影响全局。
异步消费者要自带保护机制
从队列拉取任务后,不能裸奔执行。每个任务单元需内置:
- 超时控制:用
Future.get(30, SECONDS)包裹核心计算,超时即中断 - 重试策略:失败时按指数退避重试(如 1s → 3s → 9s),最多3次
- 降级开关:当错误率超阈值,自动暂停该类任务,写告警日志
- 状态上报:执行完立刻更新数据库状态表,供监控大盘读取
这些不是锦上添花,而是防止一次异常扩散成雪崩。
补偿与可观测性不能少
异步链路天然有延迟和不确定性,所以必须补两块:
- 失败归档:所有重试仍失败的任务,转入“待人工干预队列”,附带原始参数、堆栈、输入快照
-
进度可视:提供
/recalc/status?date=2026-06-08接口,返回各分片的 success/failed/pending 数量
没有这两条,出了问题只能翻日志大海捞针。
不复杂但容易忽略。

















