基于Java + Redis + RocketMQ的库存秒杀系统设计与实现
一、秒杀场景核心挑战
-
瞬时高并发:万级QPS访问压力
-
库存准确性:避免超卖/少卖
-
系统可用性:防止雪崩效应
-
数据一致性:缓存与数据库同步
二、技术架构设计
1. 分层架构设计
用户请求 │ ▼ 接入层(Nginx限流 + 令牌拦截) │ ▼ 逻辑层(Redis预扣库存 + RocketMQ异步) │ ▼ 数据层(MySQL最终扣减)
2. 核心流程
sequenceDiagram participant 用户 participant 网关 participant Redis participant RocketMQ participant MySQL 用户->>网关: 提交秒杀请求 网关->>Redis: 执行预扣库存(LUA) alt 库存不足 Redis-->>用户: 秒杀失败 else 库存足够 Redis->>RocketMQ: 发送异步消息 RocketMQ-->>网关: 返回受理成功 网关-->>用户: 提示排队中 RocketMQ->>MySQL: 消费消息执行真实扣减 end
三、核心代码实现
1. 环境依赖
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- RocketMQ -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
2. Redis预扣库存(LUA脚本)
-- seckill.lua
local key = KEYS[1]
local quantity = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or '0')
if current >= quantity then
redis.call('decrby', key, quantity)
return 1 -- 成功
else
return 0 -- 失败
end
3. 秒杀接口实现
@RestController
public class SeckillController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
// 加载LUA脚本
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
static {
SECKILL_SCRIPT = new DefaultRedisScript<>();
SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
SECKILL_SCRIPT.setResultType(Long.class);
}
@PostMapping("/seckill")
public ResponseEntity<String> seckill(@RequestParam Long itemId,
@RequestParam Integer userId) {
// 1. 参数校验
if (itemId == null || userId == null) {
return ResponseEntity.badRequest().body("参数错误");
}
// 2. 执行LUA脚本预扣库存
List<String> keys = Collections.singletonList("stock:" + itemId);
Long result = redisTemplate.execute(SECKILL_SCRIPT, keys, "1");
// 3. 处理结果
if (result == 1) {
// 发送MQ消息
rocketMQTemplate.send("seckill_topic",
MessageBuilder.withPayload(new SeckillMessage(itemId, userId)).build());
return ResponseEntity.ok("秒杀请求已受理");
} else {
return ResponseEntity.status(429).body("库存不足");
}
}
}
4. RocketMQ消费者
@RocketMQMessageListener(
topic = "seckill_topic",
consumerGroup = "seckill_consumer_group"
)
@Service
public class SeckillConsumer implements RocketMQListener<SeckillMessage> {
@Autowired
private StockService stockService;
@Override
public void onMessage(SeckillMessage message) {
// 数据库真实扣减
stockService.reduceStock(message.getItemId());
// 记录订单等后续操作...
}
}
5. 数据库操作
@Service
public class StockServiceImpl implements StockService {
@Autowired
private StockMapper stockMapper;
@Transactional
public boolean reduceStock(Long itemId) {
// 乐观锁更新
int rows = stockMapper.reduceStockWithVersion(itemId);
if (rows == 0) {
// 库存补偿逻辑
handleStockRollback(itemId);
return false;
}
return true;
}
}
<!-- MyBatis Mapper -->
<update id="reduceStockWithVersion">
UPDATE stock
SET quantity = quantity - 1,
version = version + 1
WHERE item_id = #{itemId}
AND quantity > 0
AND version = #{version}
</update>
四、关键优化设计
1. 库存预热方案
// 活动开始前加载库存到Redis
public void preheatStock(Long itemId, Integer stock) {
redisTemplate.opsForValue().set("stock:" + itemId, stock.toString());
}
2. 限流策略
// 使用Guava RateLimiter做令牌桶限流
private RateLimiter rateLimiter = RateLimiter.create(1000); // 每秒1000个请求
@PostMapping("/seckill")
public ResponseEntity<String> seckill(...) {
if (!rateLimiter.tryAcquire()) {
return ResponseEntity.status(429).body("请求过于频繁");
}
// 原有逻辑...
}
3. 降级方案
@Value("${seckill.switch:true}")
private boolean seckillSwitch;
@PostMapping("/seckill")
public ResponseEntity<String> seckill(...) {
if (!seckillSwitch) {
return ResponseEntity.status(503).body("活动暂未开始");
}
// 原有逻辑...
}
4. 防刷策略
// Redis记录用户访问频率
String userKey = "user_limit:" + userId;
Long count = redisTemplate.opsForValue().increment(userKey, 1);
if (count != null && count > 5) { // 每秒限制5次
return ResponseEntity.status(429).body("操作过于频繁");
}
redisTemplate.expire(userKey, 1, TimeUnit.SECONDS);
五、监控与告警设计
1. 监控指标
- Redis库存余量 - MQ堆积量 - 接口QPS/TPS - 数据库连接池使用率
2. 告警规则
- MQ消息堆积超过10000条 - Redis内存使用率>80% - 接口失败率>1%
六、压测验证方案
-
JMeter压测脚本配置:
-
5000并发用户持续30秒
-
添加思考时间300ms
-
监控TPS/RT/错误率
-
-
验证指标:
- 请求成功率 ≥99.99% - 平均响应时间 <500ms - 数据库最终一致性100%
七、总结
架构优势:
-
流量分层过滤:拦截80%无效请求
-
读写分离:Redis承担99%的读压力
-
异步解耦:MQ保证最终一致性
-
柔性可用:快速失败+自动降级
扩展方向:
-
增加分布式锁防止重复下单
-
引入Sentinel实现熔断限流
-
使用Redis Cluster提升缓存容量
-
添加异步订单日志