thinkphp 的 think-queue 原生不支持任务级优先级抢占,所有任务均进入同一 redis list(如 queue:default),采用纯 fifo 的 lpush/brpop 机制,不解析 payload 中的 priority 字段,也不支持 zset 排序或多队列自动降级调度。

ThinkPHP 的 think-queue 不支持任务级优先级抢占
直接调用 \think\Queue::push() 发送的任务,无论传什么参数,最终都进同一个 Redis list 键(如 queue:default),底层用的是 LPUSH/BRPOP,纯 FIFO。它不解析 payload 里的 priority 字段,也不支持 score 排序或多队列自动降级调度。所谓“在配置里写 'queue' => 'high' 再用 --queue=high,default”,只是让所有任务塞进 queue:high 这个键,消费时仍按 BRPOP 顺序取,不是优先级抢占。
Redis ZSET 实现动态优先级需手动控制投递与消费
ZSET 是最贴近语义的结构:score 越小,越先被 ZRANGEBYSCORE 取到。但必须绕过 think-queue 默认流程:
- 推任务不用
push(),改用Redis::zAdd('queue:pri', $score, json_encode($payload)),其中$score = $priority * 1000000 - time()可保证同优先级下早入队者先执行 - 消费端不能跑
php think queue:work,得写独立命令(如php think queue:pri-consume),循环执行zRange('queue:pri', 0, 0, ['WITHSCORES' => true])拿最小 score 任务 - 取到后立刻
zRem('queue:pri', $payload)防重复;失败时可zAdd()回去并提高 score(比如 +1000)降低重试权重 - 注意:ZSET 没有原生阻塞命令,不能用
BRPOP那套,得靠sleep(1)或结合zCard()短轮询,否则 CPU 100%
多 Redis List + blpop 是更稳、更省资源的分级方案
比 ZSET 更贴合 ThinkPHP 原有习惯,也更适合生产环境:
- 预设三个键:
queue:high、queue:default、queue:low,生产者按业务类型显式推送到对应 list(如Redis::lPush('queue:high', $payload)) - 消费者脚本里用
blPop(['queue:high', 'queue:default', 'queue:low'], 1),它会按数组顺序检查——只要第一个非空就返回,空了才往下走,天然阻塞且零轮询 - 超时设为 1 秒(第二个参数),避免长期卡死;超时后手动 fallback 到下一队列,可防饿死
- 别试图用
onQueue('high')+ think-queue 自带 worker,它不会跨键调度,只会把所有任务塞进一个键然后照常 BRPOP
RabbitMQ 或 Beanstalkd 才真支持协议层优先级
如果已用 RabbitMQ 或 Beanstalkd,优先级是中间件原生能力,但必须两端对齐:
- RabbitMQ:声明队列时加
x-max-priority(如 10),发消息时在 AMQP 属性里设priority字段(值 ≤ 最大优先级),否则忽略;Laravel 默认不透出该字段,需自定义 connector 重写push() - Beanstalkd:
put($data, $priority, $delay, $ttr)中$priority越小越高(0 是最高),但 ThinkPHP 的 think-queue Beanstalkd 驱动默认没暴露该参数,得改源码或换 Enqueue 等更底层可控的库 - database/sync 驱动完全不支持优先级,加任何字段都会被丢弃
真正容易被忽略的点是:优先级是否生效,不取决于你写了多少 priority 字段,而取决于消费者拉取逻辑是否按 score / priority / 键顺序真实执行。很多线上问题,其实是消费端还在用 BRPOP 单键轮询,却以为自己启用了“高优队列”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











