php redis实现秒杀
PHP + Redis 实现秒杀功能
秒杀场景的核心在于解决高并发下的超卖问题和系统性能瓶颈。Redis 的高性能和原子操作特性非常适合实现秒杀系统。
使用 Redis 原子操作防止超卖
利用 Redis 的 DECR 或 INCRBY 命令实现原子性库存扣减:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$productId = 'product_123';
$userId = uniqid(); // 模拟用户ID
// 预先设置库存
// $redis->set('stock_' . $productId, 100);
// 秒杀逻辑
$remaining = $redis->decr('stock_' . $productId);
if ($remaining >= 0) {
// 扣减成功,记录用户购买信息
$redis->sAdd('success_users_' . $productId, $userId);
echo "秒杀成功";
} else {
// 库存不足,恢复库存
$redis->incr('stock_' . $productId);
echo "秒杀失败";
}
Lua 脚本保证操作原子性
更推荐使用 Lua 脚本保证多个操作的原子性:
$lua = <<<LUA
local stock_key = KEYS[1]
local user_key = KEYS[2]
local user_id = ARGV[1]
local stock = tonumber(redis.call('GET', stock_key))
if stock and stock > 0 then
redis.call('DECR', stock_key)
redis.call('SADD', user_key, user_id)
return 1
end
return 0
LUA;
$result = $redis->eval($lua, ['stock_' . $productId, 'success_users_' . $productId, $userId], 2);
使用 Redis 队列缓解压力
将请求先放入队列,异步处理:
// 用户请求入队
$redis->lPush('seckill_queue', json_encode(['user_id' => $userId, 'product_id' => $productId]));
// 后台 worker 处理
while(true) {
$task = $redis->rPop('seckill_queue');
if ($task) {
$data = json_decode($task, true);
// 处理秒杀逻辑
}
usleep(100000); // 0.1秒
}
限流措施
使用 Redis 实现简单限流:
$key = 'user_request_' . $userId;
$limit = 5; // 5次/秒
$current = $redis->incr($key);
if ($current == 1) {
$redis->expire($key, 1);
}
if ($current > $limit) {
echo "请求过于频繁";
exit;
}
数据预热与库存分离
提前将库存加载到 Redis:
// 从数据库加载库存到Redis
$dbStock = 100; // 从数据库查询
$redis->set('stock_' . $productId, $dbStock);
注意事项
- Redis 需要配置持久化,避免重启导致数据丢失
- 考虑使用 Redis 集群提高可用性
- 最终一致性:异步同步到数据库
- 前端需要配合进行防重复提交处理
- 监控 Redis 性能指标,及时扩容
以上方案结合了 Redis 的原子操作、队列和限流机制,能有效应对秒杀场景的高并发挑战。实际应用中可根据业务需求调整细节。







