高并发系统设计
从容量评估到分层防护、从缓存与异步化到隔离熔断降级限流,给出一套"先量化、再分层、后取舍"的高并发系统设计方法论,附通用 8 步设计法。
一、容量评估
1.1 核心指标定义
| 指标 | 定义 | 说明 |
|---|---|---|
| QPS | 每秒查询/请求数 | 互联网系统最常用 |
| TPS | 每秒事务数 | 一个完整业务事务(如下单),事务可含多次请求 |
| RT | 响应时间(平均/TP99/P99) | 单次请求耗时,P99 比平均值更能反映尾部体验 |
| 并发数 | 系统中同时处理的请求数 | 由 Little 定律:并发数 ≈ QPS × RT |
| 吞吐量 | 单位时间成功处理的请求数 | 系统能力的上限表达 |
Little 定律是容量估算的锚:并发线程数 = QPS × RT(秒)。例如 QPS 2000、RT 0.1s,则并发约 200,可据此决定线程池大小与实例数。
1.2 容量估算方法
估算公式链:
PV(日页面访问量)
→ 日请求量 ≈ PV × 单页平均请求数(一般 5~10)
→ 平均 QPS = 日请求量 / 86400
→ 峰值 QPS = 平均 QPS × 峰值因子
→ 需要的实例数 = 峰值 QPS / 单实例可支撑 QPS(预留 30% 余量)PV(日页面访问量)
→ 日请求量 ≈ PV × 单页平均请求数(一般 5~10)
→ 平均 QPS = 日请求量 / 86400
→ 峰值 QPS = 平均 QPS × 峰值因子
→ 需要的实例数 = 峰值 QPS / 单实例可支撑 QPS(预留 30% 余量)单实例可支撑 QPS 的经验量级(参考,需压测校准):
| 组件 | 单实例经验量级 |
|---|---|
| Nginx | 数万 QPS |
| 业务应用(Java,含 DB 往返) | 数百~数千 QPS |
| Redis | 5~10 万 QPS |
| MySQL(简单查询) | 数千 QPS |
| MySQL(写,单机) | 数百~千级 TPS |
1.3 峰值因子与业务特征
峰值因子不能套固定数字,按业务节奏定:
- 常态业务(后台管理系统):峰值因子 3~5 倍;
- C 端日常:峰值因子 5~10 倍(晚高峰);
- 大促/热点事件:需按"预热流量 + 活动预估"单独建模,峰值因子可到 20~50 倍甚至更高(如秒杀)。
削峰思想出现在容量评估时就应开始:估算的不是"扛住峰值"而是"峰值中多少必须实时处理、多少可以削峰异步"(见第五章)。
1.4 压测目标与容量水位
- 压测目标:找到系统的"拐点"——RT 开始陡增的 QPS(性能拐点)、以及错误率超过阈值的 QPS(容量上限),而不是压出最大数字;
- 容量水位约定:生产运行水位不超过单机容量的 60%~70%,预留故障转移与突增缓冲;
- 压测类型:单接口压测(定位瓶颈)→ 链路压测(全链路容量)→ 混合场景压测(贴近真实)→ 大促前全链路压测;
- 数据要求:压测数据量必须贴近生产(大数据量下索引与缓存命中率才真实)。
1.5 容量评估案例
一次大促预热场景:
预估大促当日 PV = 2 亿(平时 3000 万)
单页请求数 = 8
日请求量 = 16 亿
平均 QPS = 16 亿 / 86400 ≈ 18500
峰值因子按活动节奏取 6(分时段放量)
峰值 QPS ≈ 111000
单实例业务应用按 1500 QPS 计
实例数 = 111000 / 1500 × 1.3(余量)≈ 96 台预估大促当日 PV = 2 亿(平时 3000 万)
单页请求数 = 8
日请求量 = 16 亿
平均 QPS = 16 亿 / 86400 ≈ 18500
峰值因子按活动节奏取 6(分时段放量)
峰值 QPS ≈ 111000
单实例业务应用按 1500 QPS 计
实例数 = 111000 / 1500 × 1.3(余量)≈ 96 台该结果是"架构能扛多少"的输入,而非"真的全实时扛"——真正的解法是分层缓存 + 削峰异步把峰值压回一个数量级内(见第二章与第八章)。
二、分层防护总览
2.1 六层防护架构
高并发系统的核心思想是流量分层消化,越靠前的层越廉价、越靠后的层越珍贵:
2.2 各层职责与能力量级
| 层 | 职责 | 单位成本 | 处理量级 |
|---|---|---|---|
| DNS/CDN | 就近分发、静态内容卸载 | 极低 | 百万级 QPS |
| 网关 | 限流、鉴权、路由,挡掉非法流量 | 低 | 万~十万 QPS |
| 应用 | 业务逻辑、本地缓存、异步发送 | 中 | 千~万 QPS |
| 缓存 | 热点数据扛读 | 低 | 十万级 QPS |
| 数据库 | 最终一致性数据、写 | 高(最难扩展) | 千级 QPS |
2.3 分层防护的三条原则
- 能静态不动态:静态资源、可预生成的页面一律上 CDN;
- 能缓存不查库:读请求优先在缓存层消化;
- 能异步不阻塞:非即时结果走 MQ 削峰,让昂贵的写能力匀速运行。
架构师在每一层都要问:这层能替我挡掉多少流量?如果流量全打到数据库,说明前面的层全都失职了。
三、读多写少与写多读少
3.1 读多写少场景(内容/商品/资讯类)
典型特征:读写比 10:1 以上,读有热点、可容忍短暂不一致。
设计思路:
- 缓存前置:详情/列表全部缓存,key 按业务聚合(见第四章);
- CDN 卸载:图片、静态页、预渲染结果上 CDN;
- 读模型专门化:复杂查询投影到宽表/搜索/缓存(参考 DDD 笔记的 CQRS);
- 热点隔离:发现热点 key 自动本地缓存 + 多级缓存兜底(防击穿)。
3.2 写多读少场景(日志、埋点、交易流水类)
典型特征:写入量大、读少但要求查询能力、写不能丢。
设计思路:
- 批量与异步:写入先落本地/内存缓冲,攒批后批量刷库或发 MQ;
- 顺序写:日志类数据用追加写模型(如消息队列落盘、LSM 存储),避免随机写;
- 写入分片:按业务键哈希分库分表,分散单库写压力(决策见第七章);
- 读写分离:主库扛写,从库扛读与分析(延迟可容忍时)。
3.3 两类场景设计对比表
| 维度 | 读多写少 | 写多读少 |
|---|---|---|
| 主要矛盾 | 读放大、热点 | 写放大、单点写瓶颈 |
| 首要手段 | 缓存 + CDN | 异步批量 + 分片 |
| 一致性要求 | 可容忍短暂不一致 | 写不能丢(可靠性优先) |
| 数据库策略 | 缓存扛读,DB 兜底 | 分库分表 + 读写分离 |
| 典型例子 | 商品详情、资讯 Feed | 埋点、订单流水、IM 消息 |
现实中多数业务是混合型(如电商:商品详情读多写少、库存/订单写多读少),正确做法是给不同能力分别设计,而不是一套方案走天下。
四、缓存体系设计
4.1 缓存分层
| 层 | 代表 | 适用 | 容量/时效 |
|---|---|---|---|
| 客户端缓存 | 浏览器 Cache、App 本地 | 静态与弱变资源 | 由 HTTP 头控制 |
| CDN | 边缘节点 | 静态/半静态内容 | 分钟~小时级 |
| 本地缓存 | Caffeine/Guava | 热点 key、JVM 内 | 秒级,需失效同步 |
| 分布式缓存 | Redis | 全局共享热点读 | 由 TTL 控制 |
| 数据库缓存 | MySQL Buffer Pool | 引擎内部 | 自动 |
两级缓存策略(本地 + Redis)是应对"热点 key 打爆 Redis"的常见组合:本地缓存挡 90% 的同类请求,Redis 兜底。但本地缓存一致性最弱,适合变更不频繁的热数据;变更频繁的数据只放 Redis。
4.2 缓存一致性
缓存与数据库双写的一致性本质是"先写库还是先删缓存"的选择:
| 策略 | 过程 | 问题 |
|---|---|---|
| 先更新库再删缓存 | 写库 → 删缓存 | 删除失败会留下脏缓存(可用延迟双删兜底) |
| 先删缓存再更新库 | 删缓存 → 写库 | 写库期间旧值可能被并发重新入缓存 |
| Cache Aside + 订阅 binlog 删缓存 | 业务只写库,删缓存由订阅程序做 | 最稳,适合一致性敏感的写 |
务实结论:绝大多数读多写少场景能容忍秒级不一致,采用"先写库,后删缓存" + TTL 兜底即可;追求更强一致性时用"订阅 binlog 异步删缓存",不要为缓存一致性引入分布式事务(那是过度设计)。
4.3 缓存三大问题与对策
| 问题 | 现象 | 对策 |
|---|---|---|
| 穿透 | 查不存在的 key,每次打到 DB | 缓存空值(短 TTL);布隆过滤器拦截;接口层参数校验 |
| 击穿 | 单个热点 key 过期瞬间并发打到 DB | 互斥重建(只允许一个线程查库回填);逻辑过期 + 异步刷新;热点 key 永不过期+主动更新 |
| 雪崩 | 大量 key 同时过期 | 过期时间加随机抖动;多级缓存;缓存集群高可用(哨兵/Cluster) |
4.4 缓存容量与淘汰策略
- 单 key 过大(>10KB 的热点)会造成带宽压力,大对象拆分为多个 key 或压缩;
- 内存淘汰策略:绝大多数业务用
allkeys-lru/volatile-lru;noeviction(不淘汰)绝不可用于生产,会直接写失败; - 容量监控:以内存使用率 + 命中率 + 逐出量三指标盯缓存健康。
4.5 缓存设计决策树
五、异步化
5.1 异步化的三个目的
- 削峰填谷:瞬时高峰落 MQ,消费端匀速处理,保护数据库与下游;
- 降低 RT:非即时步骤(发短信、算积分)从同步链路摘除,主链路更快返回;
- 解耦:核心流程只发事件,扩展方订阅(与 DDD 领域事件天然配合)。
5.2 MQ 削峰
经典模型:
流量高峰 10 万 TPS 请求
→ 网关限流只放 1 万 TPS 进系统
→ 请求落 MQ(MQ 本身可扛百万级 TPS 写入)
→ 消费端按 DB 能力(如 2000 TPS)匀速消费流量高峰 10 万 TPS 请求
→ 网关限流只放 1 万 TPS 进系统
→ 请求落 MQ(MQ 本身可扛百万级 TPS 写入)
→ 消费端按 DB 能力(如 2000 TPS)匀速消费要点:削峰不是无限缓冲——要配套消息积压监控与消费端水平扩容,并明确"积压超过 X 分钟触发告警/扩容"的预案。消息可靠性与消费幂等设计见《微服务架构与拆分》第二章。
5.3 批量与合并请求
- 批量写:攒批刷库(如每 500ms 或 500 条刷一次),显著提升吞吐;埋点/日志类数据标配;
- 合并请求(Coalescing):同一资源的并发读合并为一个上游请求(JVM 内单 flight 请求),缓存击穿时保护下游;
- 批量拉取:循环里单查改为批量查(IN 查询),消除 N+1。
5.4 异步化引入的新问题
| 新问题 | 对策 |
|---|---|
| 消息丢失 | 生产端确认、消费端手动 ack、本地消息表兜底 |
| 消息重复 | 消费幂等(业务键去重表/唯一索引) |
| 消息积压 | 积压监控 + 队列扩容 + 临时加消费者 |
| 调试困难 | 消息体带 Trace ID,贯穿日志链路 |
| 数据延迟可见 | 对延迟敏感页面做"状态提示 + 主动刷新" |
异步化把"实时问题"变成了"延迟 + 可靠性问题",必须配套消费监控与对账才能上生产。
六、隔离 / 熔断 / 降级 / 限流
6.1 四种策略的定位
| 策略 | 保护谁 | 核心动作 | 触发依据 |
|---|---|---|---|
| 隔离 | 故障方与正常方 | 物理/逻辑分舱,互不拖垮 | 按资源/依赖预规划 |
| 熔断 | 调用方 | 快速失败,停止调用故障依赖 | 错误率/慢调用达到阈值 |
| 降级 | 整体可用性 | 牺牲非核心功能保核心 | 人工预案/自动条件 |
| 限流 | 系统容量 | 拒绝超额请求 | QPS/并发数阈值 |
一句话:隔离是分舱(预防),熔断是切断(对故障依赖),降级是取舍(保核心弃边缘),限流是闸门(防超载)。
6.2 隔离
- 线程池隔离:不同依赖用独立线程池(如 Hystrix 模式),某依赖占满线程池不影响其它;代价是线程开销大,适合依赖少的场景;
- 信号量隔离:限制并发数而非独立线程,轻量、适合快速失败场景;
- 物理隔离:热点业务独立集群/独立数据库实例,如"秒杀单独部署",故障半径最小;
- 动静/读写隔离:读服务与写服务分离,管理端与交易端分离,互相不拖累。
6.3 熔断
- 三态:关闭(Closed)→ 打开(Open)→ 半开(Half-Open)。错误率超阈值熔断打开,此时请求快速失败;经过冷却窗口进入半开,放少量探测流量,成功则关闭、失败则重新打开;
- 关键参数:错误率阈值(如 50%)、最小调用量(避免少量流量误触发)、冷却时长、半开探测请求数;
- 注意:熔断打开的"快速失败"只是止损,真正的恢复依赖下游自愈 + 上游降级兜底;
- 实现见《SpringCloud微服务》笔记的 Sentinel/Resilience4j。
6.4 降级
- 分级降级:把功能分成核心(必须保)/非核心(可牺牲)两级,压力大时关闭非核心(如关闭个性化推荐,只出通用列表);
- 降级预案:每个关键依赖提前写好降级开关(配置中心一键切换),如缓存故障 → 直连 DB(限流配合)、推荐故障 → 返回兜底列表;
- 返回降级:查询失败返回旧缓存数据(stale 兜底)而非报错;
- 降级必须有自动化触发 + 人工确认双通道,并记录降级时段用于复盘。
6.5 限流
- 维度:单机 QPS、集群 QPS、并发线程数、用户/IP 维度限流;
- 位置:网关层(全局限流)+ 应用层(接口/用户级限流)两级配合;
- 算法对比见《经典系统设计案例》附表的限流算法对比表;
- 限流拒绝语义:排队等待(削峰)或直接快速失败(返回 429/友好提示),业务按需选择。
6.6 四策略联动与指标监控
| 策略 | 关键指标 | 告警建议 |
|---|---|---|
| 隔离 | 各舱线程池活跃度、队列深度 | 队列占用 > 70% 持续 1 分钟 |
| 熔断 | 错误率、熔断打开次数、快速失败量 | 熔断打开即告警 |
| 降级 | 降级开关状态、降级请求占比 | 自动降级触发即告警 |
| 限流 | 被限 QPS、限流占比 | 限流占比 > 20% 持续 5 分钟 |
取舍顺序:资源紧张时依次执行"限流挡外部 → 熔断切故障依赖 → 降级舍非核心 → 隔离保舱内核心",目标始终是让核心链路的成功率最高。
七、数据库层设计
7.1 索引先行
数据库层的第一步不是分库分表而是索引优化(原理与优化细则见《MySQL深入》笔记),因为索引优化的成本远低于架构拆分:
- 慢查询治理:慢日志 + 执行计划分析(
EXPLAIN)常态化; - 联合索引按"等值优先、排序次之"设计,覆盖索引消除回表;
- 索引失效场景(函数操作、隐式类型转换、左模糊)纳入代码评审;
- 单表数据量在千万级以内、读写可控时,索引足够就不必分表。
7.2 读写分离
- 适用:读远大于写(读多写少),从库扛读查询与分析;
- 架构:主库写 + 从库读,中间件(如 ShardingSphere/MyCat)或应用层路由;
- 代价:主从延迟导致"写后读不一致"——对策:刚写的数据强制走主库、关键读走主库、可容忍延迟的读走从库;
- 局限:读写分离不解决写瓶颈,写放大时才需要分库分表。
7.3 分库分表决策树
决策要点:
- 分片键(Sharding Key)是分库分表的灵魂:必须按最高频查询路由,如订单按
user_id分片、订单明细与订单同片(避免跨片 join 与事务); - 分库分表无法回避的三件事:跨分片查询(汇总层/宽表/ES 兜底)、分布式 ID(见《经典系统设计案例》附表)、数据迁移与扩容(一致性哈希 + 虚拟节点 / 双写迁移);
- 事务边界收窄到单分片内,跨分片事务用最终一致性(见《微服务架构与拆分》第六章);
- 先垂直拆分(按业务拆库)再水平分表,不要一上来就水平分 64 表。
7.4 详版指引
分库分表与读写分离的完整实现细则(分片策略、中间件配置、迁移方案)详见《MySQL深入》分库分表章节,本笔记只保留决策树与方法论。判断口诀:先索引,再缓存,后读写分离,最后才是分库分表——越靠后的手段成本越高、越不可逆。
八、高并发系统设计通用 8 步法
8.1 8 步总览
- 明确场景与指标 → 2. 估算容量 → 3. 画分层架构 → 4. 设计缓存 → 5. 异步化 → 6. 保护策略 → 7. 数据库方案 → 8. 兜底与监控。口诀:"场景指标、容量分层、缓存异步、保护分库、兜底监控"。
8.2 分步详解
步骤一:明确场景与指标
- 回答:这是读多还是写多?数据规模多大?一致性要求多高?
- 定指标:目标 QPS、可用性(如 99.99%)、RT(P99 < 200ms)、数据量(总量 + 增速)。
步骤二:容量评估
- 按第一章公式估平均/峰值 QPS,推实例数与资源(中间件规格、带宽),并评估"峰值多少能削"。
步骤三:分层架构
- 按第二章六层模型画图:哪些流量在 CDN/缓存消化、哪些必须实时、哪些走异步。
步骤四:设计缓存
- 按第四章决策树定缓存层级、TTL、一致性与热点防护(穿透/击穿/雪崩三件套)。
步骤五:异步化
- 非即时步骤摘出主链路,MQ 削峰,明确消费能力、积压预案与幂等设计。
步骤六:保护策略
- 按第六章对关键依赖做隔离 + 熔断 + 降级预案,全局限流(网关)+ 应用限流。
步骤七:数据库方案
- 按第七章决策树:索引治理 → 读写分离 → 分库分表,明确分片键与分布式 ID。
步骤八:兜底与监控
- 全链路 Trace、核心指标大盘(QPS/RT/错误率/缓存命中率/积压量)、容量告警、压测与预案演练(大促前全链路压测 + 故障演练)。
8.3 一个"通用电商查询页"的 8 步速成示例
| 步骤 | 决策 |
|---|---|
| 1 指标 | 峰值 5 万 QPS 读详情,P99<200ms,容忍 1s 缓存不一致 |
| 2 容量 | 单机 1500 QPS,需 ~43 台;但缓存后实际打 DB 仅 2% |
| 3 分层 | CDN(静态) → 网关限流 → 应用(本地缓存) → Redis → DB |
| 4 缓存 | 详情 key TTL 10min + 随机抖动;热点商品本地缓存 + 互斥重建 |
| 5 异步 | 浏览计数、足迹记录走 MQ,不阻塞主读链路 |
| 6 保护 | 网关按用户限流;Redis 故障时降级直连 DB(限流降 QPS) |
| 7 数据库 | 单表千万级内保持单表 + 从库扛报表查询 |
| 8 监控 | 命中率 < 90% 告警,RT 突增告警,大促前全链路压测 |
8.4 自查清单
- [ ] 容量有数字依据(估算 + 压测),不是拍脑袋;
- [ ] 每条读链路都回答了"缓存怎么设计、热点怎么防";
- [ ] 每条写链路都回答了"峰值怎么削、消息怎么不丢不重";
- [ ] 每个外部依赖都有超时 + 熔断 + 降级预案;
- [ ] 数据库方案遵循"索引 → 缓存 → 读写分离 → 分库分表"顺序;
- [ ] 有监控大盘、告警阈值与故障演练记录。
Cao Note