Redis延迟队列核心是用ZSET存任务时间戳,配合常驻PHP进程轮询到期任务消费。1. ZADD以时间戳为score插入JSON任务;2. worker循环ZRANGEBYSCORE取≤当前时间任务,ZREM/ZPOPMIN安全移除并执行;3. 优化轮询:按最近任务动态sleep或用BRPOPLPUSH;4. 补充幂等ID、过期清理、Lua原子取删、监控等健壮性设计。

用 Redis 实现 PHP 延迟队列,核心思路是利用 有序集合(ZSET) 存储任务及其执行时间戳,再配合一个常驻进程(或定时脚本)轮询取出已到期的任务进行消费。它轻量、高效,适合中小规模场景,无需引入 RabbitMQ 或 Kafka 等重型组件。
1. 用 ZSET 存储延迟任务
ZSET 的 score 字段天然适合作为执行时间戳(单位:秒级或毫秒级),member 存任务内容(如 JSON 序列化的 job 数据)。例如:
- 插入一个 5 秒后执行的任务:
ZADD delay_queue 1717023605 {"job":"send_email","to":"user@example.com"} - PHP 中使用
$redis->zAdd('delay_queue', $timestamp, json_encode($job)) - 注意:时间戳必须是整数,推荐用
time() + $delay_seconds,避免本地时区偏差
2. 启动消费者监听到期任务
写一个长期运行的 PHP 脚本(如 worker.php),循环执行以下逻辑:
- 用
ZRANGEBYSCORE delay_queue -inf $now获取所有 score ≤ 当前时间的任务(即已到期) - 用
ZREM或ZPOPMIN(Redis 6.2+)安全移除已取任务,防止重复消费 - 对每个任务反序列化并执行业务逻辑(如发邮件、更新状态)
- 建议加 try-catch,失败任务可记录日志或转入重试队列,避免阻塞后续任务
3. 避免轮询空耗:合理控制间隔与阻塞
纯 sleep 轮询效率低,可用以下方式优化:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
立即学习“PHP免费学习笔记(深入)”;
- 每次查询后,若没拿到任务,计算下一个最近任务的延迟时间(
ZRANGE delay_queue 0 0 WITHSCORES),sleep 对应秒数 - 或使用
BRPOPLPUSH+ 辅助 list 的变体方案(需额外维护“就绪队列”),但复杂度上升 - 生产环境建议用
supervisord或systemd管理 worker 进程,确保崩溃后自动重启
4. 补充健壮性设计
真实项目中还需考虑这些细节:
- 幂等性:任务体中带上唯一 ID(如 UUID),执行前查 DB 或 Redis 判断是否已处理
-
任务过期清理:定期用
ZREMRANGEBYSCORE delay_queue -inf $cutoff删除超时未消费的陈旧任务(比如 24 小时前) - 多 Worker 协作:用 Lua 脚本封装“取-删-返回”原子操作,避免多个 worker 取到同一任务
- 监控指标:记录队列长度、平均延迟、失败率,便于及时发现堆积或异常
不复杂但容易忽略。关键不在代码多短,而在时间精度、原子性和失败兜底是否闭环。


















