PHP秒杀场景需用Redis队列解耦写请求:LPUSH入队、pconnect持久连接、LPOP消费、DECRBY或Lua原子扣库存,并用Supervisor守护进程。
PHP项目在秒杀、抢券或活动爆发时,数据库瞬间被大量写请求击穿,连接数爆满、响应超时、数据错乱,必须把耗时操作从主流程中剥离出来。
将用户请求存入Redis队列
用
把请求参数压入列表,不直接操作MySQL。这一步操作起来很简单,直接把用户ID、商品ID、时间戳打包成JSON字符串塞进去就行。
方法一:基础写入
创建Redis实例→调用
→执行
。注意不要用
覆盖,必须用
保证FIFO顺序。
方法二:带错误兜底的写入
包裹
,捕获
;失败时记录日志并降级为本地文件队列(如
),避免请求丢失。
【关键前提:Redis服务必须已启动且端口可通,否则整个写入链路中断】
立即学习
“
PHP免费学习笔记(深入)
”;
后台守护进程消费队列
用
逐条取出请求,处理完再入库。不能用
阻塞式读取——它会让进程挂起,无法响应信号退出,线上难以管理。
第一步:建立持久连接
比
省去每次TCP握手开销,尤其适合长周期运行的守护进程。
第二步:循环拉取+防空转
用
包裹
;若返回
,立即
(10毫秒),避免CPU空转打满。这比
更精细,也比无休止轮询更省资源。
第三步:解析与落库
对
做
;校验字段完整性;拼接
;执行
后检查
是否为1。失败则记录错误详情到
,跳过当前条目继续下一条。
防止库存超卖的原子扣减
下单前必须校验并扣减库存,但普通SELECT+UPDATE在并发下会失效。必须用Redis原生命令保障原子性。
方法一:DECRBY配合GET判断
先
;再
。这三行代码必须连续执行,中间不能插入其他逻辑。
方法二:Lua脚本强一致性控制
写一段Lua脚本:
;用
调用。Lua在Redis单线程内执行,彻底规避竞态条件。
配置Supervisor守护消费者进程
让
常驻后台、崩溃自动重启、日志自动轮转。
编辑
,填入以下内容:
执行
。此时进程已纳入系统级监管,
能查到正在运行的PHP进程。
LPUSH$redis->connect('127.0.0.1', 6379)$redis->LPUSH('order_queue', json_encode($payload))SETLPUSHtry/catchRedisExceptionfile_put_contents('/tmp/queue_fallback.log', ...)LPOPBRPOP$redis = new Redis(); $redis->pconnect('127.0.0.1', 6379, 30);connect()while (true)$value = $redis->LPOP('order_queue')falseusleep(10000)sleep(1)$valuejson_decode($value, true)INSERT INTO orders (...) VALUES (...)mysqli_query()mysqli_affected_rows()/var/log/queue_consumer.err$remain = $redis->DECRBY('stock:1001', 1)if ($remain INCRBY('stock:1001', 1); return '库存不足'; }if redis.call("GET", KEYS[1]) >= ARGV[1] then redis.call("DECRBY", KEYS[1], ARGV[1]) return 1 else return 0 end$redis->eval($script, ['stock:1001'], [1])php /path/to/consumer.php/etc/supervisor/conf.d/redis-consumer.conf[program:redis-consumer]
command=php /var/www/app/queue/consumer.php
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/supervisor/redis-consumer.logsupervisorctl reread && supervisorctl update && supervisorctl start redis-consumerps aux | grep consumer