2026/2/8 17:25:41
网站建设
项目流程
保定哪个公司做网站好,家电网站设计,模块化网站开发,工会网站建设方案Spring Boot + Redis + Lua 打造高并发秒杀系统
—— 防超卖 / 库存预热 / 流量削峰 / 生产级设计完整版
在高并发业务场景中,秒杀系统几乎是所有后端工程师绕不开的一道“必修课”。 它同时考验: 高并发处理能力 分布式一致性设计 Redis 使用深度 系统稳定性与可恢复性 工…Spring Boot + Redis + Lua 打造高并发秒杀系统—— 防超卖 / 库存预热 / 流量削峰 / 生产级设计完整版在高并发业务场景中,秒杀系统几乎是所有后端工程师绕不开的一道“必修课”。 它同时考验:高并发处理能力分布式一致性设计Redis 使用深度系统稳定性与可恢复性工程经验与架构能力一个合格的秒杀系统,至少要解决四个核心问题:不能超卖:库存必须绝对准确不能击穿系统:瞬时流量必须被削峰不能重复下单:要有强幂等性不能丢订单:Redis、MQ、DB 之间要最终一致本文将基于一个完整的:Spring Boot + Redis + Lua + 消息队列 的秒杀系统方案从 0 到 1 设计一个生产级高并发秒杀系统,并且:保留所有可运行代码Lua 脚本原子扣减库存支持库存预热多级限流削峰异步下单可补偿、可对账、可降级一、系统整体架构设计基础架构如下这基本就是淘宝、京东、美团等互联网公司秒杀的真实架构缩影。二、系统设计的五大核心原则在开始写代码之前,必须先统一系统设计目标:1. 原子性库存扣减必须是不可分割的操作:判断库存 → 扣减库存 → 记录用户 → 生成订单信息 必须要么全部成功,要么全部失败这正是 Lua 脚本存在的意义。2. 幂等性同一个用户:不能下两次订单MQ 重试不能造成多单网络抖动不能造成重复写库你现在已经在 Redis 层用:SISMEMBER userSetKey userId做了第一层幂等,这是非常专业的设计。后面我们会补上:数据库唯一索引MQ 消费端幂等形成双重保险。3. 削峰填谷秒杀流量是典型的:1 秒内爆发 → 10 万 QPS但数据库最多只能抗几百 QPS。所以必须:请求 → Redis 快速失败 成功请求 → MQ 排队 MQ → 平滑写 DBRedis + MQ 是整个系统“抗洪水”的核心。4. 最终一致性Redis 扣库存成功 ≠ 订单一定成功。可能发生:MQ 发送失败MQ 消费失败DB 插入失败因此我们必须引入:冻结库存模型失败补偿机制对账机制后面会专门一节讲。5. 降级与熔断任何系统都会出问题:故障应对Redis 慢返回“排队中”Redis 挂秒杀入口关闭MQ 挂只做 Redis 预扣,不下单DB 慢订单排队秒杀系统不是“永远成功”,而是永远可控失败。三、技术选型说明组件作用Spring Boot服务骨架Redis高性能缓存 + 原子操作Lua原子库存扣减Redisson分布式锁 / 限流MQ(RabbitMQ/Kafka)削峰 + 异步MySQL最终订单存储Prometheus + Grafana监控告警四、Redis + Lua 原子库存模型设计(防超卖核心)在秒杀系统中,库存扣减是整个系统最敏感、最关键的一环。 如果这里出问题,后面所有架构设计全部失去意义。为什么必须用 Lua?因为:Redis 单条命令是原子的但多条命令组合不是原子的Lua 脚本在 Redis 内部执行,天生保证原子性即:Lua = 分布式系统里的“原子事务”4.1 Redis Key 设计在你的方案中,Key 设计非常规范:Key含义seckill:stock:{productId}商品库存seckill:users:{productId}已成功秒杀用户集合seckill:order:{orderId}订单信息seckill:activity:{productId}活动信息seckill:sold:{productId}已售数量(统计用)这是一个非常“工程化”的 Key 设计,既方便业务,又方便运维。4.2 原始 Lua 脚本你设计的 Lua 脚本已经是生产级写法,这里完整保留:-- KEYS[1]: 商品库存key -- KEYS[2]: 秒杀成功用户集合key -- ARGV[1]: 用户ID -- ARGV[2]: 购买数量 -- ARGV[3]: 活动开始时间 -- ARGV[4]: 活动结束时间 -- 1. 参数校验 if #KEYS ~= 2 or #ARGV ~= 4 then return -1 -- 参数错误 end local stockKey = KEYS[1] local userSetKey = KEYS[2] local userId = ARGV[1] local quantity = tonumber(ARGV[2]) local startTime = tonumber(ARGV[3]) local endTime = tonumber(ARGV[4]) -- 2. 检查活动时间 local currentTime = redis.call('TIME')[1] if currentTime startTime then return -2 -- 活动未开始 end if currentTime endTime then return -3 -- 活动已结束 end -- 3. 检查用户是否重复购买 local exists = redis.call('SISMEMBER', userSetKey, userId) if exists == 1 then return -4 -- 重复购买 end -- 4. 获取库存并检查 local stock = redis.call('GET', stockKey) if not stock then return -5 -- 商品不存在 end stock = tonumber(stock) if stock = 0 then return -6 -- 库存不足 end if stock quantity then return -7 -- 库存不足 end -- 5. 扣减库存 redis.call('DECRBY', stockKey, quantity) -- 6. 添加用户到已购买集合 redis.call('SADD', userSetKey, userId) -- 7. 设置用户过期时间(活动结束后1小时) redis.call('EXPIREAT', userSetKey, endTime + 3600) -- 8. 记录订单信息到Hash local orderId = string.format("ORDER:%s:%s", userId, currentTime) local orderKey