设计支持万人并发抢购的秒杀系统架构方案
一、系统架构设计
1. 分层架构
一个健壮的秒杀系统通常采用分层处理策略,从客户端到数据层逐级过滤流量:
客户端层 → 接入层 → 业务服务层 → 数据层
限流 缓存 队列 数据库
2. 具体组件
- 客户端:静态资源通过 CDN 分发,倒计时校准防止时间篡改,前端防重复提交。
- 接入层:使用 Nginx+Lua 或 OpenResty 进行第一道限流和静态缓存。
- 业务层:
- 无状态秒杀服务集群,便于弹性伸缩。
- 消息队列(Kafka/RocketMQ)用于削峰填谷。
- Redis Cluster 缓存集群处理热点库存。
- 数据层:
- 主从数据库实现读写分离。
- 分库分表策略按商品 ID 或时间片拆分。
二、核心问题解决方案
1. 超卖问题
方案一:Redis 原子操作
利用 Lua 脚本保证检查与扣减的原子性,避免多线程竞争。
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock and stock > 0 then
redis.call('DECR', KEYS[1])
return 1
end
return 0
方案二:数据库乐观锁
在 SQL 层面增加版本号控制,确保并发更新时的数据一致性。
UPDATE products SET stock = stock - 1, version = version + 1
WHERE id = ? AND stock > 0 AND version = ?
方案三:预扣库存
先扣减 Redis 库存,再异步同步至数据库。若 Redis 扣减失败则回滚。
public boolean preDeductStock(String productId, int count) {
String key = "seckill:stock:" + productId;
Long remaining = redisTemplate.opsForValue().decrement(key, count);
if (remaining >= 0) {
sendStockDeductMessage(productId, count); // 发送 MQ 异步扣减 DB
return true;
} else {
redisTemplate.opsForValue().increment(key, count); // 库存不足回滚
return false;
}
}
2. 高并发请求处理
2.1 流量削峰
将同步请求转为异步处理,用户提交后立即返回排队状态,后端慢慢消费。
@Component
public class SeckillService {
@Autowired
private RocketMQTemplate mqTemplate;
public SeckillResult seckill(SeckillRequest request) {
if (!validate(request)) {
return SeckillResult.fail("校验失败");
}
String requestId = generateRequestId(request);
// 请求入队,立即返回
mqTemplate.sendOneWay("seckill-topic", MessageBuilder.withPayload(request).build());
return SeckillResult.processing(requestId);
}
}
2.2 分层过滤
通过多级校验逐步减少进入核心逻辑的请求量:
所有请求 → 合法性校验 → 库存校验 → 频率控制 → 实际下单
100 万 → 50 万 → 10 万 → 5 万 → 1 万
3. 系统性能优化
3.1 缓存策略
采用多级缓存架构,减轻数据库压力:
- 一级缓存:JVM 本地缓存(Caffeine),存储热点商品详情。
- 二级缓存:Redis 集群,存储库存信息与布隆过滤器。
- 三级缓存:数据库,保证最终一致性。
3.2 读多写少优化
活动开始前预热商品信息,避免冷启动冲击。
@Service
public class CacheWarmUpService {
@PostConstruct
public void warmUpSeckillProducts() {
List<Product> hotProducts = loadHotProducts();
for (Product product : hotProducts) {
redisTemplate.opsForValue().set("stock:" + product.getId(), product.getStock());
redisTemplate.opsForValue().set("product:" + product.getId(), JSON.toJSONString(product));
bloomFilter.add(product.getId());
}
}
}
4. 详细实现方案
4.1 秒杀流程
整合风险拦截、布隆过滤、库存判断及订单生成,形成完整闭环。
public Response processSeckill(String userId, String productId) {
// 1. 恶意请求拦截
if (!checkRisk(userId)) {
return Response.error("访问过于频繁");
}
// 2. 布隆过滤器快速判断
if (!bloomFilter.contains(productId)) {
return Response.error("商品不存在");
}
// 3. 内存标记已售罄
if (soldOutFlags.get(productId)) {
return Response.error("已售罄");
}
// 4. Redis 原子扣减库存
if (!deductStockInRedis(productId)) {
soldOutFlags.put(productId, true);
return Response.error("库存不足");
}
// 5. 生成订单 ID(雪花算法)
String orderId = snowflake.generate();
// 6. 订单信息入队
mq.send(OrderDTO.builder()
.orderId(orderId)
.userId(userId)
.productId(productId)
.time(System.currentTimeMillis())
.build());
// 7. 返回排队中
return Response.success("排队中", orderId);
}
4.2 库存同步方案
后台服务消费 MQ 消息,异步更新数据库并执行对账任务。
@Component
@Slf4j
public class StockSyncService {
@Transactional
public void syncStockToDB(String productId, int count) {
try {
boolean success = productDAO.deductStock(productId, count);
if (success) {
redisTemplate.opsForValue().set("stock_final:" + productId, getDBStock(productId));
soldOutCache.remove(productId);
}
} catch (Exception e) {
log.error("库存同步失败", e);
alertService.sendAlert(e);
}
}
@Scheduled(cron = "0 */5 * * * ?")
public void stockReconciliation() {
List<Product> products = productDAO.getAllSeckillProducts();
for (Product product : products) {
Integer redisStock = getRedisStock(product.getId());
Integer dbStock = product.getStock();
if (!Objects.equals(redisStock, dbStock)) {
log.warn("库存不一致:productId={}, redis={}, db={}", product.getId(), redisStock, dbStock);
fixStockInconsistency(product.getId(), dbStock);
}
}
}
}
三、高可用保障
1. 限流降级策略
多维度配置限流规则,保护核心服务不被压垮:
- 用户维度:每个用户 10 次/分钟。
- IP 维度:每个 IP 1000 次/分钟。
- 商品维度:每个商品 10000 次/分钟。
- 总 QPS:系统最大承受 50000 QPS。
2. 熔断降级
当依赖服务响应过慢时自动熔断,防止雪崩。
@RestController
@Slf4j
public class SeckillController {
@GetMapping("/seckill/{productId}")
@HystrixCommand(
fallbackMethod = "seckillFallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20")
}
)
public Response seckill(@PathVariable String productId, @RequestParam String userId) {
return seckillService.process(userId, productId);
}
public Response seckillFallback(String productId, String userId) {
return Response.error("系统繁忙,请稍后重试");
}
}
四、监控与告警
1. 关键监控指标
- 系统层面:QPS、RT、错误率、CPU/内存使用率。
- 应用层面:库存扣减成功率、消息堆积量。
- 业务层面:抢购成功率、用户排队时长。
2. 监控实现
使用 Micrometer 等工具记录关键指标。
@Component
public class SeckillMonitor {
private final MeterRegistry meterRegistry;
public void recordSeckill(String productId, boolean success, long cost) {
meterRegistry.counter("seckill.requests.total").increment();
if (success) {
meterRegistry.counter("seckill.success.total").increment();
} else {
meterRegistry.counter("seckill.fail.total").increment();
}
meterRegistry.timer("seckill.process.time")
.record(cost, TimeUnit.MILLISECONDS);
meterRegistry.gauge("seckill.stock." + productId, getCurrentStock(productId));
}
}
五、部署与扩展
1. 弹性扩展策略
- 水平扩展:无状态服务可快速扩容。
- 自动伸缩:基于 CPU 使用率或 QPS 自动扩缩容。
- 异地多活:重要业务支持多机房部署。
2. 压测方案
- 场景 1:库存预热,10 万用户同时抢 1 万商品。
- 场景 2:持续高压,5 万 QPS 持续 5 分钟。
- 场景 3:峰值冲击,瞬间 20 万 QPS。
- 目标:成功率 >99.9%,平均 RT <100ms,错误率 <0.1%。
六、安全考虑
- 防刷机制:验证码(峰值时降级)、设备指纹、行为分析。
- 数据安全:关键数据加密、操作日志记录、防篡改校验。
七、实战复盘:面试视角下的秒杀设计
如果在面试中被问到如何设计秒杀系统,建议从架构、核心难点、兜底策略三个维度展开:
-
架构设计上动静分离、分层削峰 静态资源推送到 CDN,网关层做限流和恶意拦截。核心下单逻辑后置,请求进入消息队列,实现削峰填谷。服务独立部署,避免影响商城其他功能。
-
解决超卖与高并发 库存预热到 Redis,使用
DECR或 Lua 脚本保证原子性扣减。数据库作为最终一致性存储,通过异步对账修复差异。应对高并发时,除了限流,还要做热点 Key 散列和服务无状态化扩容。 -
细节与兜底 前端加入计算型验证码,下单前校验资格。用户提交后返回排队 ID 轮询结果。建立定时对账任务核对 Redis、DB 库存和订单状态。若 Redis 或数据库异常,要有熔断机制,快速降级返回售罄页面。
总结来说,设计思路是:前端限流拦截,请求队列削峰;Redis 原子扣减防超卖;服务无状态化应对高并发;再通过异步、对账等手段保证最终一致性和用户体验。


