TP6.0用Redis List实现伪延时队列,本质是通过key过期触发任务入队+BLPOP阻塞消费,适合精度容忍秒级误差、QPS不高且轻量落地的场景。

TP6.0 用 Redis List 做简易延时队列,本质是“伪延迟”——它不依赖时间戳排序,而是靠 key 过期 + BLPOP 阻塞监听 实现任务延后投递,适合对精度要求不高、希望轻量落地的场景(比如订单创建后 15 分钟未支付,触发取消逻辑)。
为什么选 List 而不是 ZSet?
ZSet 更精准但需轮询或定时扫描;List 方案更轻:无需维护 score、无需额外哈希表存详情、无并发删任务的竞争问题。核心靠 Redis 的 EXPIRE + 过期事件通知(或主动轮询) 触发入队,再用阻塞式消费保证及时性。
- 适用场景:延迟精度容忍秒级误差(如 ±3~5 秒)、QPS 不高、不想引入额外组件(如 RabbitMQ)或复杂调度器
- 限制:不支持按时间动态排序;不能中途修改延迟时间;无法精确取消某条待执行任务(只能删整个 key)
- 关键前提:Redis 必须开启
notify-keyspace-events Ex(用于监听过期事件),或退而采用“定时扫 key”兜底
核心流程:三步走
不是直接把消息塞进 List 等着取,而是分阶段:准备 → 延迟触发 → 消费
-
准备阶段:把原始任务数据(如订单ID、操作类型)序列化为 JSON,存入一个临时 key,例如
delay:order_cancel:10086 -
延迟触发阶段:对该 key 设置过期时间(如 900 秒),同时注册过期监听(或由后台 Job 每 2 秒 scan 匹配
delay:*:*的过期 key);key 过期后,自动将该任务 push 到目标队列 list,如queue:cancel_order -
消费阶段:Worker 使用
BLPOP queue:cancel_order 0阻塞监听,一旦有任务进入,立即取出、反序列化、执行业务逻辑(如查订单状态、调库存回滚接口)
TP6 中实际落地要点
ThinkPHP6 自带 Redis 扩展,无需额外装包,但需确保配置正确:
- 在
config/redis.php中启用prefix和select,避免 key 冲突 - 写一个
DelayListQueue类封装上述三步,例如:->delayPut('order_cancel', $data, 900)→ 存临时 key + 设过期->triggerExpiredKeys()→ 主动 scan + move(适配无 notify 的环境)->listen('queue:cancel_order', [$handler])→ 封装 BLPOP 循环 - 监听过期事件需配合 Redis 配置与 PHP 进程常驻(建议用 think-swoole 或 supervisor 管理 worker 进程)
- 注意异常处理:push 失败要重试;消费失败需记录日志+可选重入延迟队列(加 retry 字段 + 次数限制)
重试机制怎么加?
原生 List 不自带重试,但可在任务体里嵌套控制字段:
- 任务数据结构示例:
{"order_id":10086,"retry_count":0,"max_retry":3,"next_delay":60} - 消费失败时,判断
retry_count ,若满足则重新调用 <code>delayPut,延迟时间为next_delay * (2^retry_count)(指数退避) - 每次重试前
retry_count++,并更新到新任务中,避免无限循环 - 最终失败的任务可落库标记,供人工干预或补偿作业扫描











