Skip to content

高并发系统设计 ​

从容量评估到分层防护、从缓存与异步化到隔离熔断降级限流,给出一套"先量化、再分层、后取舍"的高并发系统设计方法论,附通用 8 步设计法。


一、容量评估 ​

1.1 核心指标定义 ​

指标定义说明
QPS每秒查询/请求数互联网系统最常用
TPS每秒事务数一个完整业务事务(如下单),事务可含多次请求
RT响应时间(平均/TP99/P99)单次请求耗时,P99 比平均值更能反映尾部体验
并发数系统中同时处理的请求数由 Little 定律:并发数 ≈ QPS × RT
吞吐量单位时间成功处理的请求数系统能力的上限表达

Little 定律是容量估算的锚:并发线程数 = QPS × RT(秒)。例如 QPS 2000、RT 0.1s,则并发约 200,可据此决定线程池大小与实例数。

1.2 容量估算方法 ​

估算公式链:

text
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
Redis5~10 万 QPS
MySQL(简单查询)数千 QPS
MySQL(写,单机)数百~千级 TPS

1.3 峰值因子与业务特征 ​

峰值因子不能套固定数字,按业务节奏定:

  • 常态业务(后台管理系统):峰值因子 3~5 倍;
  • C 端日常:峰值因子 5~10 倍(晚高峰);
  • 大促/热点事件:需按"预热流量 + 活动预估"单独建模,峰值因子可到 20~50 倍甚至更高(如秒杀)。

削峰思想出现在容量评估时就应开始:估算的不是"扛住峰值"而是"峰值中多少必须实时处理、多少可以削峰异步"(见第五章)。

1.4 压测目标与容量水位 ​

  • 压测目标:找到系统的"拐点"——RT 开始陡增的 QPS(性能拐点)、以及错误率超过阈值的 QPS(容量上限),而不是压出最大数字;
  • 容量水位约定:生产运行水位不超过单机容量的 60%~70%,预留故障转移与突增缓冲;
  • 压测类型:单接口压测(定位瓶颈)→ 链路压测(全链路容量)→ 混合场景压测(贴近真实)→ 大促前全链路压测;
  • 数据要求:压测数据量必须贴近生产(大数据量下索引与缓存命中率才真实)。

1.5 容量评估案例 ​

一次大促预热场景:

text
预估大促当日 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 分层防护的三条原则 ​

  1. 能静态不动态:静态资源、可预生成的页面一律上 CDN;
  2. 能缓存不查库:读请求优先在缓存层消化;
  3. 能异步不阻塞:非即时结果走 MQ 削峰,让昂贵的写能力匀速运行。

架构师在每一层都要问:这层能替我挡掉多少流量?如果流量全打到数据库,说明前面的层全都失职了。


三、读多写少与写多读少 ​

3.1 读多写少场景(内容/商品/资讯类) ​

典型特征:读写比 10:1 以上,读有热点、可容忍短暂不一致。

设计思路:

  1. 缓存前置:详情/列表全部缓存,key 按业务聚合(见第四章);
  2. CDN 卸载:图片、静态页、预渲染结果上 CDN;
  3. 读模型专门化:复杂查询投影到宽表/搜索/缓存(参考 DDD 笔记的 CQRS);
  4. 热点隔离:发现热点 key 自动本地缓存 + 多级缓存兜底(防击穿)。

3.2 写多读少场景(日志、埋点、交易流水类) ​

典型特征:写入量大、读少但要求查询能力、写不能丢。

设计思路:

  1. 批量与异步:写入先落本地/内存缓冲,攒批后批量刷库或发 MQ;
  2. 顺序写:日志类数据用追加写模型(如消息队列落盘、LSM 存储),避免随机写;
  3. 写入分片:按业务键哈希分库分表,分散单库写压力(决策见第七章);
  4. 读写分离:主库扛写,从库扛读与分析(延迟可容忍时)。

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 异步化的三个目的 ​

  1. 削峰填谷:瞬时高峰落 MQ,消费端匀速处理,保护数据库与下游;
  2. 降低 RT:非即时步骤(发短信、算积分)从同步链路摘除,主链路更快返回;
  3. 解耦:核心流程只发事件,扩展方订阅(与 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 分库分表决策树 ​

决策要点:

  1. 分片键(Sharding Key)是分库分表的灵魂:必须按最高频查询路由,如订单按 user_id 分片、订单明细与订单同片(避免跨片 join 与事务);
  2. 分库分表无法回避的三件事:跨分片查询(汇总层/宽表/ES 兜底)、分布式 ID(见《经典系统设计案例》附表)、数据迁移与扩容(一致性哈希 + 虚拟节点 / 双写迁移);
  3. 事务边界收窄到单分片内,跨分片事务用最终一致性(见《微服务架构与拆分》第六章);
  4. 先垂直拆分(按业务拆库)再水平分表,不要一上来就水平分 64 表。

7.4 详版指引 ​

分库分表与读写分离的完整实现细则(分片策略、中间件配置、迁移方案)详见《MySQL深入》分库分表章节,本笔记只保留决策树与方法论。判断口诀:先索引,再缓存,后读写分离,最后才是分库分表——越靠后的手段成本越高、越不可逆。


八、高并发系统设计通用 8 步法 ​

8.1 8 步总览 ​

  1. 明确场景与指标 → 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 自查清单 ​

  • [ ] 容量有数字依据(估算 + 压测),不是拍脑袋;
  • [ ] 每条读链路都回答了"缓存怎么设计、热点怎么防";
  • [ ] 每条写链路都回答了"峰值怎么削、消息怎么不丢不重";
  • [ ] 每个外部依赖都有超时 + 熔断 + 降级预案;
  • [ ] 数据库方案遵循"索引 → 缓存 → 读写分离 → 分库分表"顺序;
  • [ ] 有监控大盘、告警阈值与故障演练记录。