秒殺活動中,某商品庫存100件,瞬時萬級并發請求搶購。如何保證庫存扣減的準確性和高性能?如何避免超賣?
場景挑戰:
? 瞬時萬級并發請求沖擊
? 僅100件商品庫存
? 要求:零超賣、高性能、強一致性
一、傳統方案的致命缺陷
1. 數據庫行鎖(悲觀鎖)
BEGIN;
SELECT quantity FROM stock WHERE item_id=1001 FOR UPDATE; -- 行級鎖
UPDATE stock SET quantity = quantity - 1 WHERE quantity > 0;
COMMIT;
缺陷:萬級并發下數據庫連接池耗盡,事務堆積導致雪崩
2. 應用層樂觀鎖
int version = select version from stock where item_id=1001;
int rows = update stock set quantity=quantity-1, version=version+1
where item_id=1001 and version=current_version;
if(rows == 0) throw new OptimisticLockException();
缺陷:高沖突場景導致大量請求失敗,用戶體驗災難
二、分布式緩存核心:Redis原子操作
方案1:Redis Lua腳本(原子性保障)
local key = KEYS[1] -- 庫存key: stock:1001
local quantity = tonumber(ARGV[1]) -- 扣減數量
-- 檢查庫存
local stock = tonumber(redis.call('GET', key))
if not stock or stock < quantity then
return 0 -- 庫存不足
end
-- 原子扣減
redis.call('DECRBY', key, quantity)
return 1 -- 成功
優勢:
? 單線程執行保證原子性
? 避免網絡往返開銷
? 性能可達10萬+ QPS
方案2:Redis事務+WATCH
with redis.pipeline() as pipe:
while True:
try:
pipe.watch('stock:1001') # 監控key
stock = int(pipe.get('stock:1001'))
if stock > 0:
pipe.multi() # 開啟事務
pipe.decr('stock:1001')
pipe.execute() # 提交
break
else:
pipe.unwatch()
return False
except WatchError: # 被其他修改打斷
continue
三、分層削峰架構設計
關鍵組件說明:
1. 網關層過濾
? 令牌桶限流:放行1%請求(10000并發→100請求/s)
? 惡意請求攔截:驗證碼、用戶黑名單
2. Redis集群部署
? 分片存儲:stock:{item_id}
分散到不同節點
? 持久化策略:AOF每秒刷盤 + RDB快照
? 集群規格:16分片/32G內存/50萬QPS
3. 異步訂單處理
? 扣減成功后發送MQ(RocketMQ/Kafka)
? 獨立服務消費MQ,創建訂單并更新數據庫
? 補償機制:失敗消息重試+人工干預
四、零超賣的終極防線
1. 預扣庫存(預占機制)
# 用戶維度預占記錄
HSET preempt:1001 user_001 1 EX 300 # 設置5分鐘過期
2. 庫存版本號控制
// Redis中存儲結構
stock:1001 = {
"quantity": 100,
"version": 16777216 // 24位版本號
}
// Lua腳本版本校驗
local currentVer = redis.call('HGET', KEYS[1], 'version')
if currentVer ~= ARGV[1] then
return 0 -- 版本沖突
end
3. 最終一致性校驗
-- 定時對賬任務(每5分鐘)
SELECT
redis_stock,
db_stock + unpaid_count
FROM
(SELECT quantity AS redis_stock FROM redis_stocks) r
JOIN
(SELECT stock AS db_stock,
COUNT(expired_order) AS unpaid_count
FROM orders
WHERE status='unpaid') d
ON r.redis_stock != d.db_stock;
五、性能壓測數據對比
方案 | 吞吐量(QPS) | 平均延遲(ms) | 超賣概率 |
數據庫行鎖 | 1,200 | 850 | 0% |
應用層樂觀鎖 | 8,500 | 110 | 0% |
Redis Lua原子操作 | 185,000 | 2.1 | 0% |
Redis集群分片 | 520,000+ | 0.8 | 0% |
壓測環境:8臺32核服務器/Redis 6.2集群/萬兆網絡
六、容災與降級策略
1. Redis宕機應急方案
? 熔斷降級:切換本地Guava緩存(設置極小庫存)
? 限流保護:立即啟用令牌桶限流(1請求/秒)
2. 數據庫保護措施
? 線程池隔離:訂單服務獨立線程池
? 隊列緩沖:Disruptor內存隊列暫存請求
? 拒絕策略:超過容量直接返回“活動太火爆”
3. 監控體系# Redis關鍵指標
# Redis關鍵指標
redis-cli info stats | grep instantaneous_ops
redis-cli latency monitor
# 數據庫監控
Percona Monitoring -> InnoDB Row Lock Time
七、前沿技術演進方向
1. 分布式事務優化
@TwoPhaseBusinessAction(name = "deductStock")
boolean prepareDeduct(BusinessActionContext ctx,
@BusinessActionContextParameter("itemId") long itemId);
- 阿里Seata TCC模式:Try階段預占庫存
2. RDMA網絡加速
? 基于RoCEv2的Redis網絡層改造
? 延遲從0.8ms降至0.1ms
3. 持久內存應用
? Intel Optane PMem存儲庫存數據
? 重啟后數據不丟失,恢復時間<50ms
結語
在高并發秒殺場景下,通過Redis原子操作+異步訂單+分層限流
的三重架構,實現了百萬QPS下零超賣的工業級解決方案。
關鍵認知:庫存扣減的本質是分布式系統狀態同步問題。當性能與精確性不可兼得時,通過預占機制和最終一致性校驗,在業務容忍的時間窗口內達成平衡,這正是分布式系統設計的藝術所在。