# PHP中怎么利用Redis实现电商秒杀功能 ## 一、秒杀场景的技术挑战 电商秒杀活动(如双11、618)通常面临三大技术难题: 1. **瞬时高并发**:数万QPS的访问压力 2. **库存超卖**:共享资源竞争导致数据不一致 3. **系统过载**:数据库可能成为性能瓶颈 传统MySQL方案难以应对,而Redis凭借以下特性成为理想解决方案: - 单机10万+ QPS性能 - 原子操作保证数据一致性 - 丰富的数据结构支持 ## 二、核心实现方案 ### 1. 系统架构设计
用户请求 → 负载均衡 → PHP服务层 → Redis集群 → MySQL(异步同步)
### 2. Redis关键数据结构 ```php // 商品库存预加载 $redis->set('seckill:sku:123:stock', 100); // 已购用户记录 $redis->sAdd('seckill:sku:123:users', $userID);
function handleSeckill($userId, $skuId) { $redis = new Redis(); // 1. 校验用户是否重复购买 if ($redis->sIsMember("seckill:{$skuId}:users", $userId)) { return ['code' => 400, 'msg' => '已参与活动']; } // 2. 原子性扣减库存 $remaining = $redis->decr("seckill:{$skuId}:stock"); if ($remaining < 0) { $redis->incr("seckill:{$skuId}:stock"); // 回滚 return ['code' => 400, 'msg' => '已售罄']; } // 3. 记录购买行为 $redis->sAdd("seckill:{$skuId}:users", $userId); // 4. 异步处理订单(RabbitMQ) sendToQueue(['user_id' => $userId, 'sku_id' => $skuId]); return ['code' => 200, 'msg' => '秒杀成功']; }
令牌桶限流:
$rateLimiter = new TokenBucket(1000); // 每秒1000个令牌 if (!$rateLimiter->consume(1)) { header('HTTP/1.1 429 Too Many Requests'); exit; }
队列缓冲:使用Redis List作为排队系统
$redis->lPush('seckill:queue', json_encode(['user_id' => $userId, 'sku_id' => $skuId]));
// 将库存拆分为多个子库存 for ($i = 0; $i < 10; $i++) { $redis->set("seckill:{$skuId}:stock_{$i}", 10); } // 随机选择子库存扣减 $slot = mt_rand(0, 9); $redis->decr("seckill:{$skuId}:stock_{$slot}");
// IP限频(60秒内最多5次) $key = "seckill:ip:" . md5($_SERVER['REMOTE_ADDR']); if ($redis->incr($key) > 5 && $redis->ttl($key) > 0) { return ['code' => 403, 'msg' => '操作过于频繁']; } $redis->expire($key, 60);
库存回滚机制:
try { // 业务代码... } catch (Exception $e) { $redis->incr("seckill:{$skuId}:stock"); $redis->sRem("seckill:{$skuId}:users", $userId); }
Redis集群方案:
方案 | QPS | 成功率 |
---|---|---|
纯MySQL | 1,200 | 68% |
Redis+队列 | 24,000 | 99.5% |
Redis+Lua脚本 | 38,000 | 99.9% |
<?php class SeckillService { private $redis; public function __construct() { $this->redis = new Redis(); $this->redis->connect('127.0.0.1', 6379); } public function process(Request $request) { // 参数校验、限流等... $luaScript = <<<LUA local stockKey = KEYS[1] local userKey = KEYS[2] local userId = ARGV[1] if redis.call('SISMEMBER', userKey, userId) == 1 then return 2 end local stock = redis.call('DECR', stockKey) if stock < 0 then return 0 end redis.call('SADD', userKey, userId) return 1 LUA; $result = $this->redis->eval( $luaScript, [ "seckill:{$skuId}:stock", "seckill:{$skuId}:users", $userId ], 2 ); // 处理Lua脚本返回结果... } }
必做项:
推荐项:
扩展方向:
”`
实际部署时建议使用:Redis 6.2+版本、PHPRedis扩展7.0+、连接池配置(如Swoole)
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。