秒杀系统设计

Posted by     "zengchengjie" on Wednesday, April 8, 2026

秒杀系统设计:高并发下的技术与艺术

前言

秒杀,是互联网领域最具挑战性的场景之一。双11的抢购、春运的火车票、热门商品的限量发售——这些场景的共同特点是:极短时间内,海量用户涌入,争夺有限的资源

秒杀系统的本质是一个高并发、大流量、资源有限的分布式系统问题。本文将系统性地剖析秒杀系统的设计难点、核心架构、优化策略以及完整的实现方案。

一、秒杀系统的核心挑战

1.1 秒杀场景特征

特征 说明 量化指标
瞬时高并发 短时间内请求量暴增 QPS 从 1000 飙升至 100万+
热点商品 所有请求争抢同一个资源 单个 SKU 被百万用户抢购
资源有限 商品库存极少 1000 件商品 vs 100万用户
流量洪峰 请求时间高度集中 前几秒涌入 80% 的流量
业务简单 核心逻辑相对简单 库存扣减 + 订单创建

1.2 核心难点

┌─────────────────────────────────────────────────────────────┐
│                        秒杀系统的三大难题                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │  超卖问题    │  │  高并发读    │  │  高并发写    │         │
│  ├─────────────┤  ├─────────────┤  ├─────────────┤         │
│  │ 库存扣减    │  │ 商品详情页   │  │ 订单创建     │         │
│  │ 数据一致性  │  │ 倒计时      │  │ 支付处理     │         │
│  │ 防重下单    │  │ 按钮状态    │  │ 库存扣减     │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.3 目标与约束

目标 约束
不超卖 库存扣减必须准确
不误杀 合法用户能正常参与
系统不崩 超出承载能力的流量要拒绝
响应快速 用户体验友好
公平性 先到先得(或适当随机)

二、秒杀系统整体架构

2.1 架构全景图

┌─────────────────────────────────────────────────────────────────────────┐
│                               客户端                                      │
│                    App / H5 / 小程序 / PC Web                            │
└─────────────────────────────────┬───────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼───────────────────────────────────────┐
│                           CDN (静态资源加速)                              │
│                     HTML / CSS / JS / 图片                               │
└─────────────────────────────────┬───────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼───────────────────────────────────────┐
│                        负载均衡 (SLB / LVS)                               │
└─────────────────────────────────┬───────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼───────────────────────────────────────┐
│                        接入层 (Nginx / OpenResty)                        │
│              - 限流(IP/用户)  - 黑白名单  - 静态化                      │
└─────────────────────────────────┬───────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼───────────────────────────────────────┐
│                         秒杀网关 (Seckill Gateway)                       │
│              - 请求校验  - 流量分发  - 削峰填谷                          │
└─────────────────────────────────┬───────────────────────────────────────┘
                                  │
┌─────────────────────────────────▼───────────────────────────────────────┐
│                          秒杀服务集群                                     │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐  │
│  │ 商品服务      │ │ 库存服务      │ │ 订单服务      │ │ 用户服务      │  │
│  └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘  │
└─────────────────────────────────┬───────────────────────────────────────┘
                                  │
        ┌─────────────────────────┼─────────────────────────┐
        │                         │                         │
┌───────▼───────┐        ┌────────▼────────┐        ┌───────▼───────┐
│    Redis      │        │   消息队列       │        │   MySQL      │
│  - 库存缓存    │        │  - 削峰填谷      │        │  - 订单持久化 │
│  - 用户标记    │        │  - 异步下单      │        │  - 库存记录   │
│  - 限流计数    │        │  - 失败重试      │        │  - 用户数据   │
└───────────────┘        └─────────────────┘        └───────────────┘

2.2 流量漏斗模型

秒杀系统的核心思想是层层过滤,让最终打到数据库的请求量控制在系统可承受范围内:

                    100万 请求
                        │
                        ▼
┌──────────────────────────────────────────────────────────┐
│  第1层: 前端/客户端限流                                    │
│  - 按钮置灰、防抖节流                                      │
│  剩余: 100万                                               │
└──────────────────────────────────────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────────────┐
│  第2层: CDN/接入层限流                                     │
│  - IP限流、用户限流、静态页面缓存                           │
│  剩余: 50万                                                │
└──────────────────────────────────────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────────────┐
│  第3层: Nginx 限流                                        │
│  - 连接数限制、请求速率限制                                │
│  剩余: 10万                                                │
└──────────────────────────────────────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────────────┐
│  第4层: 网关限流(令牌桶/漏桶)                            │
│  - 集群限流、用户维度限流                                  │
│  剩余: 1万                                                 │
└──────────────────────────────────────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────────────┐
│  第5层: 业务逻辑校验                                      │
│  - 活动时间校验、重复下单校验、库存预检                    │
│  剩余: 5000                                                │
└──────────────────────────────────────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────────────┐
│  第6层: 库存扣减(Redis原子操作)                          │
│  - LUA脚本保证原子性                                       │
│  成功: 1000(库存数)                                      │
└──────────────────────────────────────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────────────┐
│  第7层: 异步下单(MQ)                                    │
│  - 消息队列削峰                                            │
│  最终入库: 1000                                            │
└──────────────────────────────────────────────────────────┘

三、核心问题解决方案

3.1 库存扣减:不超卖的核心

方案选择:Redis 原子操作 + LUA 脚本

-- 秒杀库存扣减 LUA 脚本
-- KEYS[1]: 库存key
-- KEYS[2]: 用户限购key
-- ARGV[1]: 商品ID
-- ARGV[2]: 用户ID
-- ARGV[3]: 扣减数量
-- ARGV[4]: 限购数量

local stock_key = KEYS[1]
local user_limit_key = KEYS[2]
local product_id = ARGV[1]
local user_id = ARGV[2]
local quantity = tonumber(ARGV[3])
local limit_qty = tonumber(ARGV[4])

-- 1. 检查用户是否已抢购(防重复下单)
local user_bought = redis.call('GET', user_limit_key)
if user_bought and tonumber(user_bought) >= limit_qty then
    return {0, "您已达到购买上限"}
end

-- 2. 检查库存
local stock = redis.call('GET', stock_key)
if not stock or tonumber(stock) < quantity then
    return {0, "库存不足"}
end

-- 3. 扣减库存(原子操作)
local new_stock = redis.call('DECRBY', stock_key, quantity)

-- 4. 记录用户购买记录
redis.call('INCRBY', user_limit_key, quantity)
redis.call('EXPIRE', user_limit_key, 3600)  -- 1小时过期

-- 5. 返回成功
return {1, new_stock}

Java 调用示例

@Component
public class SeckillStockService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private DefaultRedisScript<Long> stockLuaScript;
    
    @PostConstruct
    public void init() {
        stockLuaScript = new DefaultRedisScript<>();
        stockLuaScript.setScriptText(SECKILL_LUA_SCRIPT);
        stockLuaScript.setResultType(Long.class);
    }
    
    public SeckillResult deductStock(String productId, String userId, int quantity, int limitQty) {
        String stockKey = "seckill:stock:" + productId;
        String userKey = "seckill:user:" + productId + ":" + userId;
        
        List<String> keys = Arrays.asList(stockKey, userKey);
        
        // 执行LUA脚本
        Long result = redisTemplate.execute(
            stockLuaScript, 
            keys, 
            productId, userId, String.valueOf(quantity), String.valueOf(limitQty)
        );
        
        if (result == 1L) {
            // 扣减成功,发送MQ消息异步创建订单
            sendOrderCreateMessage(productId, userId, quantity);
            return SeckillResult.success();
        } else {
            return SeckillResult.fail("抢购失败,库存不足或已达上限");
        }
    }
}

3.2 流量削峰:消息队列

@Component
public class SeckillOrderService {
    
    @Autowired
    private RocketMQTemplate mqTemplate;
    
    @Autowired
    private OrderService orderService;
    
    // 接收抢购请求,异步处理
    public void handleSeckill(SeckillRequest request) {
        // 1. 先进行库存扣减(Redis)
        SeckillResult result = stockService.deductStock(
            request.getProductId(), 
            request.getUserId(), 
            1, 
            1
        );
        
        if (!result.isSuccess()) {
            return;
        }
        
        // 2. 扣减成功后,发送MQ消息
        SeckillOrderMessage message = SeckillOrderMessage.builder()
            .productId(request.getProductId())
            .userId(request.getUserId())
            .seckillTime(System.currentTimeMillis())
            .build();
        
        mqTemplate.sendAsync("seckill_order_topic", message);
        
        // 3. 立即返回"排队中",告知用户稍后查询结果
    }
    
    // MQ消费者:异步创建订单
    @RocketMQMessageListener(topic = "seckill_order_topic", consumerGroup = "order_consumer")
    public class OrderConsumer implements RocketMQListener<SeckillOrderMessage> {
        
        @Override
        public void onMessage(SeckillOrderMessage message) {
            try {
                // 创建订单(写入数据库)
                Order order = orderService.createSeckillOrder(
                    message.getProductId(),
                    message.getUserId()
                );
                
                // 发送通知给用户(WebSocket/推送)
                notifyService.notifyUser(message.getUserId(), order);
                
            } catch (Exception e) {
                log.error("创建订单失败", e);
                // 失败补偿:回滚Redis库存
                stockService.rollbackStock(message.getProductId(), message.getUserId());
            }
        }
    }
}

3.3 限流策略:多层防护

3.3.1 Nginx 限流

# nginx.conf

# 限制单个IP的请求速率(10r/s)
limit_req_zone $binary_remote_addr zone=seckill_ip:10m rate=10r/s;

# 限制单个IP的连接数(20个)
limit_conn_zone $binary_remote_addr zone=conn_ip:10m;

server {
    listen 80;
    server_name seckill.example.com;
    
    location /seckill {
        # 应用限流
        limit_req zone=seckill_ip burst=20 nodelay;
        limit_conn conn_ip 20;
        
        # 只允许内网访问秒杀接口(通过网关转发)
        allow 10.0.0.0/8;
        deny all;
        
        proxy_pass http://seckill_gateway;
    }
}

3.3.2 网关层限流(Redis + 令牌桶)

@Component
public class TokenBucketLimiter {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    /**
     * 令牌桶限流
     * @param key 限流key(如用户ID、IP)
     * @param capacity 桶容量
     * @param rate 令牌生成速率(个/秒)
     */
    public boolean tryAcquire(String key, int capacity, int rate) {
        String tokenKey = "rate_limiter:" + key;
        String lastRefillKey = "rate_limiter_last:" + key;
        
        long now = System.currentTimeMillis();
        
        // 获取当前令牌数
        String tokenStr = redisTemplate.opsForValue().get(tokenKey);
        long tokens = tokenStr == null ? capacity : Long.parseLong(tokenStr);
        
        // 获取上次填充时间
        String lastStr = redisTemplate.opsForValue().get(lastRefillKey);
        long lastRefill = lastStr == null ? now : Long.parseLong(lastStr);
        
        // 计算需要补充的令牌数
        long interval = now - lastRefill;
        long newTokens = Math.min(capacity, tokens + interval * rate / 1000);
        
        if (newTokens < 1) {
            return false;
        }
        
        // 消耗一个令牌
        newTokens--;
        
        // 更新Redis
        redisTemplate.opsForValue().set(tokenKey, String.valueOf(newTokens));
        redisTemplate.opsForValue().set(lastRefillKey, String.valueOf(now));
        
        return true;
    }
}

3.3.3 用户级限流

@Component
public class UserRateLimiter {
    
    // 每秒最多请求数
    private static final int MAX_REQUESTS_PER_SECOND = 5;
    // 每分钟最多请求数
    private static final int MAX_REQUESTS_PER_MINUTE = 50;
    
    public boolean checkUserRateLimit(String userId) {
        String secondKey = "rate:user:second:" + userId;
        String minuteKey = "rate:user:minute:" + userId;
        
        // 秒级限流
        Long secondCount = redisTemplate.opsForValue().increment(secondKey);
        if (secondCount == 1) {
            redisTemplate.expire(secondKey, 1, TimeUnit.SECONDS);
        }
        if (secondCount > MAX_REQUESTS_PER_SECOND) {
            log.warn("用户 {} 触发秒级限流", userId);
            return false;
        }
        
        // 分钟级限流
        Long minuteCount = redisTemplate.opsForValue().increment(minuteKey);
        if (minuteCount == 1) {
            redisTemplate.expire(minuteKey, 60, TimeUnit.SECONDS);
        }
        if (minuteCount > MAX_REQUESTS_PER_MINUTE) {
            log.warn("用户 {} 触发分钟级限流", userId);
            return false;
        }
        
        return true;
    }
}

3.4 页面静态化与动静分离

┌─────────────────────────────────────────────────────────────┐
│                      动静分离架构                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  静态资源(CDN)                    动态数据(API)           │
│  ┌─────────────────┐              ┌─────────────────┐      │
│  │ HTML/CSS/JS     │              │ 库存数量         │      │
│  │ 商品图片         │              │ 倒计时           │      │
│  │ 页面框架         │              │ 秒杀按钮状态      │      │
│  │ 样式文件         │              │ 用户资格         │      │
│  └─────────────────┘              └─────────────────┘      │
│         │                                 │                 │
│         ▼                                 ▼                 │
│   直接返回给浏览器                  异步请求刷新              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

实现方式

<!-- 秒杀页面:静态HTML框架 -->
<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="//cdn.example.com/seckill/main.css">
    <script src="//cdn.example.com/seckill/main.js"></script>
</head>
<body>
    <div class="product-info">
        <img src="//cdn.example.com/images/product_123.jpg">
        <h1>秒杀商品:XXX手机</h1>
        <div class="price">秒杀价: ¥999</div>
    </div>
    
    <div class="seckill-info">
        <div id="countdown">距离开始: 00:00:00</div>
        <button id="seckill-btn" disabled>即将开始</button>
    </div>
    
    <script>
    // 轮询获取动态数据
    setInterval(function() {
        $.get('/api/seckill/status?productId=123', function(data) {
            updateCountdown(data.timeLeft);
            updateButtonStatus(data.status);
        });
    }, 1000);
    </script>
</body>
</html>

四、完整业务流程实现

4.1 秒杀活动流程图

┌─────────────────────────────────────────────────────────────────────┐
│                           秒杀完整流程                                │
└─────────────────────────────────────────────────────────────────────┘

用户端                              服务端
  │                                   │
  │  1. 进入秒杀页面                    │
  ├──────────────────────────────────▶│
  │                                   │
  │  2. 获取活动状态                    │
  │◀──────────────────────────────────┤  ← 返回倒计时、商品信息
  │                                   │
  │  3. 等待倒计时结束                  │
  │                                   │
  │  4. 点击秒杀按钮                    │
  ├──────────────────────────────────▶│
  │                                   │
  │                                   │ 5. 请求校验
  │                                   │    - 活动是否进行中
  │                                   │    - 用户是否登录
  │                                   │    - 用户IP是否正常
  │                                   │
  │                                   │ 6. 限流检查
  │                                   │    - 令牌桶限流
  │                                   │    - 用户级限流
  │                                   │
  │                                   │ 7. 库存预检
  │                                   │    - Redis检查库存
  │                                   │
  │                                   │ 8. 库存扣减
  │                                   │    - LUA原子操作
  │                                   │
  │                                   │ 9. 发送MQ消息
  │                                   │
  │  10. 返回"排队中"                  │
  │◀──────────────────────────────────┤
  │                                   │
  │  11. 轮询查询结果                  │
  ├──────────────────────────────────▶│
  │                                   │
  │                                   │ 12. 查询订单状态
  │                                   │     - 从Redis/DB获取
  │                                   │
  │  13. 返回结果                      │
  │    - 成功:跳转支付                │
  │    - 失败:提示信息                │
  │◀──────────────────────────────────┤
  │                                   │
  │                   后台异步处理      │
  │                                   │ 14. MQ消费
  │                                   │     - 创建订单
  │                                   │     - 扣减真实库存
  │                                   │     - 发送通知
  │                                   │

4.2 核心代码实现

@RestController
@RequestMapping("/api/seckill")
public class SeckillController {
    
    @Autowired
    private SeckillService seckillService;
    
    @Autowired
    private UserRateLimiter rateLimiter;
    
    @Autowired
    private SeckillStockService stockService;
    
    @Autowired
    private RocketMQTemplate mqTemplate;
    
    /**
     * 秒杀接口
     */
    @PostMapping("/do")
    public Result doSeckill(@RequestBody SeckillRequest request) {
        String userId = request.getUserId();
        String productId = request.getProductId();
        
        // 1. 基础校验
        // 1.1 活动时间校验
        if (!seckillService.isInSeckillPeriod(productId)) {
            return Result.error("秒杀活动未开始或已结束");
        }
        
        // 1.2 用户登录校验(从Token获取)
        if (!isLogin(userId)) {
            return Result.error("请先登录");
        }
        
        // 1.3 重复提交校验(防重令牌)
        String token = request.getToken();
        if (!validateToken(userId, token)) {
            return Result.error("请勿重复提交");
        }
        
        // 2. 限流检查
        if (!rateLimiter.checkUserRateLimit(userId)) {
            return Result.error("请求过于频繁,请稍后再试");
        }
        
        // 3. 库存预检(快速失败)
        Long stock = stockService.getStock(productId);
        if (stock == null || stock <= 0) {
            return Result.error("商品已抢光");
        }
        
        // 4. 核心:库存扣减(Redis LUA)
        SeckillResult result = stockService.deductStock(productId, userId, 1, 1);
        if (!result.isSuccess()) {
            return Result.error(result.getMessage());
        }
        
        // 5. 发送MQ消息,异步创建订单
        SeckillOrderMessage message = SeckillOrderMessage.builder()
            .productId(productId)
            .userId(userId)
            .seckillTime(System.currentTimeMillis())
            .build();
        
        String orderId = generateOrderId();
        message.setOrderId(orderId);
        
        // 将订单状态存入Redis(用于轮询)
        redisTemplate.opsForValue().set(
            "seckill:order_status:" + orderId,
            "pending",
            10, TimeUnit.MINUTES
        );
        
        mqTemplate.sendAsync("seckill_order_topic", message);
        
        // 6. 返回排队中,给用户orderId用于轮询
        return Result.success(Map.of(
            "orderId", orderId,
            "status", "pending",
            "message", "正在排队处理中,请稍后查询结果"
        ));
    }
    
    /**
     * 轮询查询秒杀结果
     */
    @GetMapping("/result")
    public Result queryResult(@RequestParam String orderId) {
        // 1. 先查Redis中的状态
        String status = redisTemplate.opsForValue().get("seckill:order_status:" + orderId);
        
        if ("pending".equals(status)) {
            return Result.success(Map.of("status", "pending"));
        }
        
        if ("success".equals(status)) {
            // 获取订单详情
            Order order = orderService.getOrder(orderId);
            return Result.success(Map.of("status", "success", "order", order));
        }
        
        if ("failed".equals(status)) {
            return Result.success(Map.of("status", "failed"));
        }
        
        // 2. Redis中没有,查数据库
        Order order = orderService.getOrder(orderId);
        if (order != null) {
            return Result.success(Map.of("status", "success", "order", order));
        }
        
        return Result.success(Map.of("status", "pending"));
    }
}

4.3 订单异步处理器

@Component
public class SeckillOrderConsumer {
    
    @Autowired
    private OrderService orderService;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Autowired
    private NotifyService notifyService;
    
    @RocketMQMessageListener(
        topic = "seckill_order_topic",
        consumerGroup = "seckill_order_consumer",
        consumeThreadMax = 64,
        maxReconsumeTimes = 3
    )
    public void onMessage(SeckillOrderMessage message) {
        String orderId = message.getOrderId();
        String userId = message.getUserId();
        String productId = message.getProductId();
        
        try {
            // 1. 幂等性检查(防止重复消费)
            Boolean consumed = redisTemplate.opsForValue()
                .setIfAbsent("seckill:consumed:" + orderId, "1", 1, TimeUnit.DAYS);
            if (Boolean.FALSE.equals(consumed)) {
                log.warn("订单已处理过,跳过: {}", orderId);
                return;
            }
            
            // 2. 创建订单(写入数据库)
            Order order = orderService.createSeckillOrder(
                orderId, productId, userId
            );
            
            // 3. 更新订单状态
            redisTemplate.opsForValue().set(
                "seckill:order_status:" + orderId,
                "success",
                10, TimeUnit.MINUTES
            );
            
            // 4. 发送通知(WebSocket/消息推送)
            notifyService.notifyUser(userId, "恭喜您抢购成功!订单号:" + orderId);
            
            // 5. 更新统计数据
            metricsCollector.recordSuccess();
            
        } catch (Exception e) {
            log.error("创建订单失败: orderId={}", orderId, e);
            
            // 失败:更新状态
            redisTemplate.opsForValue().set(
                "seckill:order_status:" + orderId,
                "failed",
                10, TimeUnit.MINUTES
            );
            
            // 回滚库存
            rollbackStock(productId, userId);
            
            // 发送失败通知
            notifyService.notifyUser(userId, "抢购失败,库存不足");
            
            metricsCollector.recordFailure();
            
            // 抛出异常触发MQ重试
            throw new RuntimeException("订单创建失败", e);
        }
    }
    
    private void rollbackStock(String productId, String userId) {
        String stockKey = "seckill:stock:" + productId;
        String userKey = "seckill:user:" + productId + ":" + userId;
        
        redisTemplate.opsForValue().increment(stockKey, 1);
        redisTemplate.delete(userKey);
    }
}

五、进阶优化策略

5.1 缓存预热

@Component
public class SeckillCacheWarmer {
    
    @EventListener(ApplicationReadyEvent.class)
    public void warmUp() {
        // 获取即将开始的秒杀活动
        List<SeckillActivity> activities = activityService.getUpcomingActivities();
        
        for (SeckillActivity activity : activities) {
            // 预热商品信息
            warmProductCache(activity.getProductId());
            
            // 预热库存到Redis
            String stockKey = "seckill:stock:" + activity.getProductId();
            redisTemplate.opsForValue().set(
                stockKey,
                String.valueOf(activity.getStock()),
                24, TimeUnit.HOURS
            );
            
            // 预热活动信息
            String activityKey = "seckill:activity:" + activity.getProductId();
            redisTemplate.opsForHash().putAll(activityKey, Map.of(
                "startTime", String.valueOf(activity.getStartTime()),
                "endTime", String.valueOf(activity.getEndTime()),
                "price", String.valueOf(activity.getPrice()),
                "status", "1"
            ));
        }
    }
    
    private void warmProductCache(String productId) {
        // 将商品详情从DB加载到Redis
        Product product = productService.getProduct(productId);
        String productKey = "seckill:product:" + productId;
        redisTemplate.opsForHash().putAll(productKey, Map.of(
            "name", product.getName(),
            "image", product.getImage(),
            "seckillPrice", String.valueOf(product.getSeckillPrice())
        ));
    }
}

5.2 热点检测与动态隔离

@Component
public class HotspotDetector {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    // 每秒统计窗口
    private final LoadingCache<String, AtomicLong> qpsCounter = Caffeine.newBuilder()
        .expireAfterWrite(2, TimeUnit.SECONDS)
        .build(key -> new AtomicLong(0));
    
    // 热点阈值
    private static final int HOTSPOT_THRESHOLD = 10000;
    
    public void recordRequest(String productId) {
        AtomicLong counter = qpsCounter.get(productId);
        long qps = counter.incrementAndGet();
        
        if (qps > HOTSPOT_THRESHOLD) {
            // 触发热点保护
            String hotspotKey = "seckill:hotspot:" + productId;
            Boolean isHot = redisTemplate.opsForValue().setIfAbsent(hotspotKey, "1", 10, TimeUnit.SECONDS);
            
            if (Boolean.TRUE.equals(isHot)) {
                log.warn("商品 {} 成为热点,触发保护机制", productId);
                // 动态扩容该商品的处理节点
                scaleUpForHotspot(productId);
            }
        }
    }
    
    private void scaleUpForHotspot(String productId) {
        // 方案1: 增加该商品的缓存副本
        String stockKey = "seckill:stock:" + productId;
        for (int i = 1; i <= 5; i++) {
            String replicaKey = stockKey + ":replica:" + i;
            redisTemplate.opsForValue().set(replicaKey, 
                redisTemplate.opsForValue().get(stockKey));
        }
        
        // 方案2: 通知K8s增加Pod副本
        k8sClient.scaleDeployment("seckill-service", 10);
    }
}

5.3 降级与熔断

@Component
public class SeckillCircuitBreaker {
    
    @Autowired
    private HealthIndicator healthIndicator;
    
    // 降级开关
    private volatile boolean degradeEnabled = false;
    
    // 熔断器状态
    private enum CircuitState { CLOSED, OPEN, HALF_OPEN }
    private volatile CircuitState state = CircuitState.CLOSED;
    private AtomicInteger failureCount = new AtomicInteger(0);
    private AtomicInteger successCount = new AtomicInteger(0);
    
    private static final int FAILURE_THRESHOLD = 50;
    private static final int SUCCESS_THRESHOLD = 10;
    private static final long OPEN_TIMEOUT = 30000; // 30秒
    
    private volatile long openTime = 0;
    
    @Scheduled(fixedDelay = 1000)
    public void checkHealth() {
        switch (state) {
            case CLOSED:
                if (failureCount.get() > FAILURE_THRESHOLD) {
                    state = CircuitState.OPEN;
                    openTime = System.currentTimeMillis();
                    log.warn("熔断器开启,秒杀服务降级");
                    degradeEnabled = true;
                }
                break;
                
            case OPEN:
                if (System.currentTimeMillis() - openTime > OPEN_TIMEOUT) {
                    state = CircuitState.HALF_OPEN;
                    successCount.set(0);
                    log.info("熔断器半开,尝试恢复");
                }
                break;
                
            case HALF_OPEN:
                if (successCount.get() > SUCCESS_THRESHOLD) {
                    state = CircuitState.CLOSED;
                    failureCount.set(0);
                    degradeEnabled = false;
                    log.info("熔断器关闭,服务恢复");
                }
                break;
        }
    }
    
    public void recordSuccess() {
        if (state == CircuitState.HALF_OPEN) {
            successCount.incrementAndGet();
        } else if (state == CircuitState.CLOSED) {
            failureCount.set(0);
        }
    }
    
    public void recordFailure() {
        if (state == CircuitState.CLOSED) {
            failureCount.incrementAndGet();
        } else if (state == CircuitState.HALF_OPEN) {
            state = CircuitState.OPEN;
            openTime = System.currentTimeMillis();
        }
    }
    
    public boolean isDegradeEnabled() {
        return degradeEnabled;
    }
}

5.4 数据库优化

-- 秒杀订单表(分库分表)
CREATE TABLE `seckill_order` (
    `id` bigint NOT NULL AUTO_INCREMENT,
    `order_id` varchar(32) NOT NULL COMMENT '订单号',
    `product_id` varchar(32) NOT NULL COMMENT '商品ID',
    `user_id` bigint NOT NULL COMMENT '用户ID',
    `price` decimal(10,2) NOT NULL COMMENT '秒杀价',
    `status` tinyint DEFAULT '0' COMMENT '0-待支付 1-已支付 2-已取消',
    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
    `pay_time` datetime DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_order_id` (`order_id`),
    KEY `idx_user_id` (`user_id`),
    KEY `idx_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀订单表';

-- 建议:
-- 1. 按 user_id 分片,方便查询用户订单
-- 2. 订单号使用雪花算法,保证全局唯一
-- 3. 历史订单定期归档到冷存储

六、监控与运维

6.1 核心监控指标

指标 说明 告警阈值
秒杀QPS 当前请求量 > 预期值200%
库存扣减成功率 成功扣减/总请求 < 50%
订单创建延迟 P99延迟 > 3秒
Redis命中率 缓存命中率 < 90%
MQ积压数 未消费消息数 > 10000
限流触发次数 被限流请求数 > 总请求50%

6.2 压测方案

压测工具: JMeter / wrk / Locust

压测场景:
  - 正常流量: 模拟真实用户行为
  - 突发流量: 瞬间大量请求
  - 超卖测试: 并发请求超过库存数
  - 异常测试: 网络延迟、服务超时

压测指标:
  - 吞吐量: 最大 QPS
  - 响应时间: P50/P99/P999
  - 错误率: 4xx/5xx 比例
  - 资源占用: CPU/内存/网络

压测数据准备:
  - 100万用户账号
  - 10个秒杀商品
  - 库存设置: 1000/商品

七、总结

7.1 设计要点回顾

问题 解决方案 关键点
超卖 Redis LUA原子扣减 保证原子性