面试知识库
高 进阶

延迟任务方案对比#

一句话答案#

四种方案按推荐度:延迟 MQ(可靠简单)> Redis ZSET > 时间轮(进程内)> DB 轮询(兜底)。

核心要点

方案精度可靠性适用
延迟MQ秒~分高通用(推荐)
Redis ZSET秒级中量不大
时间轮ms级低进程内
DB轮询分钟高兜底方案

面试回答(2分钟版)

延迟任务有四种主流方案,按推荐程度排序。首选是延迟消息队列,比如RocketMQ原生支持延迟消息(4.x 是固定 18 个延迟级别,5.x 起支持按毫秒时间戳指定任意投递时间,默认精度 1s,最长时长有上限、可配置),可靠性高、使用简单,适合大多数业务场景比如订单超时取消。第二是Redis ZSet方案,把任务ID作为member、触发时间戳作为score存入ZSet,后台线程定时用ZRANGEBYSCORE取出到期任务执行,精度秒级,适合数据量不大的场景。第三是时间轮,比如Netty的HashedWheelTimer,精度可以到毫秒级,性能极高,但它是单机内存方案,进程挂了任务就丢了,适合进程内的超时管理比如连接超时检测。第四是数据库轮询,定时扫描DB中状态为待执行且到期的记录,精度分钟级,大表扫描开销大,但胜在可靠简单,适合作为兜底方案。实际项目中通常以延迟MQ为主,DB轮询做兜底补偿。

追问与易错

追问方向:

  • “Redis ZSET 延迟任务怎么实现?”→ 将任务 ID 作为 member、触发时间戳作为 score 存入 ZSet,后台线程定时用 ZRANGEBYSCORE 取出到期任务(score <= now)执行,执行后 ZREM 删除;注意多消费者时用 Lua 保证取出和删除的原子性
  • “时间轮优缺点?”→ 优点是 O(1) 插入和触发、精度可达毫秒级、内存紧凑;缺点是单机内存方案进程挂了任务全丢、大量空槽浪费遍历时间(层级时间轮可优化)
  • “为什么推荐 MQ?”→ RocketMQ 的延迟消息有持久化、多副本和重试保障,发消息时设延迟级别(4.x)或投递时间戳(5.x)即可,天然支持分布式多消费者,可靠性高于 Redis 和时间轮方案;注意 RabbitMQ 的 delayed-message-exchange 插件消息只存单节点单副本、不适合几十万以上延迟消息,且已于 2026-04 停止维护(RabbitMQ 4.3 移除了它依赖的 Mnesia),RabbitMQ 做延迟一般改用 TTL + 死信队列(有队头阻塞问题,不同延迟要分队列)

易错点:

  • ❌ 定时扫描 DB 性能好——大表扫描开销大
  • ❌ 时间轮适合所有场景——单机内存方案
  • ❌ 用了延迟 MQ 就不用兜底——消息投递后消费方仍可能失败,且超出最大延迟(RocketMQ 5.x 有上限,官方文档写默认 24h)的任务要分段投递或落库,DB 轮询补偿仍有必要
  • ❌ Redis ZSET 方案天然可靠——Redis 主从异步复制,主从切换可能丢任务,取出后执行前宕机也会丢,需要”处理中”队列或 DB 状态兜底