设计秒杀系统需要考虑哪些因素?

一、秒杀系统核心挑战

挑战类型 具体问题
瞬时高并发 数万~百万级QPS集中在秒杀开始瞬间
超卖问题 库存扣减的原子性,避免卖超
恶意请求 黄牛脚本、DDoS攻击
系统稳定性 高并发下数据库、缓存、服务链路的雪崩风险
数据一致性 库存、订单、支付状态的一致性

二、分层架构设计

1. 整体架构(分层削峰)

plaintext 复制代码
用户层
  │
  ▼
CDN静态资源缓存(HTML/JS/CSS)
  │
  ▼
接入层:Nginx集群(限流+动静分离)
  │
  ▼
应用层:秒杀微服务(无状态集群)
  │               │
  ▼               ▼
缓存层          消息队列
Redis集群       Kafka/RocketMQ
  │               │
  ▼               ▼
数据层          订单服务
MySQL集群(分库分表)

2. 关键组件职责

  • CDN:缓存静态页面(如秒杀倒计时页面),减少服务器压力。
  • Nginx
    • 限流:IP/用户级请求限制(如令牌桶算法)。
    • 动静分离:将动态请求(如抢购)和静态请求(如商品图片)分流。
  • 秒杀服务
    • 预校验:用户资格、秒杀状态、库存缓存。
    • 异步化:请求快速进入队列,避免同步阻塞。
  • Redis
    • 库存预热:提前加载秒杀库存到Redis。
    • 原子扣减:通过DECR或Lua脚本保证原子性。
  • 消息队列
    • 削峰填谷:将瞬时请求转为异步处理。
    • 顺序消费:保证先到先得的公平性(可选)。
  • MySQL
    • 最终库存一致性:通过消息队列异步同步。
    • 订单分库分表:按用户ID哈希分片。

三、详细设计要点

1. 秒杀流程设计

plaintext 复制代码
1. 用户进入秒杀页(静态页,CDN缓存)
2. 点击"立即抢购"时:
   a. 前端JS限制频繁点击(如5秒内只能提交1次)
   b. 请求携带Token(由服务端预生成,防CSRF)
3. 服务端处理:
   a. Nginx层限流(如单IP 10次/秒)
   b. 预校验:
      - 是否黑名单用户
      - Redis检查活动是否开始/结束
      - 本地缓存检查用户是否已参与过
   c. Redis原子扣减库存(DECR或Lua脚本)
   d. 扣减成功则生成订单ID,写入消息队列
   e. 返回"抢购中"状态,轮询查询结果
4. 消费者服务:
   a. 从队列获取订单请求
   b. 创建订单(MySQL事务)
   c. 更新Redis中的订单状态

2. 库存扣减方案

方案一:Redis原子操作 + 异步落库

lua 复制代码
-- Lua脚本保证原子性
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock > 0 then
    redis.call('DECR', KEYS[1])
    return 1  -- 成功
else
    return 0  -- 失败
end

方案二:令牌桶算法

  • 提前将库存拆分为令牌放入Redis,抢到令牌才有资格下单。

3. 防刷与安全

  • Token机制

    • 秒杀开始前,服务端生成加密Token,前端提交时校验。
  • 分级限流

    plaintext 复制代码
    1. Nginx层:IP限流(滑动窗口算法)
    2. 网关层:用户ID限流(如每秒5次)
    3. 服务层:商品ID限流(根据库存比例)
  • 人机验证

    • 活动开始前要求完成滑块验证或短信验证码。

4. 数据一致性保障

  • 库存一致性
    • Redis库存扣减成功后,通过消息队列异步更新MySQL。
    • 定时任务对比Redis与MySQL库存差异,自动修正。
  • 订单状态一致性
    • 使用本地消息表或事务消息(如RocketMQ)保证创建订单与扣库存的最终一致。

四、性能优化手段

1. 读优化

  • 多级缓存

    plaintext 复制代码
    1. 本地缓存(Caffeine):活动配置、黑名单用户
    2. Redis集群:库存数量、秒杀结果
    3. MySQL:最终数据持久化
  • 缓存预热

    • 活动开始前5分钟,将商品信息、库存加载到Redis。

2. 写优化

  • 异步化设计
    • 关键路径(如库存扣减)同步处理,非关键路径(如订单生成)异步化。
  • 批量操作
    • 日志、监控数据先内存聚合,再批量写入。

3. MySQL优化

  • 分库分表

    • 订单表按用户ID哈希分16个库,每个库分32张表。
  • 特殊字段设计

    sql 复制代码
    CREATE TABLE seckill_orders (
      order_id BIGINT PRIMARY KEY,
      user_id BIGINT,
      sku_id BIGINT,
      status TINYINT COMMENT '0-待支付 1-已支付',
      INDEX idx_user (user_id),
      INDEX idx_sku (sku_id)
    ENGINE=InnoDB;

五、容灾与降级方案

1. 服务降级

  • 降级策略
    • 库存不足时直接返回"已售罄",不走后续流程。
    • 促销服务不可用时,跳过优惠计算。
  • 开关配置
    • 通过配置中心动态关闭非核心功能(如风控校验)。

2. 熔断机制

  • 监控Redis、MySQL、消息队列的响应时间,超阈值时:
    • 熔断非核心服务(如用户积分抵扣)。
    • 返回友好提示("系统繁忙,请重试")。

3. 数据恢复

  • Redis故障
    • 降级到MySQL库存检查,通过分布式锁保证一致性。
  • MySQL故障
    • 允许超卖,事后通过人工补偿(如退款)。

六、监控与告警

  1. 核心指标监控
    • Redis库存剩余量、MySQL订单创建TPS。
    • 消息队列积压情况。
  2. 报警规则
    • 库存消耗速率异常(如1秒内降为0,可能被刷)。
    • 订单创建失败率>0.1%。
  3. 日志追踪
    • 全链路TraceID,快速定位问题节点。

七、典型问题解决方案

1. 如何防止超卖?

  • Redis原子操作DECR+WATCH或Lua脚本。

  • 数据库乐观锁

    sql 复制代码
    UPDATE sku_stock 
    SET stock = stock - 1 
    WHERE sku_id = 1001 AND stock >= 1;

2. 如何解决重复下单?

  • 唯一索引:用户ID+活动ID建立唯一索引。
  • Redis标记SET user_activity:{userId}:{actId} 1 EX 3600 NX

3. 如何处理热点Key?

  • Redis分片:将库存KEY按商品ID哈希到不同节点。
  • 本地缓存:热点商品库存缓存在服务本地,定期同步。

八、示例代码片段

1. Redis库存扣减(Lua)

lua 复制代码
-- KEYS[1]: 库存KEY, ARGV[1]: 扣减数量
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock >= tonumber(ARGV[1]) then
    return redis.call('DECRBY', KEYS[1], ARGV[1])
else
    return -1
end

2. 消息队列消费者(Java)

java 复制代码
@KafkaListener(topics = "seckill-orders")
public void handleOrder(OrderMessage message) {
    // 1. 分布式锁防重处理
    String lockKey = "order:" + message.getUserId() + ":" + message.getSkuId();
    if (redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS)) {
        try {
            // 2. 创建订单(数据库事务)
            orderService.createOrder(message);
        } finally {
            redisLock.unlock(lockKey);
        }
    }
}

总结:以上只是理论设计。实际落地时需根据业务特点具体调整,并通过全链路压测验证。

相关推荐
像风一样自由202010 分钟前
RESTful API工具和框架详解
后端·restful
草捏子12 分钟前
接口幂等性设计:6种解决方法让重复请求不再成为系统隐患
后端
Captaincc12 分钟前
AI coding的隐藏王者,悄悄融了2亿美金
前端·后端·ai编程
盖世英雄酱5813621 分钟前
同事说缓存都用redis啊,数据不会丢失!真的吗?
redis·后端·面试
L2ncE2 小时前
双非计算机自救指南(找工作版)
后端·面试·程序员
cdg==吃蛋糕2 小时前
solr自动建议接口简单使用
后端·python·flask
Joseit2 小时前
基于 Spring Boot实现的图书管理系统
java·spring boot·后端
{⌐■_■}2 小时前
【go】什么是Go语言的GPM模型?工作流程?为什么Go语言中的GMP模型需要有P?
java·开发语言·后端·golang
IT杨秀才3 小时前
LangChain框架入门系列(5):Memory
人工智能·后端·langchain
程序猿chen3 小时前
JVM考古现场(二十四):逆熵者·时间晶体的永恒之战
java·jvm·git·后端·程序人生·java-ee·改行学it