# 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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。