面试基础---如何设计一个高并发的抢购系统(电商)
如何设计一个高并发的抢购系统?
在大型电商平台,抢购系统面临极端的并发挑战,例如“双十一”“秒杀”等场景。为了应对百万级 QPS,我们需要一个 高可用、高并发、低延迟、强一致性 的架构。以下是 实际项目经验,深入拆解设计方案。
1. 业务分析
🔹 场景描述
- 短时间内大量用户请求,流量峰值可达百万 QPS
- 库存有限,如 5000 件商品,需确保不超卖
- 避免数据库崩溃,减少高并发写入
🔹 主要挑战
✅ 如何应对高并发? 防止流量直接冲击数据库
✅ 如何保证库存一致性? 避免超卖和库存异常
✅ 如何提升查询效率? 提高秒杀成功率
✅ 如何削峰填谷? 平滑流量,保护核心系统
2. 架构设计
[用户] -> [CDN] -> [Nginx 网关] -> [限流 & 降级] -> [缓存层(Redis)] -> [消息队列(MQ)] -> [数据库]
组件 | 作用 |
---|---|
CDN + Nginx 负载均衡 | 缓存静态资源,减少请求压力 |
限流层 | 限制单个用户请求,防止恶意刷单 |
Redis 预加载库存 | 99% 读请求在 Redis 层完成 |
RabbitMQ / Kafka | 削峰填谷,异步订单处理 |
MySQL 分库分表 | 避免单表超大,影响性能 |
3. 技术方案
🔹 1. 限流:防止瞬时流量击穿
- Nginx 限流(基于 IP/用户)
- Redis 滑动窗口限流
- Java Guava RateLimiter(令牌桶算法)
Redis 限流 Lua 脚本
local key = "seckill_limit:" .. KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call("get", key) or "0")
if current + 1 > limit then
return 0 -- 限流
else
redis.call("incr", key)
redis.call("expire", key, 1)
return 1 -- 通过
end
🔹 2. 预减库存(缓存抗压)
思路:库存放入 Redis,抢购前先减 Redis,减少数据库查询压力。
Redis 预加载库存
SET seckill_stock_1001 5000
Redis 预减库存
Long stock = redisTemplate.decrement("seckill_stock_1001");
if (stock < 0) {
redisTemplate.increment("seckill_stock_1001"); // 回滚
return "库存不足";
}
库存回滚(订单超时未支付)
- 订单 10 分钟未支付,恢复库存
- RabbitMQ 死信队列触发库存回滚
🔹 3. 防止超卖(双层校验)
- Redis 预减库存
- MySQL 行锁最终确认
UPDATE product_stock SET stock = stock - 1 WHERE product_id = 1001 AND stock > 0;
使用 CAS
保证原子性
boolean success = redisTemplate.execute(new DefaultRedisScript<>(luaScript, Boolean.class),
Collections.singletonList("seckill_stock_1001"), "1");
if (!success) {
return "抢购失败";
}
🔹 4. 消息队列(异步削峰)
- 防止订单系统被高并发击穿
- RabbitMQ / Kafka 进行削峰,异步写订单
消息生产者
rabbitTemplate.convertAndSend("seckillQueue", new OrderMessage(userId, productId));
消息消费者
@RabbitListener(queues = "seckillQueue")
public void handleSeckillOrder(OrderMessage message) {
productStockService.reduceStock(message.getProductId());
orderService.createOrder(message.getUserId(), message.getProductId());
}
🔹 5. 分库分表(数据库优化)
方案 | 优化效果 |
---|---|
按时间分库 | order_202401 ,order_202402 |
按用户 ID 分表 | ShardingSphere 分片 |
索引优化 | product_id + order_time |
CREATE TABLE seckill_order (
id BIGINT PRIMARY KEY,
user_id BIGINT,
product_id BIGINT,
order_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_product_time (product_id, order_time)
);
4. 实战案例
📌 项目背景
某电商平台 秒杀 iPhone 15,5000 件库存,峰值 QPS 100w+,最终订单无超卖。
📌 方案落地
- Redis 预热库存:95% 请求在缓存层完成。
- 限流策略:滑动窗口 + 令牌桶防止刷单。
- 消息队列削峰:订单落库提升 5 倍吞吐量。
- 最终一致性:RabbitMQ + 事务确保库存和订单一致。
📌 结果
✅ 系统无宕机,QPS 达 100w+
✅ 库存精准控制,无超卖
✅ 订单延迟 <50ms,用户体验良好
5. 进一步优化
优化点 | 方案 |
---|---|
防止缓存穿透 | 布隆过滤器 |
降低 MySQL 压力 | Binlog + ES 查询 |
订单支付超时处理 | RabbitMQ 死信队列 |
系统高可用 | Redis 哨兵 + MySQL 主从 |
6. 总结
核心思路
- 限流(Nginx / Redis) 保护数据库
- 缓存预热(Redis 预减库存) 提升秒杀效率
- 消息队列(RabbitMQ) 削峰填谷,异步处理订单
- 数据库优化(分库分表 + 索引) 提升吞吐量
- 最终一致性(事务 + 双写校验) 确保库存精准
💡 面试 Tips:
- 结合实战经验,描述优化点(如 QPS 提升 5 倍,库存误差<1‰)
- 突出系统稳定性,讲清限流、削峰、数据库优化策略
- 深入问答准备:限流算法、Redis 崩溃时处理方案、分布式事务