如何设计一个秒杀场景?

如何设计一个秒杀场景?
北川秒杀场景的特点:高并发、低库存、短时间的爆发式访问。因此我们应该从以下方面考虑:
- 高并发处理:如何应对大量用户的同时访问
- 库存一致性:如何保证库存不会超卖和少买
- 用户体验:如何减少等待时间,避免页面崩溃
- 防刷机制:如何防止恶意用户利用脚本抢购商品
用户 → 前端限流 → CDN → 负载均衡(Nginx) → API网关 → 应用服务 → 缓存(Redis) → 消息队列 → 订单服务 → 数据库 |
1. 前端层:减少无效请求
前端是流量入口,需通过简单策略过滤大部分无效请求,降低后端压力。
- 按钮控制:
- 秒杀未开始时按钮置灰,禁止点击;
- 秒杀开始后,点击一次后立即置灰(防止用户重复点击),并显示 “处理中”。
- 限流与排队:
- 加入验证码(图形 / 滑块),增加请求成本(防脚本抢购);
- 前端排队动画(如 “您在第 XX 位”),降低用户焦虑,同时通过延迟提交分散流量。
- 静态资源优化:
- 秒杀页面(商品图片、描述)通过 CDN 分发,避免请求直达应用服务器。
2. 接入层:流量初次过滤
通过负载均衡和网关进一步拦截无效流量,保护后端服务。
- Nginx 负载均衡与限流:
- 用
limit_req模块对 IP 限流(如单 IP 每秒最多 5 次请求),超过直接返回 “繁忙”; - 配置
upstream将请求分发到多个应用实例,避免单实例过载。
- 用
- API 网关(如 Spring Cloud Gateway):
- 参数校验:检查用户登录态、商品 ID 合法性(非秒杀商品直接拒);
- 令牌桶限流:对秒杀接口设置全局 QPS 上限(如 1 万 QPS),超过进入排队队列;
- 黑名单:拦截频繁请求的恶意 IP / 用户。
3. 应用层:核心逻辑处理
应用服务需快速响应请求,核心逻辑聚焦 “库存预扣减” 和 “请求过滤”,避免直接操作数据库。
- 库存预热:
- 秒杀开始前(如提前 10 分钟),将商品库存从数据库加载到 Redis(用
String类型存储,如seckill:stock:1001 → 100),避免秒杀开始时数据库被查库请求压垮。
- 秒杀开始前(如提前 10 分钟),将商品库存从数据库加载到 Redis(用
- Redis 预扣减库存:
- 用户请求到达后,先通过 Redis 的
DECR命令预扣减库存(原子操作,保证并发安全); - 若
DECR后结果≥0:说明库存充足,进入后续流程; - 若
DECR后结果 <0:说明库存已空,立即返回 “秒杀失败”,并通过INCR回补库存(避免负数)。 - 示例:
SETEX seckill:stock:1001 3600 100(设置 1 小时过期,防止缓存雪崩)。
- 用户请求到达后,先通过 Redis 的
- 防重复下单:
- 用 Redis 的
Set记录已下单用户(如seckill:users:1001 → {user1, user2}),请求时先检查用户是否已在集合中,存在则拒。
- 用 Redis 的
4. 消息队列:削峰填谷 + 异步化
秒杀请求通过 Redis 预扣减后,需异步处理订单创建,避免同步操作阻塞应用服务。
- 引入消息队列(如 RabbitMQ/Kafka):
- 预扣库存成功后,将请求(用户 ID、商品 ID)封装成消息发送到队列,立即返回 “排队中” 给用户;
- 消息队列将瞬间高并发请求缓冲为匀速请求(如从 10 万 QPS 降为 1 万 QPS),保护下游订单服务和数据库。
- 消息可靠性:
- 用 “生产者确认机制”+“消息持久化” 防止消息丢失;
- 消费者开启手动 ACK,确保订单处理完成后再确认消息(避免重复处理)。
5. 订单层:最终库存扣减与订单创建
消息队列的消费者服务负责最终的库存扣减和订单生成,需保证数据一致性。
- 订单创建流程:
- 消费者从队列拉取消息,解析用户和商品信息;
- 数据库库存二次校验:执行
UPDATE语句时带条件(WHERE 商品ID=1001 AND 库存>0),确保 Redis 预扣减可能存在的误差(如缓存与数据库不一致); - 若更新成功:创建订单(订单表记录用户、商品、状态),返回 “秒杀成功”;
- 若更新失败:说明库存已被其他请求消耗,回补 Redis 库存(
INCR),返回 “秒杀失败”。
- 幂等性保证:
- 订单表用 “用户 ID + 商品 ID” 作为唯一索引,防止重复创建订单;
- 消息队列用 “消息 ID” 去重(如 Redis 记录已处理的消息 ID)。
评论
匿名评论隐私政策







