高并发读架构 — 百万 QPS 怎么扛
系统设计 ⭐⭐⭐ 进阶 🔥🔥🔥 必考
💡 核心要点
高并发读的核心心智模型是「全链路逐级削峰」:CDN 挡 50%、网关挡 20%、本地缓存挡 20%、Redis 挡 9%、数据库只剩 1%。任何"百万 QPS 读"的题目,先把这五层画出来,再分别讲每层的方案与瓶颈,就是面试满分答案。
这页解决什么问题
面试题:
- "设计商品详情页,单 SKU 100w QPS 怎么办?"
- "设计微博热搜,热点话题瞬时 50w QPS 怎么办?"
- "春运抢票,余票查询接口要扛 200w QPS"
- "设计 Twitter 个人主页" / "抖音 Feed 推荐流"
这一类题统称"高并发读"——共同特征是读多写少(读写比 1000:1 甚至更高),允许短时间不一致(秒级)。处理思路高度同构:全链路逐级削峰 + 多级缓存。
全链路架构总览
┌──────────┐
│ 用户 │
└────┬─────┘
│ 100w QPS
▼
┌─────────────┐
① CDN │ CDN 边缘 │ 挡 50% → 剩 50w QPS
│ (Cloudflare/└─┐
│ Cloudfront) │
└─────────────┘ │ 50w
▼
┌──────────────┐
② LB │ 负载均衡 │ 流量分发 + 健康检查
│ (Nginx/LVS) │
└──────┬───────┘
▼
┌──────────────┐
③ GW │ API 网关 │ 鉴权 + 限流,挡 20% → 剩 30w
│ (Kong/Envoy) │
└──────┬───────┘
▼
┌──────────────┐
④ APP │ 应用服务集群 │ 100 台 × 3000 QPS
└──────┬───────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
⑤ L1 │ Caffeine │ │ Caffeine │ │ Caffeine │ 本地缓存
│ (各机) │ │ │ │ │ 挡 20% → 剩 10w QPS
└────┬────┘ └────┬────┘ └────┬────┘
└──────────┬─┘────────────┘
▼
┌──────────┐
⑥ L2│ Redis │ 集群 / 分片
│ Cluster │ 挡 9% → 剩 1w QPS
└────┬─────┘
▼
┌──────────┐
⑦ DB│ MySQL │ 主 + N 个只读副本
│ (RW+R*N) │ 剩 1% = 1w QPS
└──────────┘金句:「用户能从下游走到 DB 是失败,能在上游被挡住是成功」。
每层方案与瓶颈
① CDN — 静态资源 / 半动态内容的第一道墙
适用:图片、CSS、JS、视频、静态化的 HTML
关键能力:
- 边缘节点缓存(全球数千节点,用户就近访问)
- Pull / Push 模式:Push 主动预热(618 大促)、Pull 按需回源
- 缓存 TTL 控制:静态资源年级别,半动态分钟级
- 回源率监控:理想 < 5%
深入做法:商品详情页"静态化 HTML"——把动态接口聚合后生成 HTML 推到 CDN,冷启动后 99% 流量根本不到源站。京东、淘宝商品详情页用了 15 年这套架构。
何时不能用:
- 个性化内容(首页推荐流)—— 改用 ESI / Edge Workers 动态拼装
- 必须实时数据(库存)—— 单独走 API 不走 CDN
详见 CDN 与负载均衡。
② 负载均衡 — 把流量摊到 N 台机
两层架构(生产标配):
LVS(L4,硬件级,单机 1000w QPS)
→ Nginx(L7,单机 10w QPS,做 HTTPS 卸载 / URL 路由)
→ 应用服务(单机 3000 QPS)核心算法:
- 轮询 / 加权轮询:通用
- 一致性哈希:让同一用户落到同一机器,提升本地缓存命中率
- 最少连接:长连接场景
单点防护:Nginx 必须主备 + Keepalived VIP,否则就是单点故障。
③ API 网关 — 限流 / 鉴权 / 兜底
网关能挡的流量:
- 限流:单用户 / 单 IP / 接口级(令牌桶或滑动窗口,详见 限流与熔断)
- 熔断:下游异常时直接返回降级值(如默认推荐列表)
- 鉴权 / 黑名单:恶意流量提前剔除
- 降级:非核心接口直接返回兜底值(如商品详情拿不到评论时返回空列表)
生产案例:双 11 大促,网关层会预先把"读取详情" 这类核心接口限到 100w QPS 兜底,其它非核心接口直接降级——保大放小。
④ 应用层 — 横向扩展 + 无状态
单机能扛多少:
| 应用类型 | 单机 QPS(简单读) |
|---|---|
| 传统 Java 同步阻塞 | 1000-3000 |
| Java 虚拟线程(JDK 21+) | 5000-10000 |
| Spring WebFlux / 响应式 | 10000-30000 |
| Go / Rust 异步 | 20000-50000 |
| Nginx + Lua / OpenResty | 50000+ |
横向扩展 = QPS / 单机能力:100w QPS / 3000 = 333 台。前提是无状态(session 放 Redis,不放本地)。
进阶优化:
- 同 region 部署、跨 AZ 容灾(防地震 / 机房断电)
- 用 K8s HPA 按 CPU / QPS 自动扩缩容
⑤ L1:本地缓存 — 最后一公里减压
为什么需要 L1:每次请求查 Redis 都有网络往返(1-2ms)+ 序列化反序列化(~0.5ms)。100w QPS 时 Redis 集群带宽 / CPU 顶不住。L1 本地缓存命中即 0 延迟。
主流方案:
| 库 | 算法 | 单机 QPS |
|---|---|---|
| Caffeine | W-TinyLFU | 1000w+(高命中率) |
| Guava Cache | LRU | 100w |
| ConcurrentHashMap + 手写 LRU | LRU | 1000w |
致命陷阱:L1 一致性——商品价格改了,N 台机器各自的 L1 怎么同步?
方案 A(最常用):短 TTL(1-10s),可接受秒级不一致
方案 B:发布订阅,写操作后广播失效(Redis Pub/Sub / Kafka)
方案 C:版本号 + 主动失效(业务侧通知)详见 缓存策略 — 多级缓存。
⑥ L2:分布式缓存 — Redis 集群
Redis 单实例:约 8w QPS(CPU 单线程瓶颈)
Redis Cluster 16 分片:约 128w QPS(线性扩展)
热点 Key 问题:单个 key 100w QPS 时,单分片承受不住(详见 热点 Key 与幂等性)
解法:
- Key 拆分:
stock:item_999→stock:item_999:shard_0..N,N 个分片各承担 1/N - 本地缓存兜底:用 L1 把单机热点流量吃掉
- 多副本读分流:用 Redis 读写分离(主写从读)
⑦ 数据库 — 读写分离 + 副本扩展
主库写 + N 个从库读:
App
│
┌────┴────┐
▼ 写 ▼ 读
Master ─── Slave1 (读)
─── Slave2 (读)
─── Slave3 (读) ← 扩展只读副本应对读流量主从同步延迟:MySQL 异步复制通常 < 1s,但读副本可能拿到旧值。3 种处理:
- 容忍延迟(推荐:90% 业务可接受)
- 强一致读直接走主库(金融场景)
- 读写分离中间件:Sharding-Sphere、MyCat、TiDB
单库瓶颈:单 MySQL 极限 ~ 5w QPS。再大要分库分表(详见 存储选型)。
流量估算速查表
面试时心算用:
| 维度 | 单机 / 单组件能力 |
|---|---|
| CDN 单节点 | 100w+ QPS(边缘节点) |
| Nginx 反向代理 | 5-10w QPS(L7) |
| LVS(DR 模式) | 1000w+ QPS(L4) |
| 应用服务(Java 同步) | 1000-3000 QPS / 单机 |
| 应用服务(Go / Rust) | 2-5w QPS / 单机 |
| Caffeine 本地缓存 | 1000w QPS(无网络) |
| Redis 单实例 | 8-10w QPS |
| Redis Cluster 16 分片 | ~128w QPS |
| MySQL 单库 | 3-5w QPS |
| 千兆网卡 | 12w QPS(1KB / 请求) |
| 万兆网卡 | 120w QPS |
口诀:「Redis 8w、MySQL 5w、Caffeine 千万」。
典型案例剖析
案例 1:商品详情页 100w QPS(淘宝/京东)
架构:
用户 → CDN(静态化 HTML,命中 95%)→ Nginx → 应用 → L1 Caffeine(命中 90%) → Redis Cluster → MySQL逐级削峰:
| 层 | 流量 | 命中率 / 通过率 | 剩余流量 |
|---|---|---|---|
| 用户 | 100w QPS | — | 100w |
| CDN | 100w | 命中 95% | 5w |
| 应用 + L1 Caffeine | 5w | 命中 90% | 5000 |
| Redis Cluster | 5000 | 命中 99% | 50 |
| MySQL | 50 | — | 0(实际几乎没流量到 DB) |
关键设计:
- 商品信息静态化:每次商品改价 → 触发 CDN 刷新(秒级生效)
- 价格 / 库存单独走 API:不进 HTML,走 Redis(这部分扛大头)
- L1 短 TTL:5s,可接受小不一致
案例 2:微博热搜瞬时 50w QPS
特点:热点突发、读冷启动后变热
架构:
用户 → CDN(半动态,TTL 30s)→ 网关(限流)→ 应用 + L1 → Redis(热搜 ZSet)→ DB(兜底)Redis 热点 Key 拆分:热搜榜 ZSet 在多个 key 上做副本(hotsearch:rank:0..9),读时随机选一个,单 Redis 节点压力降到 1/10。
案例 3:春运抢票余票查询 200w QPS
12306 早年崩溃就是因为没做这一层架构。新架构:
用户 → CDN(车次基础信息)→ 网关 → 应用 + L1(5s TTL)→ Redis(余票数)→ Order DB(实际订单数据)核心 trick:
- 余票查询和下单完全分离:查询走 Redis,下单走 DB
- L1 短 TTL 5s 可接受:用户看到的余票数允许 5 秒延迟(看到有票点进去发现没了,重新查即可)
- 下单串行化保证一致:下单时走 DB 行锁 / 乐观锁,确保不超卖
跟其他章节的关联
| 子主题 | 详细页面 |
|---|---|
| 多级缓存细节、Cache Aside / Write Through / Write Behind | 缓存策略 |
| 限流算法(令牌桶 / 漏桶 / 滑动窗口) | 限流与熔断、令牌桶/漏桶手撕 |
| 热点 Key 5 种解法 | 幂等性与热点 Key |
| Caffeine W-TinyLFU 内部原理 | LFU 缓存 |
| CDN 边缘节点 / 一致性哈希 | CDN 与负载均衡 |
| 数据库读写分离 / 分库分表 | 存储选型、MySQL 分库分表 |
| Redis Cluster 分片 | Redis |
| Bloom Filter 防穿透 | 哈希表 — Bloom Filter |
面试常问 & 怎么答
"百万 QPS 读怎么扛"
七层削峰口诀:「CDN 50%、网关 20%、L1 20%、L2 9%、DB 1%」。 画出全链路架构图 → 逐层讲方案 → 给数字(每层挡多少、单机能力多少、横向扩多少台)。
CDN 怎么缓存动态内容
- 静态化:把动态接口聚合后生成 HTML 推到 CDN(商品详情、文章页)
- ESI(Edge Side Includes):CDN 边缘动态拼装碎片
- Edge Workers:Cloudflare Workers / Fastly Compute@Edge 在边缘跑业务代码
L1 本地缓存的"一致性"怎么保证
3 个档位:
- 短 TTL(最常用):5-10s,业务接受秒级不一致
- 广播失效:写操作通过 Redis Pub/Sub / Kafka 广播到所有节点
- 版本号校验:每次读校验版本,过期主动拉取
电商详情页几乎全用方案 1,因为方案 2/3 的成本超过价值。
Redis 集群带宽不够怎么办
3 种解法:
- L1 兜大头:让 Caffeine 吃 80% 流量,Redis 只服务 20%
- 拆 key:热点 key 拆 N 份(详见 热点 Key)
- Read Replica:Redis 7.2+ 支持读副本,主写从读
"强一致读"怎么处理
90% 业务能接受秒级不一致(多看个旧价格不算大事)。金融 / 库存 / 风控这类必须强一致:
- 读必须走主库
- 不进 L1 缓存
- 用乐观锁 / 悲观锁兜底
看到什么就先想到这类
- "百万 QPS 读 / 读多写少" → 七层削峰架构
- "商品详情页 / 个人主页" → CDN + 多级缓存
- "热搜 / Feed 推荐流" → Redis 热点 Key 拆分 + L1
- "春运抢票余票查询" → 查询 / 下单分离,余票走 Redis
- "为什么淘宝双 11 不崩" → 全链路削峰 + 容量预案 + 限流降级
写流量(秒杀、抢单、发帖、消息推送)有不同的架构思路,见 高并发写架构。