本文目录导读:

我来为你设计一个高并发秒杀系统的完整案例,这是一个经典的分布式系统设计难题,我会从架构到代码给出完整的解决方案。
系统需求分析
核心业务场景
- 商品限量抢购(如1000件商品,10万人抢购)
- 高并发访问(瞬时QPS可达10万+)
- 需要防止超卖和重复购买
核心挑战
- 高并发:瞬间流量巨大
- 超卖问题:库存不能为负数
- 限购问题:同一用户限购1件
- 响应延迟:用户体验要求高
系统架构设计
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ 客户端/APP │────▶│ 负载均衡 │────▶│ 静态资源CDN │
└─────────────┘ │ (Nginx) │ └──────────────┘
└──────────────┘
│
┌──────▼──────┐
│ 网关层 │
│ (限流/降级) │
└──────┬──────┘
│
┌──────▼────────────────────────────────┐
│ 应用层 (集群部署) │
│ 秒杀接口 ─ 缓存预减 ─ 异步下单 │
└──────┬────────────────────────────────┘
│
┌────────────┼─────────────┐
│ │ │
┌────────▼───┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Redis集群 │ │ 消息队列 │ │ 数据库集群 │
│ (库存预减) │ │ (RabbitMQ)│ │ (最终库存) │
└────────────┘ └───────────┘ └───────────┘
核心代码实现
秒杀接口设计(Controller层)
@RestController
@RequestMapping("/seckill")
public class SeckillController {
@Autowired
private SeckillService seckillService;
@Autowired
private RedisService redisService;
/**
* 秒杀接口
* @param userId 用户ID
* @param goodsId 商品ID
*/
@PostMapping("/execute")
public Result<String> seckill(@RequestParam Long userId,
@RequestParam Long goodsId) {
// 1. 前置校验(限流拦截)
if (!seckillService.preCheck(userId, goodsId)) {
return Result.error("请求过于频繁或已参加过秒杀");
}
// 2. 执行秒杀
boolean success = seckillService.executeSeckill(userId, goodsId);
return success ? Result.success("秒杀成功") : Result.error("库存不足");
}
}
秒杀核心服务(Service层)
@Service
@Slf4j
public class SeckillServiceImpl implements SeckillService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private GoodsMapper goodsMapper;
private static final String STOCK_PREFIX = "seckill:stock:";
private static final String USER_PREFIX = "seckill:user:";
/**
* 秒杀执行(核心逻辑)
*/
@Override
public boolean executeSeckill(Long userId, Long goodsId) {
// 1. 检查用户是否已经秒杀过(防止重复秒杀)
Boolean isExists = redisTemplate.hasKey(USER_PREFIX + goodsId + ":" + userId);
if (Boolean.TRUE.equals(isExists)) {
return false;
}
// 2. Redis库存预减(原子操作)
Long stock = redisTemplate.opsForValue().decrement(STOCK_PREFIX + goodsId);
if (stock < 0) {
// 库存不足,回补库存
redisTemplate.opsForValue().increment(STOCK_PREFIX + goodsId);
return false;
}
// 3. 记录用户已秒杀(防止重复)
redisTemplate.opsForValue().set(USER_PREFIX + goodsId + ":" + userId,
String.valueOf(System.currentTimeMillis()),
24, TimeUnit.HOURS);
// 4. 发送异步消息,生成订单
try {
SeckillMessage message = new SeckillMessage(userId, goodsId);
rocketMQTemplate.convertAndSend("seckill-order-topic", message);
return true;
} catch (Exception e) {
log.error("发送秒杀消息失败", e);
// 消息发送失败,需要回滚
rollbackStock(userId, goodsId);
return false;
}
}
/**
* 回滚库存
*/
private void rollbackStock(Long userId, Long goodsId) {
redisTemplate.opsForValue().increment(STOCK_PREFIX + goodsId);
redisTemplate.delete(USER_PREFIX + goodsId + ":" + userId);
}
/**
* 前置校验
*/
public boolean preCheck(Long userId, Long goodsId) {
// 检查商品是否在秒杀窗口期
SeckillGoods goods = goodsMapper.selectById(goodsId);
if (goods == null) {
return false;
}
long current = System.currentTimeMillis();
if (current < goods.getStartTime() || current > goods.getEndTime()) {
return false;
}
return true;
}
}
Redis库存初始化
@Component
public class StockInitializer implements InitializingBean {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private GoodsMapper goodsMapper;
private static final String STOCK_PREFIX = "seckill:stock:";
@Override
public void afterPropertiesSet() {
// 系统启动时,将数据库库存加载到Redis
List<Goods> goodsList = goodsMapper.selectAll();
for (Goods goods : goodsList) {
if (goods.getStock() > 0) {
redisTemplate.opsForValue().set(
STOCK_PREFIX + goods.getId(),
String.valueOf(goods.getStock())
);
}
}
}
}
消息队列消费者(异步下单)
@Component
@RocketMQMessageListener(topic = "seckill-order-topic",
consumerGroup = "seckill-order-group")
@Slf4j
public class SeckillOrderConsumer implements RocketMQListener<SeckillMessage> {
@Autowired
private OrderService orderService;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public void onMessage(SeckillMessage message) {
Long userId = message.getUserId();
Long goodsId = message.getGoodsId();
// 生成订单
try {
Order order = new Order();
order.setUserId(userId);
order.setGoodsId(goodsId);
order.setOrderNo(generateOrderNo());
order.setCreateTime(new Date());
orderService.createOrder(order);
// 订单成功后,更新数据库库存
boolean updated = orderService.decreaseStock(goodsId);
if (!updated) {
// 数据库中库存不足,补偿
log.error("数据库库存更新失败: goodsId={}", goodsId);
}
} catch (Exception e) {
log.error("创建订单异常", e);
// 可以加入死信队列或重试机制
}
}
private String generateOrderNo() {
return "ORDER" + System.currentTimeMillis() +
String.format("%04d", new Random().nextInt(10000));
}
}
限流器实现(防止刷单)
@Component
public class RateLimiter {
// 使用Guava RateLimiter或自研令牌桶
private final Map<Long, RateLimiter> goodsRateLimiters = new ConcurrentHashMap<>();
/**
* 基于IP和用户ID的限流
*/
public boolean tryAcquire(Long userId, String ip) {
// 1. 用户级别限流:同一用户1秒内只能请求1次
String userKey = "user:" + userId;
if (!allow("rate:user:" + userId, 1, 1)) {
return false;
}
// 2. IP级别限流:同一IP 1秒内只能请求10次
if (!allow("rate:ip:" + ip, 10, 1)) {
return false;
}
// 3. 商品级别限流:控制瞬时并发
RateLimiter goodsLimiter = goodsRateLimiters.computeIfAbsent(
goodsId, k -> RateLimiter.create(1000)); // 每秒1000个请求
return goodsLimiter.tryAcquire();
}
/**
* 基于Redis的滑动窗口限流
*/
private boolean allow(String key, int limit, int windowSeconds) {
long currentTime = System.currentTimeMillis();
String member = String.valueOf(currentTime);
// 使用ZSET实现滑动窗口
Long count = redisTemplate.opsForZSet().count(key,
currentTime - windowSeconds * 1000, currentTime);
if (count < limit) {
redisTemplate.opsForZSet().add(key, member, currentTime);
// 清理过期数据
redisTemplate.expire(key, windowSeconds + 1, TimeUnit.SECONDS);
return true;
}
return false;
}
}
数据库表设计
-- 秒杀商品表 CREATE TABLE `seckill_goods` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `goods_name` varchar(100) NOT NULL COMMENT '商品名称', `stock` int(11) NOT NULL COMMENT '库存', `price` decimal(10,2) NOT NULL COMMENT '秒杀价格', `start_time` datetime NOT NULL COMMENT '秒杀开始时间', `end_time` datetime NOT NULL COMMENT '秒杀结束时间', `version` int(11) DEFAULT '0' COMMENT '乐观锁版本号', PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='秒杀商品表'; -- 订单表 CREATE TABLE `seckill_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `order_no` varchar(50) NOT NULL COMMENT '订单号', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `goods_id` bigint(20) NOT NULL COMMENT '商品ID', `status` tinyint(4) DEFAULT '0' COMMENT '订单状态', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_goods` (`user_id`,`goods_id`) -- 唯一约束防止重复下单 ) ENGINE=InnoDB COMMENT='秒杀订单表';
性能优化方案
多级缓存策略
// 一级缓存:本地缓存(Caffeine) // 二级缓存:Redis分布式缓存 // 三级缓存:数据库
数据库优化
-- 使用乐观锁防止超卖
UPDATE seckill_goods
SET stock = stock - 1, version = version + 1
WHERE id = #{goodsId}
AND stock > 0
AND version = #{version};
-- 如果更新失败,说明库存不足或版本冲突
异步订单处理
// 秒杀成功后,不立即创建订单 // 而是发送MQ消息,后台异步处理 // 用户可以在"我的订单"中查看结果
部署架构建议
生产环境建议:
├── 4台Nginx(负载均衡)
├── 4台前端服务器(静态资源)
├── 4台应用服务器(部署秒杀服务)
├── 1个Redis集群(3主3从)
├── 1个RocketMQ集群(2个节点)
└── 数据库集群(主从复制,读写分离)
实战提示
- 压测准备:使用JMeter进行压力测试
- 监控告警:使用Prometheus + Grafana
- 降级方案:秒杀结束/异常时,快速返回错误
- 数据一致性:Redis和数据库的最终一致性
- 限流策略:防止恶意刷接口
这个案例涵盖了秒杀系统的核心设计思路,你可以根据实际业务场景调整和优化,如果你有具体问题(比如某个模块的实现细节),我可以进一步详细讲解。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播