📚 案例背景
双11秒杀活动:某电商平台推出1000台特价iPhone,10万用户同时抢购。
🏗️ 系统架构图
用户请求 → Lua脚本校验 → Redis Stream队列 → 异步处理 → 数据库
📝 详细步骤说明
步骤1:用户点击秒杀按钮
// 前端调用
public Result seckillVoucher(Long voucherId) {
// 生成唯一订单ID: 2024110500012345
long orderId = redisIdWorker.nextId("order");
// 执行Lua脚本进行原子操作
Long result = stringRedisTemplate.execute(
SECKILL_SCRIPT,
Collections.emptyList(),
"1001", // 优惠券ID (iPhone特价券)
"12345", // 用户ID
"2024110500012345" // 订单ID
);
// 立即返回结果给用户
if(result != 0){
return Result.fail(result==1 ? "库存不足":"不能重复下单");
}
return Result.ok(2024110500012345L);
}
📜 Lua脚本详解 (SECKILL_SCRIPT)
-- 参数:优惠券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)
🎯 实际场景演示
场景1:用户A成功秒杀
时间线:
10:00:00.000 - 用户A点击秒杀按钮
10:00:00.050 - Lua脚本执行:
✓ 库存检查: 库存1000 > 0
✓ 重复检查: 用户A未购买
✓ 库存-1 → 999
✓ 记录用户A到已购集合
✓ 发送消息到stream.order
10:00:00.100 - 返回订单ID: 2024110500012345
10:00:00.150 - 异步线程处理订单入库
10:00:01.000 - 订单创建完成
场景2:用户B重复秒杀
时间线:
10:00:00.200 - 用户B点击秒杀按钮
10:00:00.250 - Lua脚本执行:
✓ 库存检查: 库存999 > 0
✗ 重复检查: 用户B已在已购集合中
→ 返回2 (不能重复下单)
10:00:00.300 - 前端显示:"不能重复下单"
场景3:第1001个用户秒杀
时间线:
10:00:05.000 - 用户Z点击秒杀按钮
10:00:05.050 - Lua脚本执行:
✗ 库存检查: 库存0
🔄 异步订单处理流程
正常处理流程
// VoucherOrderHandler - 订单处理线程
while(true){
// 从消息队列读取订单
List> 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 record = list.get(0);
Map
异常处理流程
private void handlePendingList() {
while(true){
try {
// 读取未确认的消息 (处理异常情况)
List> 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 record = list.get(0);
// 重新处理订单...
createVoucherOrder(voucherOrder);
// 确认消息
stringRedisTemplate.opsForStream()
.acknowledge("stream.order", "g1", record.getId());
} catch (Exception e) {
// 处理失败,等待后重试
Thread.sleep(20);
}
}
}
🎪 实战场景模拟
模拟10万并发秒杀
// 模拟10万用户同时秒杀
for(int i = 1; i {
Result result = seckillVoucher(1001L); // iPhone秒杀
if(result.getCode() == 200){
System.out.println("用户" + Thread.currentThread().getId() + "秒杀成功");
} else {
System.out.println("用户" + Thread.currentThread().getId() + "秒杀失败");
}
}).start();
}
执行结果:
用户12345: 秒杀成功
用户12346: 秒杀成功
…
用户11344: 秒杀成功
用户11345: 库存不足
用户11346: 库存不足
…
用户100000: 库存不足
🔧 Redis数据状态变化
秒杀开始前
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: [] # 消息全部处理完成
💡 核心优势总结
- 高性能: Lua脚本原子操作,毫秒级响应
- 高并发: 异步处理,支持10万+ QPS
- 数据一致性: 库存不会超卖
- 可靠性: 消息队列确保订单不丢失
- 用户体验: 立即返回结果,无需等待
🚀 扩展思考
问题: 如果异步处理订单时数据库挂了怎么办?
答案: 消息会留在pending-list中,等数据库恢复后自动重试。
问题: 如何防止恶意用户刷 单?
答案: 在Lua脚本中加入频率限制,如:redis.call('incr', 'user:limit:'..userId)
到此这篇关于Redis Stream秒杀系统实现的文章就介绍到这了,更多相关Redis Stream秒杀内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!
