Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← Web 开发 / Web Development

后端工程 / Backend Engineering

1. Backend 后端技术全景学习路线 / A Complete Backend Engineering Learning Path

2. 分布式系统——为什么单体时代过去了 / Distributed Systems Beyond the Monolith

3. 并发编程——为什么你的库存总是扣成负数 / Concurrency Control and Inventory Consistency

4. MySQL 优化——为什么你的查询总是那么慢 / MySQL Query and Storage Optimization

5. 架构模式——什么时候该用 CQRS / Architecture Patterns and When to Use CQRS

6. 系统设计——如何设计一个每秒 10 万订单的秒杀系统 / Designing a High-Throughput Flash-Sale System

7. 数据库工程化——在线表结构变更怎么不停服 / Database Engineering and Online Schema Changes

8. 可观测性——出了问题怎么快速定位 / Observability and Rapid Production Diagnosis

本页目录

系统设计——如何设计一个每秒 10 万订单的秒杀系统 / Designing a High-Throughput Flash-Sale System ​

📅 创建时间:2026-05-08 🏷️ 标签:#系统设计 #秒杀 #容量规划 #一致性哈希 #高并发 📚 前置知识:[[00-backend-overview]](全部章节内容) 📚 相关知识:[[01-redis-deep]](Redis 缓存) [[02-mq-kafka]](削峰) [[04-distributed-system]](分布式锁)


场景:面试官说,设计一个秒杀系统 ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  面试官:如何设计一个双十一秒杀系统?                    │
│                                                             │
│  你(错误示范):                                          │
│  "用 Redis 做缓存,Kafka 做削峰,MySQL 做持久化..."      │
│  面试官:好的,下一题。                                  │
│                                                             │
│  你(正确示范):                                          │
│  "先确认需求——QPS多少?库存多少?需不需要地域限制?"   │
│  面试官:...说说你的思路。                                │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13

系统设计不是背答案,而是展示你思考问题的方式。


第1节:需求分析——先问清楚问题 ​

需求澄清 checklist ​

┌─────────────────────────────────────────────────────────────┐
│              系统设计第一步:澄清需求                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  功能需求:                                              │
│  • 秒杀商品展示(提前多久?静态化还是动态?)           │
│  • 下单流程(限单?一人一单还是一人多单?)             │
│  • 支付流程(超时多久?库存锁定多久?)                 │
│  • 库存回滚(未支付自动取消)                          │
│                                                             │
│  非功能需求:                                             │
│  • QPS:峰值多少?持续多久?                           │
│  • 库存:总量多少?SKU 多少?                         │
│  • 可用性:99.9% 还是 99.99%?                        │
│  • 数据一致性:允许超卖多少?0?                        │
│                                                             │
│  约束条件:                                              │
│  • 预算(影响架构复杂度)                              │
│  • 团队技术栈(会用什么语言/框架)                    │
│  • 时间(多久要上线)                                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

典型秒杀系统的量级 ​

┌─────────────────────────────────────────────────────────────┐
│              秒杀系统典型量级                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  估算基础(以双十一为参考):                           │
│  • 日活用户:5 亿                                       │
│  • 参与秒杀用户:1 亿                                   │
│  • 秒杀商品:100 个 SKU,每个 1000 件                   │
│  • 峰值 QPS:10 万 - 100 万                          │
│  • 持续时间:2 小时                                     │
│                                                             │
│  拆解:                                                 │
│  • 10 万 QPS / 100 个商品 = 每商品 1000 QPS           │
│  • 1000 QPS / 10 秒 = 每秒 100 人抢 1 件商品          │
│                                                             │
│  结论:                                                 │
│  99% 的请求会在 Redis 层面被拦截                        │
│  只有 1% 的请求需要打到数据库                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

第2节:架构设计——分层防护 ​

从流量入口到数据层 ​

┌─────────────────────────────────────────────────────────────┐
│                 秒杀系统分层架构                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  用户请求                                                   │
│  │                                                         │
│  ▼                                                         │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  CDN(静态资源 + 商品页面缓存)                     │  │
│  │  → 秒杀开始前 1 分钟,商品页面全部缓存到 CDN     │  │
│  │  → 用户浏览器直接加载,不经过服务器                │  │
│  └─────────────────────────────────────────────────────┘  │
│  │                                                         │
│  ▼                                                         │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  API 网关层(限流 + 鉴权 + 路由)                   │  │
│  │  → 令牌桶限流(总 QPS 限制)                       │  │
│  │  → 黑名单过滤(黑产 IP/账号)                       │  │
│  │  → 路由到秒杀服务集群                              │  │
│  └─────────────────────────────────────────────────────┘  │
│  │                                                         │
│  ▼                                                         │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  秒杀服务层(Redis + Lua)                          │  │
│  │  → 活动开始前预热:库存加载到 Redis                │  │
│  │  → Lua 脚本:原子性扣库存 + 判断是否还有库存       │  │
│  │  → 库存为 0?直接返回"已售罄"(不查数据库)     │  │
│  └─────────────────────────────────────────────────────┘  │
│  │                                                         │
│  ▼                                                         │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  消息队列层(Kafka)                                │  │
│  │  → 秒杀成功后,下单请求进入 Kafka                   │  │
│  │  → 异步处理:创建订单、扣减数据库库存、发送通知  │  │
│  └─────────────────────────────────────────────────────┘  │
│  │                                                         │
│  ▼                                                         │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  订单服务层(MySQL)                                │  │
│  │  → 消费者处理 Kafka 消息                           │  │
│  │  → 乐观锁扣减数据库库存                           │  │
│  │  → 创建订单记录                                   │  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

核心代码:Redis Lua 原子扣库存 ​

python
# 秒杀的核心:Redis Lua 脚本,保证原子性
SECKILL_SCRIPT = """
local stock_key = KEYS[1]
local user_key = KEYS[2]
local limit = tonumber(ARGV[1])
local user_id = ARGV[2]
local expire = tonumber(ARGV[3])

-- 1. 检查用户是否已经购买(一人一单)
local bought = redis.call('SISMEMBER', user_key, user_id)
if bought == 1 then
    return -2  -- 已购买
end

-- 2. 检查库存
local stock = tonumber(redis.call('GET', stock_key))
if not stock then
    return -3  -- 活动不存在
end
if stock <= 0 then
    return -1  -- 已售罄
end

-- 3. 扣库存 + 标记用户已购买(原子操作)
redis.call('DECR', stock_key)
redis.call('SADD', user_key, user_id)
redis.call('EXPIRE', user_key, expire)

return stock - 1  -- 返回剩余库存
"""

def seckill(product_id, user_id, activity_id):
    stock_key = f"seckill:stock:{activity_id}:{product_id}"
    user_key = f"seckill:user:{activity_id}:{product_id}"

    result = redis.eval(
        SECKILL_SCRIPT,
        2, stock_key, user_key,
        1, user_id, 86400  # 每人限购 1 件,24 小时内有效
    )

    if result == -2:
        return {"code": "ALREADY_BOUGHT", "msg": "您已购买过此商品"}
    elif result == -1:
        return {"code": "SOLD_OUT", "msg": "已售罄"}
    elif result == -3:
        return {"code": "ACTIVITY_NOT_FOUND", "msg": "活动不存在"}
    else:
        # 秒杀成功,发送消息到 Kafka 创建订单
        kafka.send("order-topic", {
            "user_id": user_id,
            "product_id": product_id,
            "activity_id": activity_id,
            "remaining_stock": result
        })
        return {"code": "SUCCESS", "remaining_stock": result}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

第3节:容量规划——你的系统能撑多少 ​

估算方法:自顶向下 ​

┌─────────────────────────────────────────────────────────────┐
│              容量规划计算步骤                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Step 1:估算总 QPS                                       │
│  日活 1 亿,假设 10% 用户参与秒杀                         │
│  1 亿 × 10% = 1000 万 UV                                 │
│  秒杀持续 2 小时,假设 80% 流量集中在前 10 分钟         │
│  → 1000 万 × 80% / (10 × 60) = 1.3 万 QPS              │
│                                                             │
│  Step 2:拆分到各层                                       │
│  CDN 层:拦截静态请求,QPS 可忽略                        │
│  API 网关:1.3 万 QPS                                    │
│  Redis 秒杀服务:只有 5% 用户能抢到 → 650 QPS          │
│  MySQL 订单服务:异步消息队列 → 峰值 100 QPS            │
│                                                             │
│  Step 3:计算存储                                         │
│  库存:100 SKU × 1000 件 = 10 万条订单                 │
│  订单表:10 万条 × 1KB = 100 MB                        │
│  用户购买记录:10 万条 × 100B = 10 MB                   │
│  Redis 缓存:10 万 × 200B = 20 MB                      │
│                                                             │
│  Step 4:计算带宽                                         │
│  平均响应 200B,1.3 万 QPS                               │
│  → 1.3 万 × 200B × 8 = 20 Mbps                         │
│  → 留 3 倍余量:60 Mbps                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

性能预估表 ​

┌─────────────────────────────────────────────────────────────┐
│              各层性能预估                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  │ 层级         │ 单机能力    │ 需要机器数 │ 说明        │
│  ├───────────────┼─────────────┼───────────┼─────────────┤
│  │ CDN          │ 10 万 QPS  │ 1-2 台    │ 静态资源    │
│  ├───────────────┼─────────────┼───────────┼─────────────┤
│  │ API 网关     │ 1 万 QPS   │ 3-5 台    │ 限流鉴权   │
│  ├───────────────┼─────────────┼───────────┼─────────────┤
│  │ Redis        │ 10 万 QPS  │ 3-6 台    │ 哨兵/集群  │
│  ├───────────────┼─────────────┼───────────┼─────────────┤
│  │ Kafka        │ 50 万 QPS  │ 3 台      │ 削峰缓冲   │
│  ├───────────────┼─────────────┼───────────┼─────────────┤
│  │ 订单服务     │ 1000 QPS   │ 5-10 台   │ 异步处理   │
│  ├───────────────┼─────────────┼───────────┼─────────────┤
│  │ MySQL        │ 5000 QPS  │ 2 台      │ 主从备份   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

第4节:常见系统设计题 ​

设计一:设计一个短链系统 ​

┌─────────────────────────────────────────────────────────────┐
│              设计短链系统                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  需求:把长 URL 转成短 URL                                │
│  例:https://example.com/product/123456 → https://t.cn/abc │
│                                                             │
│  核心问题:                                               │
│  1. 短码如何生成?(不能重复,不能被猜测)              │
│  2. 如何存储?(KV 存储,短码→长 URL)                 │
│  3. 如何跳转?(302 临时跳转还是 301 永久跳转)         │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  方案 A:分布式 ID 转 Base62                        │ │
│  │  雪花 ID = 1234567890123456                       │ │
│  │  Base62 = 2LKx2M → https://t.cn/2LKx2M          │ │
│  │                                                     │ │
│  │  方案 B:哈希 + 冲突检测                          │ │
│  │  hash = MD5(long_url)[:6]                        │ │
│  │  冲突?→ 加盐再哈希                               │ │
│  │                                                     │ │
│  │  方案 C:发号器(推荐)                          │ │
│  │  原子计数器:1 → 2 → 3 → ...                    │ │
│  │  转 Base62 = "abc"                               │ │
│  │  碰撞率:0(严格递增,不会重复)                  │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

设计二:设计一个 Feed 流系统 ​

┌─────────────────────────────────────────────────────────────┐
│              设计 Feed 流系统                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  需求:微博/小红书/抖音的关注页                           │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  Push 模式(写扩散):                             │ │
│  │  发微博 → 写入所有粉丝的 Feed 表                  │ │
│  │  优点:读取快                                      │ │
│  │  缺点:大 V 发微博要写几亿条                      │ │
│  │  适用:小红书(粉丝少,互动高)                   │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  Pull 模式(读扩散):                             │ │
│  │  查看 Feed → 查询所有关注用户的最新 N 条微博      │ │
│  │  优点:发微博快                                    │ │
│  │  缺点:读取慢,需要合并排序                       │ │
│  │  适用:Twitter(粉丝多,内容多)                  │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  混合模式(推荐):                                │ │
│  │  普通用户:Push                                    │ │
│  │  大 V 用户:Pull(用户读时拉取大 V 的最新微博)   │ │
│  │  热门微博:Push 到热点池                          │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

设计三:设计一个延迟任务系统 ​

┌─────────────────────────────────────────────────────────────┐
│              设计延迟任务系统                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  需求:订单 30 分钟未支付,自动取消                       │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  方案 1:定时轮询(简单,但浪费资源)              │ │
│  │  每分钟扫描:WHERE status='pending' AND create_at < NOW()-30min│
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  方案 2:Redis ZSet(精准延时)                    │ │
│  │  ZADD order:cancel task_id score=过期时间戳        │ │
│  │  ZRANGEBYSCORE order:cancel 0 过期时间戳           │ │
│  │  定时扫描,精度高,但需要轮询 Redis                │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  方案 3:RabbitMQ 延迟队列(推荐)                │ │
│  │  发送消息时设置 x-delay = 30分钟                  │ │
│  │  30 分钟后消息投递到消费者                        │ │
│  │  MQ 保证不丢消息                                  │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  方案 4:时间轮算法(高性能延时调度)             │ │
│  │  分针刻度 = 60 格,每格 = 1 分钟                 │ │
│  │  每个格子 = 任务链表                              │ │
│  │  时钟每走一格,处理对应格子的任务                 │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

第5节:一致性哈希——数据分片的艺术 ​

问题:如何把数据均匀分布到 N 台机器 ​

┌─────────────────────────────────────────────────────────────┐
│              普通哈希的问题                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  hash(key) % N = 机器编号                                │
│                                                             │
│  机器从 3 台扩到 4 台:                                  │
│  → hash(key) % 4 ≠ hash(key) % 3                        │
│  → 几乎所有数据都需要迁移!                              │
│                                                             │
│  N=3 时:1, 2, 3 → 全部重新映射 → 迁移量 = 100%        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13

一致性哈希 ​

┌─────────────────────────────────────────────────────────────┐
│              一致性哈希原理                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  哈希空间:[0, 2^32),组成一个环                        │
│                                                             │
│           0                                              │
│            \                                             │
│             \                                           │
│              \                                          │
│               ●──────────────●───────────●              │
│           节点 A            节点 B           节点 C      │
│           hash(A)          hash(B)          hash(C)     │
│                                                             │
│  数据定位:                                               │
│  hash(key) → 顺时针找到第一个节点                        │
│                                                             │
│  扩缩容:                                                │
│  新增节点 D:只需迁移 D 相邻区间的数据                  │
│  其他节点的数据不受影响                                   │
│                                                             │
│  虚拟节点:                                              │
│  每台物理节点 = 100 个虚拟节点                           │
│  → 使数据分布更均匀                                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
python
import hashlib

class ConsistentHash:
    def __init__(self, replicas=100):
        self.replicas = replicas
        self.ring = {}
        self.sorted_keys = []

    def _hash(self, key):
        return int(hashlib.md5(str(key).encode()).hexdigest(), 16)

    def add_node(self, node):
        for i in range(self.replicas):
            key = self._hash(f"{node}:{i}")
            self.ring[key] = node
            self.sorted_keys.append(key)
        self.sorted_keys.sort()

    def get_node(self, key):
        if not self.ring:
            return None
        hash_key = self._hash(key)
        for node_key in self.sorted_keys:
            if hash_key <= node_key:
                return self.ring[node_key]
        return self.ring[self.sorted_keys[0]]  # 环绕到第一个

# 使用
ch = ConsistentHash()
ch.add_node("192.168.1.1")
ch.add_node("192.168.1.2")
ch.add_node("192.168.1.3")

# 数据定位
node = ch.get_node("user:12345")  # 永远路由到同一台机器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

升华:系统设计的思维框架 ​

┌─────────────────────────────────────────────────────────────┐
│              系统设计四步法                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Step 1:澄清需求(2-3 分钟)                          │
│  → 功能需求 / 非功能需求 / 约束条件                       │
│  → 不要急着画图,先问清楚问题                            │
│                                                             │
│  Step 2:高层设计(5-8 分钟)                          │
│  → 数据流 / 服务划分 / 关键组件                         │
│  → 先给宏观架构图                                        │
│                                                             │
│  Step 3:核心问题深入(10-15 分钟)                     │
│  → 数据存储 / 可用性 / 一致性 / 扩展性                 │
│  → 选择 + 权衡(Trade-off)                             │
│                                                             │
│  Step 4:总结优化(2-3 分钟)                          │
│  → 监控 / 告警 / 容灾 / 成本                           │
│  → 展示工程思维                                          │
│                                                             │
│  一句话:                                                │
│  系统设计考的是权衡能力,不是背答案。                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

"AI 可查 vs 必须理解"清单 ​

AI 可查:
✅ 一致性哈希的具体代码实现
✅ 雪花算法的具体代码
✅ 容量规划的计算公式
✅ 各开源组件的选型参数

必须理解:
🔴 系统设计的四步法(澄清需求→高层设计→深入→总结)
🔴 秒杀系统的分层防护架构
🔴 Redis Lua 脚本保证原子性的原理
🔴 Push / Pull / 混合 Feed 流的选择
🔴 一致性哈希 vs 普通哈希的 trade-off
1
2
3
4
5
6
7
8
9
10
11
12

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇5. 架构模式——什么时候该用 CQRS / Architecture Patterns and When to Use CQRS
下一篇7. 数据库工程化——在线表结构变更怎么不停服 / Database Engineering and Online Schema Changes

持续记录,持续成长

Copyright © Tidenflow