Redis Stream秒殺系統(tǒng)實現(xiàn)
?? 案例背景
雙11秒殺活動:某電商平臺推出1000臺特價iPhone,10萬用戶同時搶購。
??? 系統(tǒng)架構圖
用戶請求 → Lua腳本校驗 → Redis Stream隊列 → 異步處理 → 數(shù)據(jù)庫
?? 詳細步驟說明
步驟1:用戶點擊秒殺按鈕
// 前端調(diào)用
public Result seckillVoucher(Long voucherId) {
// 生成唯一訂單ID: 2024110500012345
long orderId = redisIdWorker.nextId("order");
// 執(zhí)行Lua腳本進行原子操作
Long result = stringRedisTemplate.execute(
SECKILL_SCRIPT,
Collections.emptyList(),
"1001", // 優(yōu)惠券ID (iPhone特價券)
"12345", // 用戶ID
"2024110500012345" // 訂單ID
);
// 立即返回結果給用戶
if(result != 0){
return Result.fail(result==1 ? "庫存不足":"不能重復下單");
}
return Result.ok(2024110500012345L);
}?? Lua腳本詳解 (SECKILL_SCRIPT)
-- 參數(shù):優(yōu)惠券ID、用戶ID、訂單ID
local voucherId = ARGV[1]
local userId = ARGV[2]
local orderId = ARGV[3]
-- 構建Redis Key
local stockKey = 'seckill:stock:' .. voucherId
local orderKey = 'seckill:order:' .. voucherId
-- 1. 判斷庫存是否充足
local stock = redis.call('get', stockKey)
if tonumber(stock) <= 0 then
return 1 -- 庫存不足
end
-- 2. 判斷用戶是否已經(jīng)下單 (set集合)
if redis.call('sismember', orderKey, userId) == 1 then
return 2 -- 不能重復下單
end
-- 3. 扣減庫存
redis.call('decr', stockKey)
-- 4. 記錄用戶購買記錄
redis.call('sadd', orderKey, userId)
-- 5. 發(fā)送消息到Stream隊列
redis.call('xadd', 'stream.order', '*',
'voucherId', voucherId,
'userId', userId,
'orderId', orderId
)
return 0 -- 成功?? 實際場景演示
場景1:用戶A成功秒殺
時間線:
10:00:00.000 - 用戶A點擊秒殺按鈕
10:00:00.050 - Lua腳本執(zhí)行:
? 庫存檢查: 庫存1000 > 0
? 重復檢查: 用戶A未購買
? 庫存-1 → 999
? 記錄用戶A到已購集合
? 發(fā)送消息到stream.order
10:00:00.100 - 返回訂單ID: 2024110500012345
10:00:00.150 - 異步線程處理訂單入庫
10:00:01.000 - 訂單創(chuàng)建完成場景2:用戶B重復秒殺
時間線:
10:00:00.200 - 用戶B點擊秒殺按鈕
10:00:00.250 - Lua腳本執(zhí)行:
? 庫存檢查: 庫存999 > 0
? 重復檢查: 用戶B已在已購集合中
→ 返回2 (不能重復下單)
10:00:00.300 - 前端顯示:"不能重復下單"場景3:第1001個用戶秒殺
時間線:
10:00:05.000 - 用戶Z點擊秒殺按鈕
10:00:05.050 - Lua腳本執(zhí)行:
? 庫存檢查: 庫存0 <= 0
→ 返回1 (庫存不足)
10:00:05.100 - 前端顯示:"庫存不足"?? 異步訂單處理流程
正常處理流程
// VoucherOrderHandler - 訂單處理線程
while(true){
// 從消息隊列讀取訂單
List<MapRecord<String, Object, Object>> list = stringRedisTemplate
.opsForStream()
.read(
Consumer.from("g1", "c1"), // 消費者組g1,消費者c1
StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),
StreamOffset.create("stream.order", ReadOffset.lastConsumed()) // 讀取新消息
);
if(!list.isEmpty()){
MapRecord<String, Object, Object> record = list.get(0);
Map<Object, Object> values = record.getValue();
// 構建訂單對象
VoucherOrder order = new VoucherOrder();
order.setId(Long.parseLong((String)values.get("orderId")));
order.setUserId(Long.parseLong((String)values.get("userId")));
order.setVoucherId(Long.parseLong((String)values.get("voucherId")));
// 保存到數(shù)據(jù)庫
voucherOrderService.save(order);
// 確認消息已處理
stringRedisTemplate.opsForStream()
.acknowledge("stream.order", "g1", record.getId());
log.info("訂單處理成功: {}", order.getId());
}
}異常處理流程
private void handlePendingList() {
while(true){
try {
// 讀取未確認的消息 (處理異常情況)
List<MapRecord<String, Object, Object>> list = stringRedisTemplate
.opsForStream()
.read(
Consumer.from("g1", "c1"),
StreamReadOptions.empty().count(1),
StreamOffset.create("stream.order", ReadOffset.from("0")) // 從pending-list讀取
);
if(list.isEmpty()) break; // 沒有異常消息
MapRecord<String, Object, Object> record = list.get(0);
// 重新處理訂單...
createVoucherOrder(voucherOrder);
// 確認消息
stringRedisTemplate.opsForStream()
.acknowledge("stream.order", "g1", record.getId());
} catch (Exception e) {
// 處理失敗,等待后重試
Thread.sleep(20);
}
}
}?? 實戰(zhàn)場景模擬
模擬10萬并發(fā)秒殺
// 模擬10萬用戶同時秒殺
for(int i = 1; i <= 100000; i++){
new Thread(() -> {
Result result = seckillVoucher(1001L); // iPhone秒殺
if(result.getCode() == 200){
System.out.println("用戶" + Thread.currentThread().getId() + "秒殺成功");
} else {
System.out.println("用戶" + Thread.currentThread().getId() + "秒殺失敗");
}
}).start();
}執(zhí)行結果:
用戶12345: 秒殺成功
用戶12346: 秒殺成功
...
用戶11344: 秒殺成功
用戶11345: 庫存不足
用戶11346: 庫存不足
...
用戶100000: 庫存不足
?? Redis數(shù)據(jù)狀態(tài)變化
秒殺開始前
seckill:stock:1001: "1000" # 庫存1000 seckill:order:1001: [] # 空集合 stream.order: [] # 空消息隊列
秒殺過程中
seckill:stock:1001: "500" # 庫存剩余500 seckill:order:1001: ["12345", "12346", ...] # 500個用戶ID stream.order: [消息1, 消息2, ...] # 500條待處理消息
秒殺結束后
seckill:stock:1001: "0" # 庫存為0 seckill:order:1001: [1000個用戶ID] # 1000個購買用戶 stream.order: [] # 消息全部處理完成
?? 核心優(yōu)勢總結
- 高性能: Lua腳本原子操作,毫秒級響應
- 高并發(fā): 異步處理,支持10萬+ QPS
- 數(shù)據(jù)一致性: 庫存不會超賣
- 可靠性: 消息隊列確保訂單不丟失
- 用戶體驗: 立即返回結果,無需等待
?? 擴展思考
問題: 如果異步處理訂單時數(shù)據(jù)庫掛了怎么辦?
答案: 消息會留在pending-list中,等數(shù)據(jù)庫恢復后自動重試。
問題: 如何防止惡意用戶刷 單?
答案: 在Lua腳本中加入頻率限制,如:redis.call('incr', 'user:limit:'..userId)
到此這篇關于Redis Stream秒殺系統(tǒng)實現(xiàn)的文章就介紹到這了,更多相關Redis Stream秒殺內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
redis底層數(shù)據(jù)結構之ziplist實現(xiàn)詳解
這篇文章主要為大家介紹了redis底層數(shù)據(jù)結構之ziplist實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12
Redis基于Session實現(xiàn)分布式登錄的示例代碼
本文主要介紹了Redis基于Session實現(xiàn)分布式登錄的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2025-08-08

