Redis Stream秒杀系统实现

 更新时间:2025年11月28日 09:56:59   作者:⑩-  
文章浏览阅读345次,点赞7次,收藏3次。高性能: Lua脚本原子操作,毫秒级响应高并发: 异步处理,支持10万+ QPS数据一致性: 库存不会超卖可靠性: 消息队列确保订单不丢失用户体验: 立即返回结果,无需等待。

📚 案例背景

双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) <= 0 then
    return 1  -- 库存不足
end
-- 2. 判断用户是否已经下单 (set集合)
if redis.call('sismember', orderKey, userId) == 1 then
    return 2  -- 不能重复下单
end
-- 3. 扣减库存
redis.call('decr', stockKey)
-- 4. 记录用户购买记录
redis.call('sadd', orderKey, userId)
-- 5. 发送消息到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脚本执行:
                ✓ 库存检查: 库存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 <= 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")));
        // 保存到数据库
        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);
        }
    }
}

🎪 实战场景模拟

模拟10万并发秒杀

// 模拟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();
}

执行结果:

用户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秒杀内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • redis过期回调功能实现示例

    redis过期回调功能实现示例

    Redis提供了一种过期回调的机制,可以在某个键过期时触发一个回调函数,本文就来介绍一下redis过期回调功能实现示例,感兴趣的可以了解一下
    2023-09-09
  • redis常用命令、常见错误、配置技巧等分享

    redis常用命令、常见错误、配置技巧等分享

    这篇文章主要介绍了redis常用命令、常见错误、配置技巧等分享,本文分享了12条redis知识,需要的朋友可以参考下
    2015-02-02
  • 关于Redis你可能不了解的一些事

    关于Redis你可能不了解的一些事

    这篇文章主要给大家介绍了关于Redis你可能不了解的一些事,对大家学习或者使用Redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-04-04
  • IDEA初次连接Redis配置的实现

    IDEA初次连接Redis配置的实现

    本文主要介绍了IDEA初次连接Redis配置的实现,文中通过图文步骤介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-12-12
  • Redis常用的数据结构及实际应用场景

    Redis常用的数据结构及实际应用场景

    本文介绍了Redis中常用的数据结构,包括字符串、列表、集合、哈希表、有序集合和Bitmap,并详细说明了它们在各种场景下的使用,需要的朋友可以参考下
    2024-05-05
  • Redis基于Session实现分布式登录的示例代码

    Redis基于Session实现分布式登录的示例代码

    本文主要介绍了Redis基于Session实现分布式登录的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-08-08
  • redis sentinel监控高可用集群实现的配置步骤

    redis sentinel监控高可用集群实现的配置步骤

    这篇文章主要介绍了redis sentinel监控高可用集群实现的配置步骤,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • Redis存储断点续传文件状态的最佳实践

    Redis存储断点续传文件状态的最佳实践

    在断点续传系统中,如何高效地存储和更新文件上传状态是关键,得益于 Redis 高效的内存操作和多种数据结构的支持,它非常适合用于存储上传过程中的临时状态信息,下面,我们将探讨如何利用 Redis 实现文件上传状态的存储,需要的朋友可以参考下
    2024-12-12
  • dubbo服务使用redis注册中心的系列异常解决

    dubbo服务使用redis注册中心的系列异常解决

    这篇文章主要为大家介绍了dubbo服务在使用redis注册中心遇到的一系列异常的解决,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03
  • Redis的4种缓存模式分享

    Redis的4种缓存模式分享

    这篇文章主要介绍了Redis的4种缓存模式分享,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下
    2022-07-07

最新评论