php 8.5 无原生队列支持,需依赖 redis 等外部存储实现;推荐用 redis stream(支持消费者组、ack、重发)而非 list,注意 php 8.5 的 json 默认返回数组、session 锁竞争及信号处理等兼容性细节。

PHP 8.5 里没有原生队列支持
PHP 本身不提供队列服务,php8.5 也不例外——它只是语言版本,不是运行时框架。所谓“PHP 队列”,实际是靠外部存储(如 Redis、Beanstalkd、MySQL) + 自定义逻辑或第三方库实现的。你看到的“PHP 队列教程”,99% 是在讲怎么用 PHP 去读写 Redis 的 list 或 stream,再配合进程管理跑消费者。
用 Redis list 实现最简异步队列(push / pop 模式)
这是新手最容易上手、也最容易出问题的方式。核心就是 LPUSH 入队、BRPOP 阻塞取任务,避免轮询浪费 CPU。
常见错误现象:
• 消费者脚本一运行就退出(没加死循环)
• 多个消费者同时拿到同一个任务(没做原子性处理)
• 任务失败后丢失(没做重试或失败队列)
实操建议:
立即学习“PHP免费学习笔记(深入)”;
-
BRPOP必须设超时(如0表示永久阻塞,30表示最多等 30 秒),否则信号中断时进程卡死 - 任务数据建议 JSON 编码后存入,避免特殊字符或嵌套结构出错:
json_encode(['job' => 'send_email', 'data' => ['to' => 'a@b.c']]) - 不要直接
DEL任务,先BRPOP取出,处理成功后再删;失败则LPUSH回原队列或进failed_jobslist - PHP 进程需手动管理:用
nohup php worker.php &启动,或配合supervisord,别依赖 Web 服务器触发
Redis Stream 更适合生产(PHP 8.5 原生支持更好)
Stream 是 Redis 5.0+ 引入的持久化日志结构,天然支持多消费者组、消息确认(ACK)、未处理消息重发,比 list 更可靠。PHP 8.5 的 ext-redis 已完整支持 XADD、XREADGROUP 等命令。
使用场景:
• 需要任务不丢(哪怕消费者崩溃)
• 多个服务共用同一队列但要各自消费全量(不同 consumer group)
• 要求查看积压任务数、重放某段历史
参数差异注意点:
- 创建消费者组必须指定初始 ID,常用
0(从头读)或$(只读新消息):XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream > - 处理完必须调用
XACK,否则该消息会一直留在PENDING列表,被XPENDING查到 -
XAUTOCLAIM可用于抢回超时未 ACK 的任务,但 PHP 官方扩展暂不直接封装,得用eval或原始命令 - Stream 不自动清理,要用
XTRIM或MAXLEN限制长度,否则内存涨满
PHP 8.5 下要注意的兼容性细节
PHP 8.5 对 Redis 扩展没做破坏性变更,但几个实际影响性能和行为的点容易被忽略:
- 默认
redis.session.locking_enabled = 1,若你用 Redis 存 session 又同时跑队列消费者,可能因锁竞争导致延迟;非必要可关掉 -
Redis::setOption(Redis::OPT_PREFIX, 'myapp:')会影响所有 key,包括队列 key,调试时容易漏看前缀导致查不到数据 - PHP 8.5 的
json_decode默认返回array(非stdClass),如果你旧代码假设对象访问($job->job),会报Trying to get property 'job' of non-object - 用
pcntl_fork()启动多 worker 时,PHP 8.5 要求显式调用pcntl_async_signals(true)才能正确响应SIGTERM,否则 kill -15 不生效
真正麻烦的从来不是“怎么把任务塞进 Redis”,而是消费者挂了谁来拉起来、失败任务怎么分类重试、监控怎么看积压、扩容时怎么平滑迁移消费者组——这些没法靠一个 XADD 解决。











