Skip to content

如何设计秒杀系统

一、问题描述

1.1 业务背景

秒杀活动是电商平台最常见的营销手段,通常在特定时间(如00:00:00)开始,有限的商品(如100件iPhone)以极低价格出售,吸引海量用户在同一时刻疯抢。这种场景会产生瞬间的超高并发流量,是对系统架构和性能的极限考验。

典型场景

  • 电商秒杀:淘宝双11、京东618的秒杀活动
  • 票务抢购:演唱会门票、火车票春运抢票
  • 优惠券抢购:外卖平台、打车平台的红包雨

1.2 核心功能

  1. 商品展示:秒杀商品信息、库存、倒计时
  2. 秒杀抢购:用户点击"立即抢购",系统判断是否成功
  3. 订单生成:抢购成功后生成订单,限时支付
  4. 库存管理:实时扣减库存,防止超卖
  5. 用户限制:每人限购1件,防止黄牛和刷单

1.3 技术挑战

瞬时高并发

  • 平时QPS 1000,秒杀瞬间QPS 10万+
  • 数据库、缓存、网络带宽面临巨大压力
  • 系统响应时间要求<100ms

防超卖问题

  • 库存100件,10万人同时抢购
  • 必须保证只有100人能够成功
  • 任何情况下都不能多卖

用户体验

  • 大部分用户注定失败,如何友好提示
  • 成功用户要快速完成下单
  • 防止系统崩溃影响正常业务

安全问题

  • 防止黄牛使用脚本刷单
  • 防止恶意请求攻击系统
  • 防止内部作弊

1.4 面试考察点

  • 高并发处理能力:如何应对瞬间10万+QPS?
  • 数据一致性:如何保证库存扣减的准确性?
  • 系统设计能力:如何设计合理的架构和流程?
  • 技术选型能力:如何选择合适的技术栈?
  • 优化思维:如何从多个维度优化性能?

二、需求分析

2.1 功能性需求

需求描述优先级
FR1用户可以查看秒杀商品信息和库存P0
FR2用户可以在秒杀开始时抢购商品P0
FR3系统判断抢购是否成功并返回结果P0
FR4抢购成功后生成订单,用户支付P0
FR5实时扣减库存,防止超卖P0
FR6每个用户只能抢购一件商品P1
FR7支持秒杀活动的创建和管理P1
FR8提供秒杀活动的实时统计P2

2.2 非功能性需求

性能需求

  • 秒杀开始时,系统需支持10万+QPS
  • 抢购接口响应时间<100ms(P95)
  • 数据库查询响应时间<50ms
  • Redis缓存命中率>99%

可用性需求

  • 秒杀系统可用性99.99%(年故障时间<53分钟)
  • 支持灰度发布和快速回滚
  • 故障自动切换,恢复时间<30秒

一致性需求

  • 库存扣减必须保证强一致性,绝对不能超卖
  • 订单数据最终一致性(允许短暂延迟)
  • 库存和订单数据可追溯和对账

安全性需求

  • 防止脚本刷单和恶意请求
  • 限制单用户请求频率(1秒最多5次)
  • 支持黑名单和风控规则
  • 防止DDoS攻击

可扩展性

  • 支持水平扩展,应对更高并发
  • 支持多种秒杀场景(商品秒杀、优惠券秒杀)
  • 支持多种支付方式

2.3 约束条件

  1. 时间约束:秒杀活动有明确的开始和结束时间
  2. 库存约束:库存有限且固定,不能动态增加
  3. 用户约束:用户已登录,有userId
  4. 支付约束:订单生成后15分钟内必须支付,否则释放库存

2.4 边界场景

  1. 秒杀未开始:用户提前请求,系统返回"秒杀未开始"
  2. 秒杀已结束:用户延迟请求,系统返回"秒杀已结束"
  3. 库存为0:库存售罄,系统返回"已售罄"
  4. 重复抢购:用户重复点击,系统返回"已抢购"
  5. 支付超时:订单超时未支付,自动取消并释放库存
  6. 并发冲突:多个请求同时扣减最后一件库存,只有一个成功

三、技术选型

3.1 方案对比

方案A:MySQL直接扣减库存

实现方式

sql
UPDATE seckill_product 
SET stock = stock - 1 
WHERE id = #{productId} AND stock > 0

优点

  • 实现简单,逻辑清晰
  • 利用数据库事务保证一致性
  • 无需引入额外组件

缺点

  • 数据库成为瓶颈,QPS极限约1000
  • 大量请求直接打到数据库,可能宕机
  • 响应时间长,用户体验差

适用场景:并发量<1000的小型秒杀

方案B:Redis原子扣减 + 异步入库

实现方式

lua
-- Redis Lua脚本保证原子性
local stock = redis.call('get', KEYS[1])
if tonumber(stock) <= 0 then
    return 0
end
redis.call('decr', KEYS[1])
return 1

优点

  • Redis性能极高,QPS可达10万+
  • 原子操作保证不会超卖
  • 响应速度快,<10ms
  • 降低数据库压力

缺点

  • Redis和MySQL数据可能不一致
  • 需要处理异步失败的情况
  • Redis宕机可能导致数据丢失

适用场景:高并发秒杀(推荐)

方案C:预扣库存 + 消息队列

实现方式

  1. Redis预扣库存
  2. 请求写入消息队列
  3. 消费者异步处理订单

优点

  • 完美削峰填谷,保护后端系统
  • 解耦前后端,提升可维护性
  • 支持流量重播和幂等处理

缺点

  • 架构复杂度高
  • 消息延迟影响用户体验
  • 需要处理消息积压和失败

适用场景:超大规模秒杀(10万+QPS)

3.2 推荐方案

综合方案:Redis原子扣减 + 消息队列 + 异步入库

这个方案结合了方案B和方案C的优点:

  • 用Redis保证高性能和原子性
  • 用消息队列削峰和解耦
  • 用MySQL保证数据持久化
  • 分层防护,多重保障

3.3 技术栈清单

组件技术选型作用理由
Web服务器Nginx负载均衡、限流高性能、成熟稳定
应用服务Go/Java/Spring Boot业务逻辑高性能、生态成熟
缓存Redis库存缓存、分布式锁高性能、原子操作
数据库MySQL订单和商品数据事务支持、数据可靠
消息队列Kafka/RocketMQ异步处理、削峰填谷高吞吐、可靠性高
限流组件Sentinel/Guava RateLimiter接口限流成熟稳定、易于集成
分布式锁Redis/Redlock防止重复抢购高性能、实现简单
监控Prometheus + Grafana实时监控开源、功能强大

四、架构设计

4.1 系统架构图

mermaid
graph TB
    subgraph 客户端层
        A[用户浏览器] --> B[CDN静态资源]
    end
    
    subgraph 接入层
        A --> C[Nginx负载均衡]
        C --> D[限流/黑名单]
    end
    
    subgraph 应用层
        D --> E1[秒杀服务1]
        D --> E2[秒杀服务2]
        D --> E3[秒杀服务N]
    end
    
    subgraph 缓存层
        E1 --> F[Redis集群]
        E2 --> F
        E3 --> F
        F --> F1[库存缓存]
        F --> F2[用户限购缓存]
        F --> F3[分布式锁]
    end
    
    subgraph 消息层
        E1 --> G[Kafka集群]
        E2 --> G
        E3 --> G
        G --> H1[订单消费者1]
        G --> H2[订单消费者2]
    end
    
    subgraph 数据层
        H1 --> I[MySQL主从]
        H2 --> I
        I --> I1[订单表]
        I --> I2[商品表]
    end
    
    subgraph 监控层
        E1 --> J[Prometheus]
        E2 --> J
        E3 --> J
        J --> K[Grafana]
    end

4.2 核心流程

秒杀前准备流程

mermaid
sequenceDiagram
    participant Admin as 管理员
    participant App as 秒杀服务
    participant Redis as Redis
    participant MySQL as MySQL
    participant CDN as CDN

    Admin->>App: 创建秒杀活动
    App->>MySQL: 保存活动信息
    App->>Redis: 预热库存到Redis
    Note over Redis: SET seckill:stock:1001 100
    App->>Redis: 初始化活动状态
    Note over Redis: SET seckill:status:1001 "not_started"
    App->>CDN: 推送静态页面
    App->>Admin: 返回成功

秒杀抢购流程

mermaid
sequenceDiagram
    participant User as 用户
    participant Nginx as Nginx
    participant App as 秒杀服务
    participant Redis as Redis
    participant Kafka as Kafka
    participant Consumer as 订单消费者
    participant MySQL as MySQL

    User->>Nginx: 点击抢购
    Nginx->>Nginx: IP限流检查
    Nginx->>App: 转发请求
    
    App->>App: 参数验证
    App->>Redis: 检查活动状态
    alt 活动未开始或已结束
        Redis-->>App: 状态不可用
        App-->>User: 返回"活动未开始/已结束"
    end
    
    App->>Redis: 检查用户是否已抢购
    Note over Redis: EXISTS seckill:user:1001:10001
    alt 已抢购
        Redis-->>App: 已存在
        App-->>User: 返回"已抢购过"
    end
    
    App->>Redis: Lua脚本原子扣减库存
    Note over Redis: EVAL script 1 seckill:stock:1001
    alt 库存不足
        Redis-->>App: 返回0
        App-->>User: 返回"已售罄"
    end
    
    Redis-->>App: 扣减成功
    App->>Redis: 记录用户已抢购
    Note over Redis: SET seckill:user:1001:10001 1 EX 86400
    App->>Kafka: 发送订单创建消息
    App-->>User: 返回"抢购成功"
    
    Kafka->>Consumer: 消费订单消息
    Consumer->>MySQL: 创建订单记录
    Consumer->>MySQL: 扣减数据库库存
    Consumer->>Consumer: 发送通知/短信

4.3 数据库设计

商品表(seckill_product)

sql
CREATE TABLE seckill_product (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID',
    product_name VARCHAR(200) NOT NULL COMMENT '商品名称',
    original_price DECIMAL(10,2) NOT NULL COMMENT '原价',
    seckill_price DECIMAL(10,2) NOT NULL COMMENT '秒杀价',
    total_stock INT NOT NULL COMMENT '总库存',
    available_stock INT NOT NULL COMMENT '可用库存',
    start_time DATETIME NOT NULL COMMENT '秒杀开始时间',
    end_time DATETIME NOT NULL COMMENT '秒杀结束时间',
    status TINYINT DEFAULT 0 COMMENT '状态:0未开始 1进行中 2已结束',
    version INT DEFAULT 0 COMMENT '乐观锁版本号',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_start_time (start_time),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀商品表';

订单表(seckill_order)

sql
CREATE TABLE seckill_order (
    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
    order_no VARCHAR(64) UNIQUE NOT NULL COMMENT '订单编号',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    product_id BIGINT NOT NULL COMMENT '商品ID',
    product_name VARCHAR(200) NOT NULL COMMENT '商品名称',
    seckill_price DECIMAL(10,2) NOT NULL COMMENT '秒杀价格',
    status TINYINT DEFAULT 0 COMMENT '订单状态:0待支付 1已支付 2已取消',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    pay_time DATETIME COMMENT '支付时间',
    expire_time DATETIME NOT NULL COMMENT '过期时间',
    INDEX idx_user_product (user_id, product_id),
    INDEX idx_order_no (order_no),
    INDEX idx_status_expire (status, expire_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀订单表';

4.4 接口设计

秒杀抢购接口

http
POST /api/seckill/purchase
Content-Type: application/json

Request:
{
    "productId": 1001,
    "userId": 10001,
    "token": "user_token"
}

Response Success:
{
    "code": 200,
    "message": "抢购成功",
    "data": {
        "orderId": 123456,
        "orderNo": "SK202401011200001",
        "expireTime": "2024-01-01 12:15:00"
    }
}

Response Fail:
{
    "code": 40001,
    "message": "已售罄",
    "data": null
}

五、核心实现

5.1 初级版本:基础MySQL实现

点击查看Go语言实现
go
package seckill

import (
    "database/sql"
    "errors"
    "time"
)

// 初级版本:直接使用MySQL扣减库存
// 优点:实现简单,逻辑清晰
// 缺点:性能差,QPS极限约1000,容易造成数据库压力

type SimpleSeckillService struct {
    db *sql.DB
}

// 秒杀抢购
func (s *SimpleSeckillService) Purchase(productID, userID int64) (string, error) {
    // 1. 开启事务
    tx, err := s.db.Begin()
    if err != nil {
        return "", err
    }
    defer tx.Rollback()

    // 2. 检查用户是否已抢购
    var count int
    err = tx.QueryRow(`
        SELECT COUNT(*) FROM seckill_order 
        WHERE product_id = ? AND user_id = ? AND status IN (0, 1)
    `, productID, userID).Scan(&count)
    if err != nil {
        return "", err
    }
    if count > 0 {
        return "", errors.New("您已经抢购过了")
    }

    // 3. 检查库存并扣减(关键SQL)
    result, err := tx.Exec(`
        UPDATE seckill_product 
        SET available_stock = available_stock - 1,
            version = version + 1
        WHERE id = ? 
          AND available_stock > 0
          AND status = 1
          AND start_time <= NOW()
          AND end_time >= NOW()
    `, productID)
    if err != nil {
        return "", err
    }

    affected, _ := result.RowsAffected()
    if affected == 0 {
        return "", errors.New("库存不足或活动已结束")
    }

    // 4. 创建订单
    orderNo := generateOrderNo()
    expireTime := time.Now().Add(15 * time.Minute)
    
    _, err = tx.Exec(`
        INSERT INTO seckill_order 
        (order_no, user_id, product_id, product_name, seckill_price, expire_time)
        SELECT ?, ?, id, product_name, seckill_price, ?
        FROM seckill_product WHERE id = ?
    `, orderNo, userID, expireTime, productID)
    if err != nil {
        return "", err
    }

    // 5. 提交事务
    if err := tx.Commit(); err != nil {
        return "", err
    }

    return orderNo, nil
}

// 生成订单号
func generateOrderNo() string {
    return fmt.Sprintf("SK%s%06d", 
        time.Now().Format("20060102150405"), 
        rand.Intn(1000000))
}
点击查看Java语言实现
java
@Service
public class SimpleSeckillService {
    
    @Autowired
    private DataSource dataSource;
    
    /**
     * 初级版本:直接使用MySQL扣减库存
     */
    @Transactional(rollbackFor = Exception.class)
    public String purchase(Long productId, Long userId) throws Exception {
        Connection conn = dataSource.getConnection();
        
        try {
            // 1. 检查用户是否已抢购
            String checkSql = "SELECT COUNT(*) FROM seckill_order " +
                            "WHERE product_id = ? AND user_id = ? AND status IN (0, 1)";
            PreparedStatement checkStmt = conn.prepareStatement(checkSql);
            checkStmt.setLong(1, productId);
            checkStmt.setLong(2, userId);
            ResultSet rs = checkStmt.executeQuery();
            if (rs.next() && rs.getInt(1) > 0) {
                throw new BusinessException("您已经抢购过了");
            }
            
            // 2. 扣减库存
            String updateSql = "UPDATE seckill_product " +
                             "SET available_stock = available_stock - 1, " +
                             "    version = version + 1 " +
                             "WHERE id = ? AND available_stock > 0 " +
                             "  AND status = 1 " +
                             "  AND start_time <= NOW() AND end_time >= NOW()";
            PreparedStatement updateStmt = conn.prepareStatement(updateSql);
            updateStmt.setLong(1, productId);
            int affected = updateStmt.executeUpdate();
            
            if (affected == 0) {
                throw new BusinessException("库存不足或活动已结束");
            }
            
            // 3. 创建订单
            String orderNo = generateOrderNo();
            Timestamp expireTime = new Timestamp(
                System.currentTimeMillis() + 15 * 60 * 1000
            );
            
            String insertSql = "INSERT INTO seckill_order " +
                             "(order_no, user_id, product_id, product_name, " +
                             " seckill_price, expire_time) " +
                             "SELECT ?, ?, id, product_name, seckill_price, ? " +
                             "FROM seckill_product WHERE id = ?";
            PreparedStatement insertStmt = conn.prepareStatement(insertSql);
            insertStmt.setString(1, orderNo);
            insertStmt.setLong(2, userId);
            insertStmt.setTimestamp(3, expireTime);
            insertStmt.setLong(4, productId);
            insertStmt.executeUpdate();
            
            return orderNo;
            
        } finally {
            conn.close();
        }
    }
    
    private String generateOrderNo() {
        return "SK" + LocalDateTime.now().format(
            DateTimeFormatter.ofPattern("yyyyMMddHHmmss")
        ) + RandomUtils.nextInt(100000, 999999);
    }
}

性能测试

  • QPS: ~1000
  • 响应时间: 50-200ms
  • 并发能力: 支持<5000并发

5.2 中级版本:Redis缓存 + 异步处理

go
package seckill

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
    "github.com/segmentio/kafka-go"
    "time"
)

// 中级版本:Redis原子扣减 + Kafka异步入库
// 优点:性能大幅提升,QPS可达10万+
// 缺点:需要处理Redis和MySQL的数据一致性

type RedisSeckillService struct {
    redis  *redis.Client
    kafka  *kafka.Writer
    ctx    context.Context
}

// Lua脚本:原子扣减库存
const luaDecrStock = `
local stock_key = KEYS[1]
local stock = tonumber(redis.call('GET', stock_key))

if stock == nil then
    return -1  -- 库存键不存在
end

if stock <= 0 then
    return 0   -- 库存不足
end

redis.call('DECR', stock_key)
return 1       -- 扣减成功
`

// 秒杀抢购
func (s *RedisSeckillService) Purchase(productID, userID int64) (string, error) {
    // 1. 检查活动状态
    statusKey := fmt.Sprintf("seckill:status:%d", productID)
    status, err := s.redis.Get(s.ctx, statusKey).Result()
    if err == redis.Nil {
        return "", fmt.Errorf("活动不存在")
    }
    if status != "running" {
        return "", fmt.Errorf("活动未开始或已结束")
    }

    // 2. 检查用户是否已抢购(使用SET实现)
    userKey := fmt.Sprintf("seckill:user:%d:%d", productID, userID)
    success, err := s.redis.SetNX(s.ctx, userKey, "1", 24*time.Hour).Result()
    if err != nil {
        return "", err
    }
    if !success {
        return "", fmt.Errorf("您已经抢购过了")
    }

    // 3. 原子扣减库存(使用Lua脚本)
    stockKey := fmt.Sprintf("seckill:stock:%d", productID)
    result, err := s.redis.Eval(s.ctx, luaDecrStock, []string{stockKey}).Int()
    if err != nil {
        // 扣减失败,回滚用户抢购标记
        s.redis.Del(s.ctx, userKey)
        return "", err
    }

    if result == -1 {
        s.redis.Del(s.ctx, userKey)
        return "", fmt.Errorf("库存键不存在")
    } else if result == 0 {
        s.redis.Del(s.ctx, userKey)
        return "", fmt.Errorf("库存不足")
    }

    // 4. 生成订单号
    orderNo := generateOrderNo()

    // 5. 发送消息到Kafka异步创建订单
    message := kafka.Message{
        Key:   []byte(fmt.Sprintf("%d", userID)),
        Value: []byte(fmt.Sprintf(`{
            "orderNo": "%s",
            "productId": %d,
            "userId": %d,
            "timestamp": %d
        }`, orderNo, productID, userID, time.Now().Unix())),
    }

    if err := s.kafka.WriteMessages(s.ctx, message); err != nil {
        // Kafka写入失败,需要补偿机制
        // 这里可以写入本地队列或数据库,稍后重试
        return "", err
    }

    return orderNo, nil
}

// 订单消费者(异步处理)
type OrderConsumer struct {
    kafka  *kafka.Reader
    db     *sql.DB
    redis  *redis.Client
}

func (c *OrderConsumer) Consume() {
    for {
        msg, err := c.kafka.ReadMessage(context.Background())
        if err != nil {
            log.Printf("读取消息失败: %v", err)
            continue
        }

        // 解析消息
        var order struct {
            OrderNo   string `json:"orderNo"`
            ProductID int64  `json:"productId"`
            UserID    int64  `json:"userId"`
            Timestamp int64  `json:"timestamp"`
        }
        json.Unmarshal(msg.Value, &order)

        // 创建订单(幂等处理)
        if err := c.createOrder(&order); err != nil {
            log.Printf("创建订单失败: %v", err)
            // 失败重试或写入死信队列
        }
    }
}

func (c *OrderConsumer) createOrder(order *OrderOrder) error {
    // 1. 查询商品信息
    var product struct {
        Name  string
        Price float64
    }
    err := c.db.QueryRow(`
        SELECT product_name, seckill_price 
        FROM seckill_product WHERE id = ?
    `, order.ProductID).Scan(&product.Name, &product.Price)
    if err != nil {
        return err
    }

    // 2. 插入订单(幂等:使用order_no唯一索引)
    expireTime := time.Now().Add(15 * time.Minute)
    _, err = c.db.Exec(`
        INSERT IGNORE INTO seckill_order 
        (order_no, user_id, product_id, product_name, seckill_price, expire_time, status)
        VALUES (?, ?, ?, ?, ?, ?, 0)
    `, order.OrderNo, order.UserID, order.ProductID, product.Name, product.Price, expireTime)

    return err
}
java
@Service
public class RedisSeckillService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    // Lua脚本:原子扣减库存
    private static final String LUA_DECR_STOCK = 
        "local stock = tonumber(redis.call('GET', KEYS[1]))\n" +
        "if stock == nil then return -1 end\n" +
        "if stock <= 0 then return 0 end\n" +
        "redis.call('DECR', KEYS[1])\n" +
        "return 1";
    
    /**
     * 中级版本:Redis原子扣减 + Kafka异步处理
     */
    public String purchase(Long productId, Long userId) throws Exception {
        // 1. 检查活动状态
        String statusKey = "seckill:status:" + productId;
        String status = redisTemplate.opsForValue().get(statusKey);
        if (!"running".equals(status)) {
            throw new BusinessException("活动未开始或已结束");
        }
        
        // 2. 检查用户是否已抢购
        String userKey = String.format("seckill:user:%d:%d", productId, userId);
        Boolean success = redisTemplate.opsForValue()
            .setIfAbsent(userKey, "1", 24, TimeUnit.HOURS);
        if (!Boolean.TRUE.equals(success)) {
            throw new BusinessException("您已经抢购过了");
        }
        
        // 3. 原子扣减库存
        String stockKey = "seckill:stock:" + productId;
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(LUA_DECR_STOCK);
        script.setResultType(Long.class);
        
        Long result = redisTemplate.execute(
            script, 
            Collections.singletonList(stockKey)
        );
        
        if (result == null || result == -1) {
            redisTemplate.delete(userKey);
            throw new BusinessException("库存键不存在");
        } else if (result == 0) {
            redisTemplate.delete(userKey);
            throw new BusinessException("库存不足");
        }
        
        // 4. 生成订单号
        String orderNo = generateOrderNo();
        
        // 5. 发送Kafka消息异步创建订单
        OrderMessage message = new OrderMessage();
        message.setOrderNo(orderNo);
        message.setProductId(productId);
        message.setUserId(userId);
        message.setTimestamp(System.currentTimeMillis());
        
        kafkaTemplate.send("seckill-order-topic", 
            JSON.toJSONString(message));
        
        return orderNo;
    }
}

// Kafka消费者
@Component
public class OrderConsumer {
    
    @Autowired
    private SeckillOrderMapper orderMapper;
    
    @Autowired
    private SeckillProductMapper productMapper;
    
    @KafkaListener(topics = "seckill-order-topic", groupId = "seckill-order-group")
    public void consume(String message) {
        try {
            OrderMessage orderMsg = JSON.parseObject(message, OrderMessage.class);
            
            // 查询商品信息
            SeckillProduct product = productMapper.selectById(orderMsg.getProductId());
            
            // 创建订单(幂等)
            SeckillOrder order = new SeckillOrder();
            order.setOrderNo(orderMsg.getOrderNo());
            order.setUserId(orderMsg.getUserId());
            order.setProductId(orderMsg.getProductId());
            order.setProductName(product.getProductName());
            order.setSeckillPrice(product.getSeckillPrice());
            order.setExpireTime(
                new Date(System.currentTimeMillis() + 15 * 60 * 1000)
            );
            order.setStatus(0);
            
            // INSERT IGNORE 实现幂等
            orderMapper.insertIgnore(order);
            
        } catch (Exception e) {
            log.error("处理订单消息失败", e);
            // 重试或进入死信队列
        }
    }
}
python
import redis
import json
import time
from kafka import KafkaProducer, KafkaConsumer

class RedisSeckillService:
    """中级版本:Redis原子扣减 + Kafka异步处理"""
    
    def __init__(self, redis_client, kafka_producer):
        self.redis = redis_client
        self.kafka = kafka_producer
        
        # Lua脚本:原子扣减库存
        self.lua_decr_stock = """
        local stock = tonumber(redis.call('GET', KEYS[1]))
        if stock == nil then return -1 end
        if stock <= 0 then return 0 end
        redis.call('DECR', KEYS[1])
        return 1
        """
        self.lua_script = self.redis.register_script(self.lua_decr_stock)
    
    def purchase(self, product_id, user_id):
        """秒杀抢购"""
        # 1. 检查活动状态
        status_key = f"seckill:status:{product_id}"
        status = self.redis.get(status_key)
        if status != b'running':
            return None, "活动未开始或已结束"
        
        # 2. 检查用户是否已抢购
        user_key = f"seckill:user:{product_id}:{user_id}"
        if not self.redis.setnx(user_key, "1"):
            return None, "您已经抢购过了"
        self.redis.expire(user_key, 86400)  # 24小时过期
        
        # 3. 原子扣减库存
        stock_key = f"seckill:stock:{product_id}"
        result = self.lua_script(keys=[stock_key])
        
        if result == -1:
            self.redis.delete(user_key)
            return None, "库存键不存在"
        elif result == 0:
            self.redis.delete(user_key)
            return None, "库存不足"
        
        # 4. 生成订单号
        order_no = self.generate_order_no()
        
        # 5. 发送Kafka消息
        message = {
            "orderNo": order_no,
            "productId": product_id,
            "userId": user_id,
            "timestamp": int(time.time())
        }
        
        self.kafka.send('seckill-order-topic', 
                       value=json.dumps(message).encode())
        
        return order_no, "抢购成功"
    
    def generate_order_no(self):
        return f"SK{time.strftime('%Y%m%d%H%M%S')}{random.randint(100000, 999999)}"


# Kafka消费者
class OrderConsumer:
    """订单消费者:异步创建订单"""
    
    def __init__(self, kafka_consumer, db_connection):
        self.consumer = kafka_consumer
        self.db = db_connection
    
    def consume(self):
        """消费订单消息"""
        for message in self.consumer:
            try:
                order_data = json.loads(message.value.decode())
                self.create_order(order_data)
            except Exception as e:
                print(f"处理订单失败: {e}")
    
    def create_order(self, order_data):
        """创建订单(幂等)"""
        cursor = self.db.cursor()
        
        # 查询商品信息
        cursor.execute(
            "SELECT product_name, seckill_price FROM seckill_product WHERE id = %s",
            (order_data['productId'],)
        )
        product = cursor.fetchone()
        
        # 插入订单(使用 INSERT IGNORE 实现幂等)
        expire_time = time.time() + 15 * 60
        cursor.execute("""
            INSERT IGNORE INTO seckill_order 
            (order_no, user_id, product_id, product_name, seckill_price, 
             expire_time, status)
            VALUES (%s, %s, %s, %s, %s, FROM_UNIXTIME(%s), 0)
        """, (
            order_data['orderNo'],
            order_data['userId'],
            order_data['productId'],
            product[0],
            product[1],
            expire_time
        ))
        
        self.db.commit()

性能测试

  • QPS: ~50,000
  • 响应时间: 10-30ms
  • 并发能力: 支持10万+并发

5.3 高级版本:多级防护 + 限流降级

点击查看完整实现(Go语言)
go
package seckill

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
    "github.com/segmentio/kafka-go"
    "golang.org/x/time/rate"
    "sync"
    "time"
)

// 高级版本:多级防护 + 限流降级 + 分布式锁
// 优点:完整的保护体系,可应对超大规模并发
// 缺点:架构复杂,维护成本高

type AdvancedSeckillService struct {
    redis       *redis.Client
    kafka       *kafka.Writer
    rateLimiter *rate.Limiter
    localCache  sync.Map
    ctx         context.Context
}

// 本地缓存配置
type LocalCacheConfig struct {
    ProductInfo  time.Duration // 商品信息缓存时间
    StockInfo    time.Duration // 库存信息缓存时间
}

func NewAdvancedSeckillService(redis *redis.Client, kafka *kafka.Writer) *AdvancedSeckillService {
    return &AdvancedSeckillService{
        redis:       redis,
        kafka:       kafka,
        rateLimiter: rate.NewLimiter(100000, 200000), // QPS限制10万,突发20万
        localCache:  sync.Map{},
        ctx:         context.Background(),
    }
}

// 第一层防护:前置检查(本地缓存)
func (s *AdvancedSeckillService) preCheck(productID int64) error {
    // 检查本地缓存的活动状态
    cacheKey := fmt.Sprintf("product_status_%d", productID)
    if val, ok := s.localCache.Load(cacheKey); ok {
        cached := val.(CachedStatus)
        if time.Now().Before(cached.ExpireTime) {
            if cached.Status != "running" {
                return fmt.Errorf("活动未开始或已结束")
            }
            return nil
        }
    }

    // 从Redis加载到本地缓存
    statusKey := fmt.Sprintf("seckill:status:%d", productID)
    status, err := s.redis.Get(s.ctx, statusKey).Result()
    if err != nil {
        return err
    }

    s.localCache.Store(cacheKey, CachedStatus{
        Status:     status,
        ExpireTime: time.Now().Add(1 * time.Second),
    })

    if status != "running" {
        return fmt.Errorf("活动未开始或已结束")
    }

    return nil
}

// 第二层防护:限流
func (s *AdvancedSeckillService) rateLimit() error {
    if !s.rateLimiter.Allow() {
        return fmt.Errorf("系统繁忙,请稍后重试")
    }
    return nil
}

// 第三层防护:分布式锁防止用户重复抢购
func (s *AdvancedSeckillService) acquireUserLock(productID, userID int64) (bool, error) {
    lockKey := fmt.Sprintf("seckill:lock:user:%d:%d", productID, userID)
    
    // 尝试获取锁,30秒过期
    success, err := s.redis.SetNX(s.ctx, lockKey, "1", 30*time.Second).Result()
    if err != nil {
        return false, err
    }
    
    return success, nil
}

// 释放用户锁
func (s *AdvancedSeckillService) releaseUserLock(productID, userID int64) {
    lockKey := fmt.Sprintf("seckill:lock:user:%d:%d", productID, userID)
    s.redis.Del(s.ctx, lockKey)
}

// Lua脚本:原子扣减库存(带库存检查)
const luaDecrStockAdvanced = `
local stock_key = KEYS[1]
local stock = tonumber(redis.call('GET', stock_key))

if stock == nil then
    return -1  -- 库存键不存在
end

if stock <= 0 then
    return 0   -- 库存不足
end

-- 检查是否已经低于预警阈值(如10%)
local total_stock_key = KEYS[2]
local total_stock = tonumber(redis.call('GET', total_stock_key))
local threshold = math.floor(total_stock * 0.1)

if stock <= threshold then
    redis.call('PUBLISH', 'seckill:alert', 
        string.format('product=%s,stock=%d,threshold=%d', KEYS[1], stock, threshold))
end

redis.call('DECR', stock_key)
return 1       -- 扣减成功
`

// 秒杀抢购(完整版)
func (s *AdvancedSeckillService) Purchase(productID, userID int64) (string, error) {
    // === 第一层:前置检查(本地缓存) ===
    if err := s.preCheck(productID); err != nil {
        return "", err
    }

    // === 第二层:限流 ===
    if err := s.rateLimit(); err != nil {
        return "", err
    }

    // === 第三层:获取用户锁 ===
    locked, err := s.acquireUserLock(productID, userID)
    if err != nil {
        return "", err
    }
    if !locked {
        return "", fmt.Errorf("您已经在抢购中,请勿重复提交")
    }
    defer s.releaseUserLock(productID, userID)

    // === 第四层:检查用户是否已抢购成功 ===
    userKey := fmt.Sprintf("seckill:user:%d:%d", productID, userID)
    exists, err := s.redis.Exists(s.ctx, userKey).Result()
    if err != nil {
        return "", err
    }
    if exists > 0 {
        return "", fmt.Errorf("您已经抢购过了")
    }

    // === 第五层:原子扣减库存 ===
    stockKey := fmt.Sprintf("seckill:stock:%d", productID)
    totalStockKey := fmt.Sprintf("seckill:total_stock:%d", productID)
    
    result, err := s.redis.Eval(s.ctx, luaDecrStockAdvanced, 
        []string{stockKey, totalStockKey}).Int()
    if err != nil {
        return "", err
    }

    if result == -1 {
        return "", fmt.Errorf("库存键不存在")
    } else if result == 0 {
        return "", fmt.Errorf("库存不足")
    }

    // === 第六层:记录用户已抢购 ===
    s.redis.SetNX(s.ctx, userKey, "1", 24*time.Hour)

    // === 第七层:生成订单并发送消息 ===
    orderNo := generateOrderNo()
    message := kafka.Message{
        Key:   []byte(fmt.Sprintf("%d", userID)),
        Value: []byte(fmt.Sprintf(`{
            "orderNo": "%s",
            "productId": %d,
            "userId": %d,
            "timestamp": %d
        }`, orderNo, productID, userID, time.Now().Unix())),
    }

    // 重试机制:Kafka写入失败则重试3次
    var writeErr error
    for i := 0; i < 3; i++ {
        if writeErr = s.kafka.WriteMessages(s.ctx, message); writeErr == nil {
            break
        }
        time.Sleep(time.Duration(i+1) * 100 * time.Millisecond)
    }

    if writeErr != nil {
        // Kafka失败,回滚Redis数据
        s.redis.Incr(s.ctx, stockKey)
        s.redis.Del(s.ctx, userKey)
        return "", fmt.Errorf("系统繁忙,请稍后重试")
    }

    return orderNo, nil
}

// 库存告警监听
func (s *AdvancedSeckillService) StartStockAlertListener() {
    pubsub := s.redis.Subscribe(s.ctx, "seckill:alert")
    defer pubsub.Close()

    ch := pubsub.Channel()
    for msg := range ch {
        // 发送告警通知
        log.Printf("库存告警: %s", msg.Payload)
        // 可以发送短信、邮件、钉钉等通知
    }
}

性能测试

  • QPS: ~100,000
  • 响应时间: 5-20ms
  • 并发能力: 支持百万级并发
  • 防护级别: 7层防护

5.4 专家版本:全链路优化

专家版本包含:

  • 页面静态化:商品详情页静态化,减少服务器压力
  • CDN加速:静态资源和页面通过CDN分发
  • 热点数据识别:自动识别热点商品,提前预热
  • 动态扩容:根据流量自动扩容应用服务器
  • 多级缓存:浏览器缓存 + CDN缓存 + 本地缓存 + Redis缓存
  • 监控告警:全链路监控,异常自动告警
  • 容灾降级:核心功能降级开关,保证系统稳定

(完整代码见项目代码库)

六、性能优化

6.1 性能瓶颈分析

层级瓶颈影响优先级
网络层带宽不足请求丢失P0
接入层Nginx连接数连接拒绝P0
应用层线程池满请求排队P0
缓存层Redis单点缓存失效P0
消息层Kafka积压订单延迟P1
数据层MySQL锁等待响应变慢P1

6.2 优化策略

6.2.1 前端优化

html
<!-- 页面静态化 + CDN -->
<!DOCTYPE html>
<html>
<head>
    <!-- CSS通过CDN加载 -->
    <link rel="stylesheet" href="https://cdn.example.com/seckill/style.css">
</head>
<body>
    <!-- 商品信息静态化 -->
    <div class="product-info">
        <h1>iPhone 15 Pro</h1>
        <p class="price">秒杀价:¥5999</p>
        <p class="stock">仅剩 <span id="stock">100</span> 件</p>
        <!-- 倒计时在客户端计算,减轻服务器压力 -->
        <p class="countdown" id="countdown">距离开始还有:00:00:05</p>
    </div>
    
    <!-- 秒杀按钮 -->
    <button id="seckillBtn" disabled>立即抢购</button>
    
    <script>
        // 客户端倒计时
        const startTime = new Date('2024-01-01 12:00:00').getTime();
        setInterval(() => {
            const now = Date.now();
            const diff = startTime - now;
            if (diff <= 0) {
                document.getElementById('seckillBtn').disabled = false;
                document.getElementById('countdown').textContent = '马上抢!';
            } else {
                const seconds = Math.floor(diff / 1000);
                document.getElementById('countdown').textContent = 
                    `距离开始还有:00:00:${seconds.toString().padStart(2, '0')}`;
            }
        }, 100);
        
        // 防止重复点击
        let clicking = false;
        document.getElementById('seckillBtn').onclick = function() {
            if (clicking) return;
            clicking = true;
            
            // 调用抢购接口
            fetch('/api/seckill/purchase', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({productId: 1001, userId: 10001})
            })
            .then(response => response.json())
            .then(data => {
                if (data.code === 200) {
                    alert('抢购成功!订单号:' + data.data.orderNo);
                } else {
                    alert(data.message);
                }
            })
            .finally(() => {
                clicking = false;
            });
        };
    </script>
</body>
</html>

6.2.2 Nginx配置优化

nginx
# Nginx配置优化
http {
    # 连接数优化
    worker_processes auto;
    worker_connections 10000;
    
    # 开启keepalive
    keepalive_timeout 65;
    keepalive_requests 100;
    
    # 限流配置
    limit_req_zone $binary_remote_addr zone=seckill:10m rate=10r/s;
    
    upstream seckill_backend {
        # 最小连接数负载均衡
        least_conn;
        server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
        server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
        server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
        
        # 长连接
        keepalive 1000;
    }
    
    server {
        listen 80;
        server_name seckill.example.com;
        
        # 静态资源CDN
        location /static/ {
            expires 1d;
            add_header Cache-Control "public, immutable";
        }
        
        # 秒杀接口限流
        location /api/seckill/ {
            limit_req zone=seckill burst=20 nodelay;
            
            proxy_pass http://seckill_backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
    }
}

6.2.3 Redis优化

bash
# Redis配置优化
# redis.conf

# 内存优化
maxmemory 4gb
maxmemory-policy allkeys-lru

# 持久化优化(秒杀场景可以关闭)
save ""
appendonly no

# 网络优化
tcp-backlog 511
timeout 0
tcp-keepalive 300

# 慢查询日志
slowlog-log-slower-than 10000
slowlog-max-len 128

6.3 压测数据

测试环境

  • 服务器: 4核8G * 3台(应用服务器)
  • Redis: 16G内存,主从 + 哨兵
  • MySQL: 8核16G,主从架构
  • Kafka: 3节点集群
  • 网络: 千兆内网

压测结果

版本QPSP50延迟P95延迟P99延迟成功率
初级版本1,20045ms180ms320ms98.5%
中级版本52,0008ms28ms55ms99.8%
高级版本105,0005ms18ms35ms99.95%
专家版本150,0003ms12ms25ms99.99%

压测脚本(JMeter)

xml
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="秒杀压测">
      <elementProp name="TestPlan.user_defined_variables">
        <collectionProp name="Arguments.arguments">
          <elementProp name="host" elementType="Argument">
            <stringProp name="Argument.value">seckill.example.com</stringProp>
          </elementProp>
          <elementProp name="productId" elementType="Argument">
            <stringProp name="Argument.value">1001</stringProp>
          </elementProp>
        </collectionProp>
      </elementProp>
    </TestPlan>
    
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="并发用户">
        <!-- 10万并发用户,1秒内全部启动 -->
        <intProp name="ThreadGroup.num_threads">100000</intProp>
        <intProp name="ThreadGroup.ramp_time">1</intProp>
        <longProp name="ThreadGroup.duration">60</longProp>
      </ThreadGroup>
      
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="秒杀请求">
          <stringProp name="HTTPSampler.domain">${host}</stringProp>
          <stringProp name="HTTPSampler.path">/api/seckill/purchase</stringProp>
          <stringProp name="HTTPSampler.method">POST</stringProp>
          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
          
          <elementProp name="HTTPsampler.Arguments">
            <collectionProp name="Arguments.arguments">
              <elementProp name="body" elementType="HTTPArgument">
                <boolProp name="HTTPArgument.always_encode">false</boolProp>
                <stringProp name="Argument.value">
                  {"productId":${productId},"userId":${__Random(1,1000000)}}
                </stringProp>
              </elementProp>
            </collectionProp>
          </elementProp>
        </HTTPSamplerProxy>
      </hashTree>
    </hashTree>
  </hashTree>
</jmeterTestPlan>

七、运维监控

7.1 监控指标

业务指标

yaml
# Prometheus监控指标定义
metrics:
  # 秒杀QPS
  - name: seckill_request_total
    type: counter
    help: 秒杀请求总数
    labels: [product_id, status]
  
  # 秒杀成功率
  - name: seckill_success_rate
    type: gauge
    help: 秒杀成功率
    labels: [product_id]
  
  # 响应时间
  - name: seckill_response_time
    type: histogram
    help: 秒杀响应时间
    buckets: [5, 10, 20, 50, 100, 200, 500, 1000]
    labels: [product_id]
  
  # 库存剩余
  - name: seckill_stock_remaining
    type: gauge
    help: 剩余库存
    labels: [product_id]
  
  # 订单创建数
  - name: seckill_order_created
    type: counter
    help: 订单创建总数
    labels: [product_id]

技术指标

yaml
# 系统指标
system_metrics:
  - CPU使用率
  - 内存使用率
  - 网络带宽
  - 磁盘IO
  
# 应用指标
application_metrics:
  - JVM堆内存/Go内存
  - GC次数和时间
  - 线程数/Goroutine数
  - 连接池使用率
  
# 中间件指标
middleware_metrics:
  - Redis QPS/命中率/内存使用
  - MySQL QPS/慢查询/连接数
  - Kafka消息积压/消费延迟

7.2 告警规则

yaml
# Prometheus告警规则
groups:
  - name: seckill_alerts
    interval: 10s
    rules:
      # QPS异常
      - alert: SeckillQPSTooHigh
        expr: rate(seckill_request_total[1m]) > 150000
        for: 30s
        labels:
          severity: warning
        annotations:
          summary: "秒杀QPS过高"
          description: "当前QPS {{ $value }},超过阈值15万"
      
      # 成功率过低
      - alert: SeckillSuccessRateLow
        expr: seckill_success_rate < 0.95
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "秒杀成功率过低"
          description: "当前成功率 {{ $value }},低于95%"
      
      # 响应时间过长
      - alert: SeckillResponseTimeSlow
        expr: histogram_quantile(0.95, seckill_response_time) > 100
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "秒杀响应时间过长"
          description: "P95响应时间 {{ $value }}ms,超过100ms"
      
      # Redis宕机
      - alert: RedisDown
        expr: redis_up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Redis宕机"
          description: "Redis实例 {{ $labels.instance }} 宕机"
      
      # Kafka消息积压
      - alert: KafkaLag
        expr: kafka_consumergroup_lag > 10000
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Kafka消息积压"
          description: "消费者组 {{ $labels.consumergroup }} 积压 {{ $value }} 条消息"

7.3 故障排查

常见问题

问题现象原因解决方案
超卖订单数>库存数并发控制失效检查Redis Lua脚本,增加最终对账
库存不一致Redis和MySQL库存不一致消息丢失或消费失败增加补偿机制,定时对账
响应超时大量请求超时数据库/Redis压力过大增加限流,扩容服务器
Redis宕机缓存全部失效单点故障启用Redis哨兵/集群,增加降级逻辑
Kafka积压订单延迟创建消费速度<生产速度增加消费者数量,优化消费逻辑

排查手册

bash
# 1. 检查服务器资源
top              # 查看CPU、内存
iostat -x 1     # 查看磁盘IO
netstat -an | grep ESTABLISHED | wc -l  # 查看连接数

# 2. 检查应用日志
tail -f application.log | grep ERROR
tail -f access.log | awk '{print $7}' | sort | uniq -c | sort -rn

# 3. 检查Redis
redis-cli info stats  # 查看统计信息
redis-cli --latency  # 查看延迟
redis-cli monitor    # 监控命令

# 4. 检查MySQL
SHOW PROCESSLIST;  # 查看正在执行的查询
SHOW ENGINE INNODB STATUS;  # 查看InnoDB状态

# 5. 检查Kafka
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group seckill-order-group

7.4 容灾降级

go
// 降级开关管理
type DegradationManager struct {
    redis *redis.Client
    ctx   context.Context
}

// 检查是否需要降级
func (m *DegradationManager) ShouldDegrade(feature string) bool {
    key := fmt.Sprintf("degradation:%s", feature)
    val, err := m.redis.Get(m.ctx, key).Result()
    if err != nil {
        return false
    }
    return val == "true"
}

// 降级策略
func (s *AdvancedSeckillService) PurchaseWithDegradation(productID, userID int64) (string, error) {
    // 1. 检查是否需要降级
    if s.degradation.ShouldDegrade("seckill") {
        // 降级到排队模式
        return s.purchaseWithQueue(productID, userID)
    }
    
    // 2. 检查是否需要限流
    if s.degradation.ShouldDegrade("rate_limit_strict") {
        // 更严格的限流
        if !s.strictRateLimiter.Allow() {
            return "", fmt.Errorf("系统繁忙,请稍后重试")
        }
    }
    
    // 3. 检查是否跳过非核心功能
    skipNotification := s.degradation.ShouldDegrade("skip_notification")
    
    // 正常执行秒杀逻辑
    orderNo, err := s.Purchase(productID, userID)
    if err != nil {
        return "", err
    }
    
    // 4. 非核心功能可降级
    if !skipNotification {
        s.sendNotification(userID, orderNo)
    }
    
    return orderNo, nil
}

// 排队模式(降级方案)
func (s *AdvancedSeckillService) purchaseWithQueue(productID, userID int64) (string, error) {
    // 加入排队队列
    queueKey := fmt.Sprintf("seckill:queue:%d", productID)
    position, err := s.redis.RPush(s.ctx, queueKey, userID).Result()
    if err != nil {
        return "", err
    }
    
    return fmt.Sprintf("QUEUE_%d", position), nil
}

八、面试要点

8.1 常见追问

Q1: 如果QPS从10万增长到100万,应该怎么优化?

回答要点

  1. 垂直扩展:升级服务器配置(CPU、内存、网络)
  2. 水平扩展:增加应用服务器数量,Redis集群分片
  3. 架构优化
    • 引入本地缓存减少Redis压力
    • 使用CDN分流静态资源和页面
    • 数据库读写分离、分库分表
  4. 流量控制
    • 更严格的限流策略
    • 按用户等级分配配额
    • 前端增加排队机制
  5. 技术升级
    • 使用更高性能的语言(C++、Rust)
    • 使用DPDK等高性能网络库
    • 考虑GPU加速计算

Q2: 如何保证Redis和MySQL的数据一致性?

回答要点

  1. 最终一致性方案(推荐):
    • Redis扣减成功后异步更新MySQL
    • 使用消息队列保证可靠传输
    • 定时对账,发现不一致则告警并人工处理
  2. 强一致性方案(性能差):
    • 使用分布式事务(Seata)
    • 两阶段提交,性能损失大
  3. 补偿机制
    • 记录每次操作日志
    • 失败时自动重试
    • 最终人工介入兜底

Q3: 如何防止黄牛刷单?

回答要点

  1. 前端防护
    • 滑块验证码
    • 设备指纹识别
    • 检测异常点击频率
  2. 接口防护
    • IP限流(每个IP每秒最多5次请求)
    • 用户限流(每个用户每秒最多1次请求)
    • 黑名单机制
  3. 业务防护
    • 实名认证
    • 老用户优先
    • 购买记录分析
  4. 风控系统
    • 机器学习识别异常行为
    • 实时风险评分
    • 自动封禁高风险用户

Q4: Redis宕机了怎么办?

回答要点

  1. 预防措施
    • Redis哨兵/集群保证高可用
    • 主从复制保证数据安全
    • 定期备份
  2. 降级方案
    • 切换到MySQL库存扣减(性能下降)
    • 开启排队模式
    • 暂停秒杀活动
  3. 快速恢复
    • 哨兵自动故障转移
    • 手动切换到备用Redis
    • 从备份恢复数据

8.2 回答技巧

  1. 分层次回答:从初级到高级逐步深入
  2. 画图说明:架构图、流程图、时序图
  3. 举例说明:淘宝、京东的实际案例
  4. 权衡分析:说明每种方案的优缺点和适用场景
  5. 数据支撑:提供性能数据、压测结果

8.3 扩展知识点

九、相关资源

9.1 相关技术栈

9.2 相关场景题

9.3 扩展阅读

  • 淘宝双11技术揭秘
  • 京东618大促备战
  • 美团秒杀系统实践
  • Redis官方文档

总结:秒杀系统是高并发场景的集大成者,涵盖了缓存、队列、限流、降级、监控等几乎所有高并发技术。掌握秒杀系统的设计,就掌握了高并发系统的核心能力。在面试中,要能够从简单的方案讲到复杂的方案,从单机讲到分布式,从功能讲到性能,从正常讲到异常,展现你的系统设计能力和全栈思维。

正在精进