php队列关键在状态闭环、失败兜底和资源可控:redis需设brpop超时并捕获异常,mysql队列表须用select for update skip locked,rabbitmq必须手动ack+死信队列,所有消费逻辑须幂等且入队前先落库。

PHP队列不是“加个扩展就能跑”,关键在状态闭环、失败兜底和资源可控。选错方案或漏掉一个细节,轻则任务重复执行,重则数据库锁死、Redis内存爆满。
Redis队列必须设BRPOP超时并捕获异常
很多人写消费者直接用brPop(['queue'], 0),以为“一直等”很省心,结果Redis临时断连或重启,进程就卡死不动,supervisor也拉不起来。
-
brPop第二个参数别写0,建议设为10(秒),超时后主动sleep(1)再重试,避免CPU空转 - 必须用
try/catch包裹,捕获RedisException,否则异常一出进程静默退出 - 消息体统一用
json_encode(),别塞PHP对象——反序列化时类不存在会触发Fatal error - 生产者用
lPush()没问题,但要支持优先级就得换zAdd()+ 时间戳,BRPOP就不适用了
MySQL队列表必须用SELECT FOR UPDATE SKIP LOCKED
这是MySQL 8.0+才支持的语法,老版本只能靠乐观锁兜底。不用它,多个消费者同时查status = 0,会拿到同一条记录,任务就被重复执行。
- SQL必须包在事务里:
BEGIN→SELECT ... FOR UPDATE SKIP LOCKED→UPDATE ... SET status = 1→COMMIT -
UPDATE影响行数为0,说明已被抢走,应立即continue下一轮,别sleep等 -
status字段必须建索引,否则10万条数据时轮询延迟可达秒级 -
payload别存大字段(如HTML模板),BLOB类型拖慢整表SELECT效率
RabbitMQ消费者必须手动ACK且配置死信队列
没手动ack,消息就一直卡在Ready状态,越积越多;没配死信队列,失败三次后消息直接丢弃,连排查依据都没了。
- 消费者回调函数里,业务逻辑成功后必须调用
$msg->ack(),失败则$msg->reject(true)让其重回队列 - 声明队列时加
arguments:设置x-dead-letter-exchange和x-dead-letter-routing-key,把反复失败的消息导流到DLX - 消息发布时务必设
delivery_mode = 2,否则RabbitMQ重启后消息全丢 - 连接不能复用跨请求——每个消费者脚本应独立建
AMQPStreamConnection,别试图全局单例
所有队列消费逻辑必须幂等且入队前先落库
网络抖动、超时重试、消费者崩溃重启,都可能导致同一条消息被多次投递。靠“队列不重复”来保证幂等是幻觉。
- 入队前必须先在数据库插入
status = 'pending'记录,哪怕队列丢失也能靠ID重放 - 消费时第一步不是执行业务,而是
UPDATE jobs SET status = 'processing' WHERE id = ? AND status = 'pending',只有一行被更新才算抢到任务 - 发短信这类操作,防重不能只查“有没有发过”,得靠唯一索引约束
(phone, content_hash, created_at > NOW() - INTERVAL 1 HOUR) - 数据库连接必须复用——常驻消费者脚本里别每次循环都
new PDO(),否则很快打满max_connections
最易被忽略的是“状态更新时机”:不是消费开始时改processing,也不是结束时改done,而是在确认抢到任务后立刻改,且必须是原子UPDATE。这一步漏掉,整个队列系统就失去可靠性根基。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











