秒杀活动如何设计_PHP高并发秒杀系统实现指南【教程】

2次阅读

php不适合直接处理高并发秒杀,应拆分“抢资格”与“扣库存”,用redis原子操作预减库存(decr+expire)、异步队列下单、前端限流及最终一致性对账补偿。

秒杀活动如何设计_PHP高并发秒杀系统实现指南【教程】

PHP 本身不适合直接扛高并发秒杀请求,硬上会垮在 Web 层或数据库层;真正能稳住的,是把「抢资格」和「扣库存」拆开,用缓存 + 队列 + 原子操作做隔离。

为什么不能直接用 UPDATE product SET stock = stock - 1 WHERE id = 1 AND stock > 0

这条 sql 看似安全,但在 PHP-FPM + mysql 默认配置下,高并发时会出现大量行锁等待、死锁或超卖:

  • MySQL 的 WHERE stock > 0 条件检查和 SET stock = stock - 1 不是原子的(除非加 select ... for UPDATE,但会严重阻塞)
  • PHP 进程间无法共享锁状态,靠数据库锁又扛不住 5000+ QPS
  • 每次请求都查库 → Redis 缓存击穿 + MySQL 连接池打满 → 502/504 大量出现

用 Redis 实现库存预减:DECR + EXPIRE 组合必须配对

核心逻辑不是“查库存再扣”,而是“先扣再验证”,靠 Redis 原子性兜底:

  • 秒杀开始前,用 SET stock:1001 100 初始化库存,再 EXPIRE stock:1001 3600 防久未清理
  • 用户请求进来,直接 DECR stock:1001 —— 返回值 ≥ 0 才代表抢到了资格
  • 返回负数?说明库存已扣完,立刻返回「已售罄」,不走后续流程
  • 注意:DECR 对不存在的 key 会先初始化为 0 再减,所以必须提前 SET,不能依赖首次 DECR

异步下单队列:别让 PHP 等 MySQL INSERT 完成才返回

抢到资格 ≠ 下单成功,要解耦「资格确认」和「订单落库」:

立即学习PHP免费学习笔记(深入)”;

  • 抢成功后,只往 Redis List(如 queue:order)里 LPUSH 一条轻量 jsON:{"uid":123,"pid":1001,"ts":171xxxxxx}
  • 用独立的 PHP CLI 脚本(或 swoole Worker)持续 BRPOP queue:order 1 消费,批量写 MySQL 订单表
  • 避免每个请求都触发一次 INSERT INTO orders —— 单机 MySQL 插入极限约 1k~2k TPS,而 Redis List 可轻松扛 10w+ QPS
  • 消费脚本需做幂等:用 uid+pid+ts 拼唯一订单号,插入前先 SELECT 判重

前端和网关层必须做的三件事

再好的后端也架不住恶意刷请求,防护得从前端就开始:

  • 按钮点击后立即置灰 + 显示「提交中」,防止用户连点(JS 层拦截)
  • nginx 层用 limit_req 限制单 IP 每秒最多 5 次 /seckill 接口请求
  • 接口必须带时效性签名,例如 sign=md5(uid+pid+salt+timestamp),且 timestamp 与服务端时间差超过 2 秒即拒掉 —— 防工具脚本重放

真正难的不是代码怎么写,而是库存数字在 Redis、MySQL、消息队列、本地缓存之间如何保持最终一致;一旦某个环节失败(比如 Redis 扣减成功但消息丢失),就得靠定时任务对账补偿 —— 这部分往往被教程跳过,但线上出问题,90% 出在这儿。

text=ZqhQzanResources