一、秒杀场景的核心挑战
在开发黑马点评的秒杀模块时,我们面临着典型的高并发系统设计难题。以"限量特价套餐秒杀"活动为例,1000份商品在活动开始后3秒内就被抢购一空,系统峰值QPS达到5000+。
1.1 秒杀场景三大难题
1. 超卖问题
数据库库存减到负数,出现"卖了1000件商品却产生1200个订单"的情况
2. 高并发冲击
瞬时流量是平时的100倍以上,直接击穿数据库连接池
3. 恶意请求泛滥
脚本抢购、刷单等行为占用系统资源
二、技术方案设计与实现
2.1 整体架构设计
采用分层削峰策略:
-
前端:静态资源CDN + 按钮防重复点击
-
网关层:限流熔断
-
服务层:Redis库存校验 + 异步下单
-
持久层:数据库最终一致性
2.2 Redis库存预减
关键代码实现:
// 初始化库存到Redis
public void initSeckillStock(Long seckillId, int stock) {
String key = "seckill:stock:" + seckillId;
stringRedisTemplate.opsForValue().set(key, String.valueOf(stock));
}
// Lua脚本保证原子性
private static final String STOCK_DEDUCTION_SCRIPT =
"if tonumber(redis.call('get', KEYS[1])) > 0 then\n" +
" redis.call('decr', KEYS[1])\n" +
" return 1\n" +
"else\n" +
" return 0\n" +
"end";
2.3 RabbitMQ异步削峰
消息队列配置:
@Configuration
public class RabbitMQConfig {
@Bean
public Queue seckillQueue() {
return new Queue("seckill.queue", true);
}
@Bean
public Exchange seckillExchange() {
return new DirectExchange("seckill.exchange");
}
@Bean
public Binding binding() {
return BindingBuilder.bind(seckillQueue())
.to(seckillExchange()).with("seckill.routingKey").noargs();
}
}
下单流程改造:
public Result handleSeckill(Long userId, Long seckillId) {
// 1. Redis校验库存
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<>(STOCK_DEDUCTION_SCRIPT, Long.class),
Collections.singletonList("seckill:stock:" + seckillId));
// 2. 库存不足
if (result == 0) {
return Result.fail("秒杀已结束");
}
// 3. 发送MQ消息
SeckillMessage message = new SeckillMessage(userId, seckillId);
rabbitTemplate.convertAndSend("seckill.exchange",
"seckill.routingKey",
message);
return Result.ok("排队中");
}
三、性能优化成果
通过JMeter压测对比(模拟10000并发):
指标 | 原始方案 | 优化方案 |
---|---|---|
峰值QPS | 500 | 3200 |
平均响应时间 | 1200ms | 180ms |
数据库CPU使用率 | 95% | 30% |
订单创建成功率 | 68% | 99.9% |
关键提升点:
-
Redis承担了99%的读请求
-
数据库写入量降低到原来的1/20
-
系统吞吐量提升6倍
四、安全防护措施
4.1 限流策略实现
网关层限流:
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getHostName());
}
@Bean
public RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(100, 200);
}
接口级别限流:
@RateLimiter(value = 100, key = "#seckillId")
public Result querySeckillInfo(Long seckillId) {
// 查询逻辑
}
4.2 防刷策略
-
验证码机制:秒杀前需要完成图形验证
-
用户限购:Redis记录用户购买标记
redis
SETEX seckill:user:{userId}:{seckillId} 3600 1
-
IP黑名单:Nginx限制单个IP访问频率
五、延伸思考与优化方向
-
库存预热优化:
-
采用分段库存方案(如将1000库存分为10个100)
-
使用Redis Cluster分散热点key压力
-
-
订单处理优化:
/* 使用乐观锁防止超卖 */ UPDATE seckill_goods SET stock = stock - 1 WHERE id = #{id} AND stock > 0
-
数据一致性保障:
-
引入定时任务补偿机制
-
建立死信队列处理异常订单
-
六、经验总结
-
避免过早优化:先确保功能正确性,再考虑性能
-
监控必不可少:
-
Redis内存/CPU监控
-
RabbitMQ堆积告警
-
数据库慢查询监控
-
-
降级方案:准备静态化降级页面应对极端情况