定时任务频率与延迟任务 / 轮询选型指南典型场景借阅逾期ZSET Scheduled、购书未支付取消RabbitMQ 延迟消息、预约爽约待扩展写在前面业务里常遇到一类问题某件事要在未来某个时间点发生订单超时取消、借阅到期标记逾期、预约结束自动完成等。实现方式通常两类模式代表延迟任务事件发生时预约一次执行RabbitMQx-delayed-message、Redis 键 TTL 回调等轮询定时醒来查「有没有到期的」Scheduled SQL / Redis ZSET本文说明Scheduled 任务频率怎么设、两种模式怎么选并结合本项目已有实现举例。一、Scheduled 任务频率怎么设1.1 核心原则频率 ≤ 业务容忍误差先问晚处理一会儿业务能不能接受业务场景典型容忍误差建议频率借阅逾期标记15 分钟poll-interval-ms: 60000300000未支付订单取消秒分钟级延迟 MQ 精确触发或 30s1min 轮询借阅申请超时未取APPLIED→CANCELLED小时级每小时或每 30 分钟预约时段结束 / 爽约分钟级15 分钟报表、对账小时天级每小时 / 每天凌晨缓存预热、统计不敏感530 分钟不要把频率设得比业务需要的精度更高越快 空跑越多、DB/Redis 压力越大收益却有限。1.2 用「批次 频率」估算能否消化峰值本项目借阅逾期配置application.yamlbookstore:borrow:overdue:enabled:truepoll-interval-ms:60000# 每次 tick 间隔 60 秒fixedDelaybatch-size:100# Lua / SQL 每批最多 100 条单次 tick 的处理能力当前实现每 tick 只 Lua pop 一次吞吐 ≈ batchSize / pollInterval 默认100 条 / 60 秒 ≈ 100 条/分钟若某时刻500 条同时到期仅靠 Lua pop约 5 个 tick ≈5 分钟处理完fixedDelay 从上次结束计时对账 SQLreconcileOverdueBatch可在同一 tick 内多批消化部分 backlog估算公式pollInterval ≤ 可接受最大延迟 / ceil(峰值条数 / batchSize)示例要求 2 分钟内处理完 500 条、batchSize100→ 至少需要 5 批 →pollInterval约 ≤ 24 秒或同一 tick 内循环 pop 直到为空。1.3 为什么需要 batchSize批次大小BorrowOverdueScheduler与 Lua 脚本borrow_due_pop.lua均使用batch-size限制单次处理量原因说明Redis Lua 阻塞Lua 执行期间 Redis 单线程阻塞一次 pop 上万条会卡顿定时任务耗时N 条 N 次 DB UPDATE过长会拖住 Scheduler 线程削峰大量due_at相同时分批摊平失败隔离单批失败影响面小1.4 fixedDelay vs fixedRate类型行为推荐fixedDelay上次执行结束后再等待 N 毫秒✅ 本项目BorrowOverdueScheduler使用fixedRate每 N 毫秒触发一次不管上次是否跑完易任务堆叠慎用Scheduled(fixedDelayString${bookstore.borrow.overdue.poll-interval-ms:60000})publicvoidprocessOverdue(){...}若单次处理 30 秒、fixedDelay60s → 实际约每 90 秒一轮fixedRate60s 则可能重叠执行。1.5 环境与参数建议环境poll-interval-msbatch-size说明本地联调1000010 秒50100配合手动改due_at到过去生产 / 答辩60000300000100借阅量小15 分钟足够压测 / 大量到期30000200或 tick 内循环 pop其他实践参数放application.yaml支持环境变量覆盖提供enabled: false开关本项目已支持多个 Scheduler错峰cron避免整点同时打 DB日志记录每批marked/reconciled条数与耗时便于调参二、延迟任务 vs 轮询两种触发模型轮询定时醒来查有没有到期有则处理延迟任务事件发生预约 N 秒后执行到点触发一次2.1 对比总表维度延迟任务MQ / 延迟队列轮询Scheduled / ZSET Scheduler触发精度单事件较精确秒级取决于间隔常见分钟级延迟时长短延迟分钟合适长延迟30 天需评估 Broker长短均可只判断「是否到期」实现复杂度MQ、延迟插件、DLQ、消费幂等Scheduler SQL / Redis 即可空闲时资源无到期事件则不消费定时空跑ZSET 可减轻无效 SQL批量同时到期每单一条消息量大时消息多一次批量取天然适合改期续借难需取消/替换已发消息易改due_at ZADD 覆盖多实例Consumer 竞争 业务幂等Lua 原子 pop / 条件 UPDATE故障恢复死信队列 定时对账下次轮询 SQL 对账兜底2.2 决策树需要秒级精确触发 └─ 是 → 倾向延迟 MQ如未支付 15 分钟取消 延迟是「每单不同、且很长」如 30 天 └─ 是 → 倾向轮询 / ZSET慎用长延迟 MQ 大量订单会在同一时刻到期 └─ 是 → 倾向轮询 批量ZSET / 条件 UPDATE 到期后要触发很多下游通知、罚息、审计 └─ 是 → 延迟 MQ或「轮询发现 发 MQ 异步」 只是改个状态、量不大、要求简单 └─ 是 → Scheduled 条件 UPDATE 最省事 已有 MQ 且希望技术栈统一短延迟场景 └─ 是 → 可优先延迟 MQ三、本项目两个典型案例3.1 购书未支付取消 → 延迟 MQ ✅实现TradeMqProducerx-delayed-messageTradeOrderTimeoutConsumerbookstore:trade:unpaid-cancel-delay-minutes:15# unpaid-cancel-delay-ms: 60000 # 联调可改为 1 分钟流程创建订单 → 发 delay15min 的消息 → 到点 Consumer 取消订单为何选延迟任务因素说明延迟固定、较短15 分钟Broker 友好需要相对精确超时后应尽快释放库存每单一条消息量级可控基础设施已有与秒杀 MQ 同一套 RabbitMQ3.2 借阅逾期 → ZSET 轮询 ✅实现BorrowDueRedisServiceborrow_due_pop.luaBorrowOverdueSchedulerbookstore:borrow:overdue:enabled:truepoll-interval-ms:60000batch-size:100流程confirm → ZADD(due_at) Scheduler 每 60s → Lua 原子 pop → UPDATE OVERDUE └─ reconcileOverdueBatch SQL 对账兜底 return → ZREM为何选轮询 ZSET因素说明延迟长且每单不同如 30 天不宜每单占一条长延迟 MQ精度要求低分钟级标记 OVERDUE 即可可能批量到期同一批 confirm 的due_at接近续借改期ZADD 覆盖 score 即可多实例Lua 原子 pop 避免重复领任务若强行用延迟 MQ30 天 × 大量借阅 消息长期堆积、续借难撤消息、Broker 长延迟可靠性需单独验证。3.3 对比小结模块选型延迟特征精度购书超时取消延迟 MQ固定 15 分钟较高借阅逾期ZSET 轮询每单不同天级分钟级借阅 APPLIED 超时未取规划定时 SQL固定 48 小时小时级预约爽约规划定时 SQL时段相关分钟级结论两个模块选型不同是按场景匹配不是随意混搭。四、BorrowOverdueScheduler 逻辑速览便于理解「轮询 ZSET」如何与频率配置配合Scheduled(fixedDelayString${bookstore.borrow.overdue.poll-interval-ms:60000})publicvoidprocessOverdue(){// 1. Lua原子弹出 score now 的 orderId最多 batchSize 条ListLongorderIdsborrowDueRedisService.popDueOrderIds(nowEpoch,batchSize);// 2. 同步 DBBORROWED → OVERDUE条件 UPDATE幂等for(LongorderId:orderIds){borrowOrderRepository.updateToOverdue(orderId);}// 3. SQL 对账ZADD 失败 / 历史数据未入 ZSET 的补漏reconcileOverdueBatch(batchSize);}步骤作用Lua pop多实例下原子领任务避免重复读 ZSET同步 UPDATE不依赖 MQ借还闭环内完成状态变更reconcileRedis 与 MySQL 不一致时的兜底五、常见组合模式生产实践模式组成适用纯轮询Scheduled 条件 SQL小项目、只改状态纯延迟 MQ创建时发延迟消息短延迟、单事件、高精度ZSET 轮询 SQL 对账本项目借阅逾期长延迟、批量到期、要防多实例延迟 MQ 定时对账MQ 主路径 每日 SQL 补漏对可靠性要求高轮询 发 MQScheduler 只负责发现通知走 MQ状态同步与通知解耦六、选型速查表你的需求推荐15 分钟未支付取消延迟 MQ已实现30 天借阅逾期ZSET 轮询15 分钟已实现只展示「是否逾期」、不改库查询懒判断无需 Scheduler毕设求最简单定时 SQL 条件 UPDATE无需 ZSET到期要发短信 / 站内信在 OVERDUE 后同步或异步发 MQ规划 topicborrow.overdue秒级触发 已有 MQ短延迟用 MQ长延迟用轮询七、面试 / 答辩参考话术定时任务频率不是越快越好而是看业务容忍误差。我们用fixedDelay避免任务堆叠用batchSize控制单次 Redis Lua 和 DB 压力。延迟任务适合「事件发生时就知道何时触发、延迟较短」的场景比如购书 15 分钟未支付取消用 RabbitMQ 延迟交换机精确触发。轮询适合「到期时间分散、延迟很长、可能批量到期」的场景比如借阅 30 天逾期用 Redis ZSET 索引 Scheduler 每分钟 pop同步 UPDATE MySQL并用 SQL 对账兜底。两者可以组合主路径 对账保证最终一致。