Redis 秒杀系统设计
决策
- 预期并发量
- 可接受的复杂度
- 一致性要求(是否允许短暂不一致)
- 团队维护能力
下面是一个完整的秒杀系统时序图(使用 Mermaid 语法),涵盖了从客户端发起请求到最终成功或失败的整个流程,并包含限流、防重、Redis 预减库存、异步消息队列、数据库最终扣减以及客户端轮询等关键步骤。
时序图关键点说明
- 限流防刷:Nginx 层做 IP 限流,业务层可进一步做用户级限流(例如使用令牌桶或漏桶)。
- 防重复秒杀:用户秒杀成功后,在 Redis 中记录
user:goodsId:userId,有效期设为一个活动周期,防止同一用户重复请求。 - Redis 预减库存:使用 Lua 脚本保证原子性(先
get判断 >0 再decr),避免超卖。这一步是系统的核心瓶颈保护点,支撑高并发。 - 异步消息队列:预减成功后立即返回“排队中”,秒杀请求异步化,不再同步等待数据库操作。
- 客户端轮询:用户端通过轮询结果接口获取最终状态,避免 WebSocket 长连接带来的资源消耗。
- 消费者数据库操作:
- 使用
update goods set stock = stock - 1 where goods_id = ? and stock > 0作为最后防线,确保数据库不会出现负库存。 - 订单表可建
(user_id, goods_id)唯一索引,防止 MQ 重复消费导致重复下单。
- 使用
- 补偿机制:若数据库更新失败(比如库存被别的消费者提前扣完),消费者需将 Redis 预减的库存加回,保证 Redis 与数据库最终一致。
- 超时处理:客户端轮询超过一定时间(如 30 秒)仍未生成订单,可提示失败。后台也可设置定时任务清理长时间未完成的“排队中”状态。
可选的简化与优化
- 去掉 MQ:如果并发不是极高(如 < 5k QPS),可以在 Redis 预减后直接同步写数据库,但需做好数据库连接池和限流。
- 轮询改进:可使用 Redis 的
list或简单标记,轮询时直接查 Redis 中的状态(如order:status:userId:goodsId),减少数据库压力。 - 本地缓存标记:在秒杀服务内存中缓存“已售罄”标志,提前拦截请求。
如果需要更详细的文字解释或某个环节的具体代码实现(比如 Lua 脚本、幂等处理),请告诉我。