Hyperf 协程 PHP 实战:高并发秒杀接口从 0 到 1 落地
秒杀是高并发场景的典型考题:防超卖、防重复下单、削峰、异步落库、协程高吞吐。本文基于 Hyperf + Swoole 协程 + Redis Lua + 异步队列,给出可直接上线的生产级实现。
一、方案选型与核心要点
1.1 为什么选 Hyperf
- 原生协程、连接池、异步任务,IO 密集型场景性能提升 5--10 倍
- 开箱即用:Redis/DB/ 队列 / 限流 / 注解,适合快速搭建高并发服务
- 同步写法、异步执行,无回调地狱,PHP 开发者低成本上手
1.2 秒杀核心设计(必看)
- Redis Lua 原子扣减:库存判断 + 扣减 + 去重单原子执行,杜绝超卖
- 一人一单:用户 ID 集合判重,防止重复抢购
- 异步队列削峰:Redis 预扣成功后,异步写 DB,接口秒返回
- 幂等 + 防刷:请求唯一标识、接口限流、IP / 用户维度风控
- 最终一致性:定时任务补偿库存、订单超时回补
二、环境准备
2.1 依赖安装
bash
运行
composer require hyperf/redis hyperf/async-queue hyperf/limiter hyperf/database
2.2 配置(config/autoload/)
- redis.php:连接池、超时、集群按需配置
- async_queue.php:驱动 Redis,并发数、重试次数
- databases.php:MySQL 订单 / 商品表
三、核心数据结构与初始化
3.1 Redis Key 设计
plaintext
seckill:stock:{product_id} # 商品库存
seckill:uid:{product_id} # 已购用户集合(去重)
seckill:token:{request_id} # 幂等令牌
3.2 库存初始化(命令 / 后台)
php
运行
// 商品ID=1001,库存=100
Redis::set('seckill:stock:1001', 100);
Redis::del('seckill:uid:1001');
四、Lua 原子脚本(防超卖核心)
文件:storage/lua/seckill_stock.lua
lua
-- KEYS[1]=库存key, KEYS[2]=用户集合key
-- ARGV[1]=用户ID, ARGV[2]=购买数量
local stock = redis.call('GET', KEYS[1])
if not stock or tonumber(stock) < tonumber(ARGV[2]) then
return -1 -- 库存不足
end
if redis.call('SISMEMBER', KEYS[2], ARGV[1]) == 1 then
return -2 -- 已购买
end
redis.call('DECRBY', KEYS[1], ARGV[2])
redis.call('SADD', KEYS[2], ARGV[1])
return 0 -- 成功
五、秒杀接口实现(控制器)
5.1 注解与依赖注入
php
运行
use Hyperf\Redis\Redis;
use Hyperf\AsyncQueue\Driver\DriverFactory;
use Hyperf\RateLimit\Annotation\RateLimit;
use App\Job\CreateSeckillOrderJob;
#[RateLimit(create: 100, consume: 100)] // 限流
public function seckill(int $product_id, int $user_id, string $request_id)
5.2 完整逻辑
php
运行
public function seckill(int $product_id, int $user_id, string $request_id)
{
// 1. 基础校验
if ($user_id <= 0 || $product_id <= 0 || empty($request_id)) {
return $this->fail('参数错误');
}
// 2. 幂等去重
if (Redis::exists("seckill:token:{$request_id}")) {
return $this->fail('重复请求');
}
Redis::setex("seckill:token:{$request_id}", 300, 1);
// 3. Lua 原子扣减
$lua = file_get_contents(BASE_PATH . '/storage/lua/seckill_stock.lua');
$keys = [
"seckill:stock:{$product_id}",
"seckill:uid:{$product_id}"
];
$argv = [$user_id, 1];
$res = Redis::eval($lua, 2, ...$keys, ...$argv);
// 4. 结果处理
if ($res === -1) return $this->fail('库存不足');
if ($res === -2) return $this->fail('已购买');
if ($res !== 0) return $this->fail('抢购失败');
// 5. 异步队列落库
$driver = DriverFactory::get('default');
$driver->push(new CreateSeckillOrderJob($user_id, $product_id, $request_id));
return $this->success('抢购成功,请在订单中查看');
}
六、异步队列:订单落库(防 DB 雪崩)
6.1 任务类:app/Job/CreateSeckillOrderJob.php
php
运行
use Hyperf\AsyncQueue\Job;
use Hyperf\DB\DB;
class CreateSeckillOrderJob extends Job
{
public $user_id;
public $product_id;
public $request_id;
public function __construct($user_id, $product_id, $request_id)
{
$this->user_id = $user_id;
$this->product_id = $product_id;
$this->request_id = $request_id;
}
public function handle()
{
// 幂等校验
if (DB::table('seckill_order')->where('request_id', $this->request_id)->exists()) {
return;
}
// 事务写订单 + 扣减DB库存
DB::beginTransaction();
try {
// 扣减DB库存
DB::table('product')->where('id', $this->product_id)->decrement('stock', 1);
// 创建订单
DB::table('seckill_order')->insert([
'user_id' => $this->user_id,
'product_id' => $this->product_id,
'request_id' => $this->request_id,
'status' => 1,
'created_at' => date('Y-m-d H:i:s')
]);
DB::commit();
} catch (\Throwable $e) {
DB::rollBack();
// 失败回补Redis
Redis::incr("seckill:stock:{$this->product_id}");
Redis::srem("seckill:uid:{$this->product_id}", $this->user_id);
throw $e;
}
}
}
七、生产级加固(必做)
7.1 限流防刷
- 注解 / 中间件:用户 / IP 维度限流
- 黑名单:恶意请求直接拦截
7.2 超时未支付回补
定时任务:15 分钟未支付,释放库存:
php
运行
// 取消订单
Redis::srem("seckill:uid:{$product_id}", $user_id);
Redis::incr("seckill:stock:{$product_id}", 1);
7.3 一致性补偿
定时任务对比 Redis 库存与 DB 库存,差异自动修复。
7.4 安全加固
- 请求鉴权、参数签名
- 商品上下线状态控制
- 前端按钮防抖 + 排队提示
八、压测与调优
8.1 压测指标
- 单机 QPS:5000--10000+(4 核 8G)
- 接口响应:<10ms
- 无超卖、无重复单
8.2 调优项
- Redis:关闭持久化 / 使用集群 / 开启 pipeline
- Hyperf:worker_num=CPU 核数 * 2,协程池大小合理
- 队列:提高并发,开启多消费
- DB:分库分表、订单表索引、读写分离
九、总结
这套方案用协程高吞吐 + Lua 原子性 + 异步削峰,完美解决秒杀三大痛点:
- 超卖:Lua 单原子扣减
- 慢响应:Redis 预扣 + 异步落库
- 雪崩:限流 + 队列 + 连接池
可直接用于电商大促、优惠券、抢票等场景,按需扩展分布式锁、Sentinel 熔断、Redis Stream 等即可支撑更大流量。