Skip to content

秒杀系统设计

🔥 大规模秒杀架构

系统架构设计

Q1: 设计一个能支持千万级用户同时抢购的秒杀系统?如何处理高并发和库存一致性?

难度: ⭐⭐⭐⭐⭐

答案: 秒杀系统需要在极短时间内处理海量请求,核心挑战是高并发、库存一致性和系统稳定性。

1. 整体架构设计:

mermaid
graph TB
    A[用户浏览器] --> B[CDN内容分发网络]
    B --> C[负载均衡器 Nginx/HAProxy]
    C --> D[API网关 Kong/Zuul]
    
    D --> E[秒杀服务集群]
    D --> F[用户服务]
    D --> G[订单服务]
    
    E --> H[Redis集群 - 库存缓存]
    E --> I[消息队列 Kafka]
    
    I --> J[订单处理服务]
    I --> K[库存扣减服务]
    I --> L[支付服务]
    
    J --> M[(MySQL主库)]
    K --> M
    L --> N[(MySQL从库)]
    
    E --> O[预热系统]
    E --> P[限流熔断]

2. 核心技术方案:

前端优化:

javascript
// 前端防重复点击和请求优化
class SeckillClient {
    constructor() {
        this.isSubmitting = false;
        this.token = null;
        this.retryCount = 0;
        this.maxRetries = 3;
    }
    
    async initSeckill(productId) {
        try {
            // 获取秒杀token
            this.token = await this.getSeckillToken(productId);
            
            // 启动心跳检测
            this.startHeartbeat();
            
            // 开启实时库存监控
            this.watchStock(productId);
            
        } catch (error) {
            console.error('秒杀初始化失败:', error);
        }
    }
    
    async submitSeckill(productId) {
        // 防重复提交
        if (this.isSubmitting) {
            return { success: false, message: '请勿重复点击' };
        }
        
        this.isSubmitting = true;
        const startTime = Date.now();
        
        try {
            // 客户端限流
            if (!this.canSubmit()) {
                throw new Error('提交过于频繁,请稍后再试');
            }
            
            const result = await this.callSeckillAPI(productId);
            
            // 请求统计
            this.recordMetric('seckill_submit', Date.now() - startTime);
            
            return result;
            
        } catch (error) {
            // 智能重试机制
            if (this.retryCount < this.maxRetries && this.isRetryableError(error)) {
                this.retryCount++;
                await this.delay(Math.pow(2, this.retryCount) * 100); // 指数退避
                return this.submitSeckill(productId);
            }
            
            throw error;
        } finally {
            this.isSubmitting = false;
        }
    }
    
    canSubmit() {
        const now = Date.now();
        const lastSubmit = localStorage.getItem('lastSeckillSubmit');
        
        // 1秒内只能提交一次
        return !lastSubmit || (now - lastSubmit) > 1000;
    }
    
    async watchStock(productId) {
        // WebSocket实时库存监控
        const ws = new WebSocket(`ws://api.seckill.com/stock/${productId}`);
        
        ws.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.updateStockDisplay(data.stock);
            
            // 库存售罄自动禁用按钮
            if (data.stock <= 0) {
                this.disableSeckillButton();
            }
        };
    }
}

3. 后端核心服务设计:

秒杀服务核心逻辑:

java
@Service
public class SeckillService {
    
    @Autowired private RedisTemplate redisTemplate;
    @Autowired private KafkaTemplate kafkaTemplate;
    @Autowired private BloomFilter bloomFilter;
    
    private static final String STOCK_KEY = "seckill:stock:";
    private static final String USER_KEY = "seckill:user:";
    
    public SeckillResult doSeckill(Long productId, Long userId) {
        // 1. 参数校验和预检查
        if (!preCheck(productId, userId)) {
            return SeckillResult.fail("参数校验失败");
        }
        
        // 2. 用户重复购买检查
        if (isDuplicatePurchase(productId, userId)) {
            return SeckillResult.fail("您已经参与过该商品的秒杀");
        }
        
        // 3. 库存预检查(布隆过滤器)
        if (!bloomFilter.mightContain(productId.toString())) {
            return SeckillResult.fail("商品不存在或已售罄");
        }
        
        // 4. 分布式锁 + 库存扣减
        String lockKey = "seckill:lock:" + productId;
        
        return distributedLock.execute(lockKey, 100, TimeUnit.MILLISECONDS, () -> {
            // 双重检查库存
            Long currentStock = getCurrentStock(productId);
            if (currentStock <= 0) {
                return SeckillResult.fail("商品已售罄");
            }
            
            // 原子性库存扣减
            Long remainStock = decrementStock(productId);
            if (remainStock < 0) {
                // 回滚库存
                incrementStock(productId);
                return SeckillResult.fail("商品已售罄");
            }
            
            // 标记用户已参与
            markUserParticipated(productId, userId);
            
            // 异步创建订单
            createOrderAsync(productId, userId, remainStock);
            
            return SeckillResult.success("秒杀成功", remainStock);
        });
    }
    
    private Long decrementStock(Long productId) {
        String stockKey = STOCK_KEY + productId;
        
        // Lua脚本保证原子性
        String luaScript = 
            "local stock = redis.call('GET', KEYS[1]) " +
            "if stock and tonumber(stock) > 0 then " +
            "  return redis.call('DECR', KEYS[1]) " +
            "else " +
            "  return -1 " +
            "end";
            
        return redisTemplate.execute(
            RedisScript.of(luaScript, Long.class), 
            Collections.singletonList(stockKey)
        );
    }
    
    private void createOrderAsync(Long productId, Long userId, Long remainStock) {
        SeckillOrderEvent event = SeckillOrderEvent.builder()
            .productId(productId)
            .userId(userId)
            .timestamp(System.currentTimeMillis())
            .remainStock(remainStock)
            .build();
            
        // 发送到消息队列异步处理
        kafkaTemplate.send("seckill-order", event);
        
        // 发送库存变化通知
        kafkaTemplate.send("stock-change", new StockChangeEvent(productId, remainStock));
    }
}

4. 库存管理策略:

库存预热和分层扣减:

java
@Component
public class StockManager {
    
    // 多级库存管理
    @Autowired private RedisCluster redisCluster;
    @Autowired private DatabaseService databaseService;
    
    public void initSeckillStock(Long productId, Long totalStock) {
        // 1. 数据库中设置总库存
        databaseService.setTotalStock(productId, totalStock);
        
        // 2. Redis集群分片存储
        int shardCount = 10; // 分10个分片
        Long stockPerShard = totalStock / shardCount;
        
        for (int i = 0; i < shardCount; i++) {
            String shardKey = "seckill:stock:" + productId + ":shard:" + i;
            redisCluster.set(shardKey, stockPerShard.toString());
        }
        
        // 3. 布隆过滤器标记商品存在
        bloomFilter.put(productId.toString());
        
        // 4. 预热本地缓存
        localCache.put("stock:" + productId, totalStock);
    }
    
    public Long decrementStockOptimized(Long productId) {
        // 随机选择分片,减少热点
        int shardIndex = ThreadLocalRandom.current().nextInt(10);
        String shardKey = "seckill:stock:" + productId + ":shard:" + shardIndex;
        
        // Lua脚本原子操作
        String luaScript = 
            "local shard_stock = redis.call('GET', KEYS[1]) " +
            "if shard_stock and tonumber(shard_stock) > 0 then " +
            "  local remain = redis.call('DECR', KEYS[1]) " +
            "  -- 更新总库存 " +
            "  redis.call('DECR', KEYS[2]) " +
            "  return remain " +
            "else " +
            "  return -1 " +
            "end";
        
        List<String> keys = Arrays.asList(shardKey, "seckill:stock:" + productId);
        return redisCluster.eval(luaScript, keys, Collections.emptyList());
    }
    
    // 库存回补机制
    @Scheduled(fixedRate = 1000) // 每秒执行一次
    public void stockReplenishment() {
        // 检查支付超时的订单
        List<TimeoutOrder> timeoutOrders = orderService.getTimeoutOrders();
        
        for (TimeoutOrder order : timeoutOrders) {
            // 回补库存
            incrementStock(order.getProductId());
            // 取消订单
            orderService.cancelOrder(order.getOrderId());
        }
    }
}

5. 消息队列异步处理:

订单处理服务:

java
@KafkaListener(topics = "seckill-order", groupId = "order-group")
public class OrderProcessor {
    
    @Autowired private OrderService orderService;
    @Autowired private UserService userService;
    @Autowired private RedisTemplate redisTemplate;
    
    public void processOrder(SeckillOrderEvent event) {
        try {
            // 1. 用户信息验证
            User user = userService.getUser(event.getUserId());
            if (user == null || !user.isActive()) {
                handleOrderFailure(event, "用户信息异常");
                return;
            }
            
            // 2. 商品信息验证
            Product product = productService.getProduct(event.getProductId());
            if (product == null || !product.isOnSale()) {
                handleOrderFailure(event, "商品信息异常");
                return;
            }
            
            // 3. 创建预订单
            Order order = Order.builder()
                .productId(event.getProductId())
                .userId(event.getUserId())
                .quantity(1)
                .price(product.getSeckillPrice())
                .status(OrderStatus.PENDING_PAYMENT)
                .createTime(new Date(event.getTimestamp()))
                .expireTime(new Date(event.getTimestamp() + 15 * 60 * 1000)) // 15分钟支付
                .build();
            
            orderService.createOrder(order);
            
            // 4. 发送支付通知
            PaymentEvent paymentEvent = PaymentEvent.builder()
                .orderId(order.getId())
                .userId(event.getUserId())
                .amount(product.getSeckillPrice())
                .expireTime(order.getExpireTime())
                .build();
                
            kafkaTemplate.send("payment-notify", paymentEvent);
            
            // 5. 更新用户状态缓存
            updateUserOrderStatus(event.getUserId(), event.getProductId(), order.getId());
            
        } catch (Exception e) {
            log.error("订单处理失败", e);
            handleOrderFailure(event, e.getMessage());
        }
    }
    
    private void handleOrderFailure(SeckillOrderEvent event, String reason) {
        // 1. 回补库存
        stockManager.incrementStock(event.getProductId());
        
        // 2. 清除用户参与标记
        redisTemplate.delete("seckill:user:" + event.getProductId() + ":" + event.getUserId());
        
        // 3. 记录失败日志
        log.warn("订单创建失败: productId={}, userId={}, reason={}", 
            event.getProductId(), event.getUserId(), reason);
    }
}

6. 限流和熔断机制:

多层限流策略:

java
@Component
public class SeckillRateLimiter {
    
    // 1. 接入层限流 - Nginx限流
    /*
    nginx.conf:
    http {
        limit_req_zone $binary_remote_addr zone=seckill:10m rate=10r/s;
        
        server {
            location /api/seckill {
                limit_req zone=seckill burst=20 nodelay;
                proxy_pass http://backend;
            }
        }
    }
    */
    
    // 2. 应用层限流 - 基于用户
    @Autowired private RedisTemplate redisTemplate;
    
    public boolean checkUserRateLimit(Long userId) {
        String key = "rate_limit:user:" + userId;
        String luaScript = 
            "local key = KEYS[1] " +
            "local limit = tonumber(ARGV[1]) " +
            "local window = tonumber(ARGV[2]) " +
            "local current = redis.call('INCR', key) " +
            "if current == 1 then " +
            "  redis.call('EXPIRE', key, window) " +
            "end " +
            "return current <= limit";
        
        Boolean allowed = redisTemplate.execute(
            RedisScript.of(luaScript, Boolean.class),
            Collections.singletonList(key),
            "3", "60" // 1分钟内最多3次请求
        );
        
        return allowed != null && allowed;
    }
    
    // 3. 服务层限流 - 令牌桶
    private final RateLimiter serviceLimiter = RateLimiter.create(1000.0); // 每秒1000个令牌
    
    public boolean tryAcquire() {
        return serviceLimiter.tryAcquire(100, TimeUnit.MILLISECONDS);
    }
    
    // 4. 熔断器
    @Autowired private CircuitBreaker circuitBreaker;
    
    public SeckillResult doSeckillWithCircuitBreaker(Long productId, Long userId) {
        return circuitBreaker.executeSupplier(() -> {
            return seckillService.doSeckill(productId, userId);
        });
    }
}

7. 监控和告警系统:

java
@Component
public class SeckillMonitor {
    
    private final MeterRegistry meterRegistry;
    private final Counter seckillRequestCounter;
    private final Counter seckillSuccessCounter;
    private final Timer seckillLatencyTimer;
    
    public SeckillMonitor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        this.seckillRequestCounter = Counter.builder("seckill.requests.total")
            .description("Total seckill requests")
            .register(meterRegistry);
        this.seckillSuccessCounter = Counter.builder("seckill.requests.success")
            .description("Successful seckill requests")
            .register(meterRegistry);
        this.seckillLatencyTimer = Timer.builder("seckill.requests.latency")
            .description("Seckill request latency")
            .register(meterRegistry);
    }
    
    public void recordSeckillMetrics(SeckillResult result, long latency) {
        seckillRequestCounter.increment();
        
        if (result.isSuccess()) {
            seckillSuccessCounter.increment();
        }
        
        seckillLatencyTimer.record(latency, TimeUnit.MILLISECONDS);
        
        // 实时告警检查
        checkAlerts();
    }
    
    private void checkAlerts() {
        // 检查成功率
        double successRate = seckillSuccessCounter.count() / seckillRequestCounter.count();
        if (successRate < 0.8) {
            alertService.sendAlert("秒杀成功率低于80%: " + successRate);
        }
        
        // 检查平均响应时间
        double avgLatency = seckillLatencyTimer.mean(TimeUnit.MILLISECONDS);
        if (avgLatency > 500) {
            alertService.sendAlert("秒杀平均响应时间超过500ms: " + avgLatency);
        }
    }
}

面试要点:

  • 理解秒杀系统的核心挑战:高并发、库存一致性、系统稳定性
  • 掌握多层防护:前端限流 + 后端限流 + 分布式锁 + 异步处理
  • 了解库存管理的分片策略和原子操作
  • 掌握消息队列在订单处理中的作用和可靠性保证

这个秒杀系统设计涵盖了从前端到后端的完整方案,包括库存管理、限流熔断、异步处理等核心技术点,适合高级工程师和架构师岗位的技术评估。

正在精进