高并发系统设计:架构演进与关键技术选型
本文系统梳理高并发系统设计的核心方法:从 QPS/TPS/响应时间等核心指标出发,讲解水平扩展 vs 垂直扩展的选择依据,深入分析多级缓存、读写分离、分库分表、消息队列削峰、CDN 加速、连接池与线程池、异步化改造等关键技术,并给出系统演进的实践路径。
性能工程系列:秒杀系统设计 · 性能优化方法论 · 相关:消息队列核心原理 · 分布式一致性
目录
| 章节 | 说明 |
|---|---|
| 核心指标 | QPS / TPS / 响应时间 / 并发数 |
| 三大通用方法 | Scale-out / 缓存 / 异步 |
| 水平扩展 vs 垂直扩展 | 两种扩容思路的适用场景 |
| 缓存层设计 | 多级缓存 / 本地缓存 / 分布式缓存 |
| 数据库扩展 | 读写分离 / 分库分表 |
| 消息队列削峰填谷 | 异步处理 / 解耦合 / 削峰 |
| CDN 静态资源加速 | 静态资源就近分发 |
| 连接池与线程池 | 池化技术的核心设计 |
| 系统演进路径 | 从单体到高并发的渐进演进 |
| 存储架构的演进(许式伟视角) | 存储层次、外存管理、对象存储与 CDN |
| 过载保护与容量规划(许式伟视角) | 雪崩成因、自适应节流、优雅降级 |
| 权衡法则:成本、效率、可靠性三角(郭东白视角) | 性能优化的商业价值量化、不同阶段的权重 |
| 分层高并发设计(李智慧视角) | 前端层/网关层/服务层/数据层各层的高并发策略 |
| 高并发三板斧的系统化讲解(李智慧视角) | 缓存/异步/水平扩展的深度展开与权衡 |
核心指标
| 指标 | 定义 | 说明 |
|---|---|---|
| QPS(Queries Per Second) | 每秒查询次数 | 衡量读请求处理能力 |
| TPS(Transactions Per Second) | 每秒事务次数 | 衡量写请求处理能力,通常 TPS < QPS |
| 响应时间(RT) | 从请求发出到收到响应的时间 | 通常关注 TP99(99% 请求的响应时间) |
| 并发数 | 同时处理的请求数量 | 并发数 = QPS × 平均响应时间 |
| DAU | 日活跃用户数 | 估算峰值流量的基础数据 |
峰值流量估算方法:
日均请求量 = DAU × 人均请求次数
平均 QPS = 日均请求量 / 86400
峰值 QPS ≈ 平均 QPS × 3(经验倍数)
设计目标 = 峰值 QPS × 4(预留余量)
参考基准:4核8G 云服务器上 MySQL 5.7 约可支撑 500 TPS / 10000 QPS。
三大通用方法
高并发系统设计的三种核心思路,类比治水方案:
graph TD
A["高并发流量冲击"] --> B["Scale-out<br/>横向扩展<br/>(分流,类比都江堰)"]
A --> C["缓存<br/>加速访问<br/>(拓宽河道)"]
A --> D["异步<br/>削峰解耦<br/>(水库蓄洪)"]
style A fill:#fcc,stroke:#c00
style B fill:#cfc,stroke:#060
style C fill:#cfc,stroke:#060
style D fill:#cfc,stroke:#060
水平扩展 vs 垂直扩展
| 维度 | Scale-up(垂直扩展) | Scale-out(水平扩展) |
|---|---|---|
| 方式 | 升级单机硬件(4核→8核,4G→8G) | 增加机器数量,组成分布式集群 |
| 优点 | 简单,无需改造代码 | 突破单机极限,理论上无上限 |
| 缺点 | 有物理上限,成本指数增长 | 引入分布式复杂性(一致性、故障处理) |
| 适用阶段 | 系统初期,流量未超单机极限 | 并发超过单机极限后 |
Scale-out 引入的核心问题:
- 某节点故障如何保证整体可用性?
- 多节点状态如何保持一致?
- 如何做到对使用方无感知地增删节点?
缓存层设计
缓存的本质
缓存是空间换时间的性能优化手段。核心原理:
内存寻址:~100ns
磁盘寻道:~10ms
差距:约 10 万倍
多级缓存架构
graph TD
U["用户请求"] --> CDN["CDN<br/>静态资源缓存"]
CDN --> NGX["Nginx<br/>静态页面缓存"]
NGX --> LC["本地缓存<br/>Guava Cache / Caffeine<br/>(热点数据,秒级 TTL)"]
LC --> DC["分布式缓存<br/>Redis / Memcached<br/>(动态数据,分钟级 TTL)"]
DC --> DB["数据库<br/>MySQL"]
style CDN fill:#cfc,stroke:#060
style LC fill:#cfc,stroke:#060
style DC fill:#cfc,stroke:#060
原则:请求尽量挡在上层,越往下层,并发承受能力越差。
三种缓存类型对比
| 类型 | 部署位置 | 适用场景 | 典型实现 |
|---|---|---|---|
| 静态缓存 | Nginx / CDN 节点 | 静态内容(文章、图片) | Nginx 静态文件、Squid |
| 分布式缓存 | 独立缓存集群 | 动态数据查询 | Redis、Memcached |
| 本地缓存 | 应用进程内 | 极端热点数据(明星微博、首页推荐) | Guava Cache、Caffeine、HashMap |
本地缓存示例
// 使用 Guava Cache 缓存首页推荐商品(30s 刷新)
LoadingCache<String, List<Product>> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.refreshAfterWrite(30, TimeUnit.SECONDS)
.build(new CacheLoader<String, List<Product>>() {
@Override
public List<Product> load(String key) throws Exception {
return productService.loadRecommended(); // 从数据库加载
}
});
缓存的不足
| 问题 | 说明 |
|---|---|
| 读多写少限制 | 写多场景命中率低,缓存意义不大 |
| 数据不一致风险 | 更新 DB 成功但更新缓存失败 → 脏数据 |
| 内存容量限制 | 必须设置 TTL,避免内存溢出 |
| 增加运维复杂度 | 多了一个需要排查的组件 |
缓存命中率是最重要的监控指标,热点数据命中率应 > 95%。
数据库扩展
读写分离
原理:利用主从复制,主库处理写请求,从库处理读请求。
graph LR
APP["应用层"] -->|"写"| M["主库 Master"]
APP -->|"读"| S1["从库 Slave 1"]
APP -->|"读"| S2["从库 Slave 2"]
M -->|"主从复制"| S1
M -->|"主从复制"| S2
适用场景:读多写少(大多数 Web 系统),读写比 10:1 以上效果显著。
分库分表
触发条件:单表数据量超过千万级别,索引无法全量缓存,查询性能下降。
两种拆分方式:
| 方式 | 思路 | 解决的问题 |
|---|---|---|
| 垂直拆分(分库) | 按业务模块拆分到不同数据库 | 故障隔离、减少单库压力 |
| 水平拆分(分表) | 按字段值将数据分散到多库多表 | 突破单表数据量瓶颈 |
水平拆分的两种规则:
1. 哈希拆分(适合实体表)
分库索引 = hash(userId) % 16
分表索引 = hash(userId) % 64
优点:数据分布均匀
缺点:范围查询困难
2. 区间拆分(适合时序数据)
按月份建表:order_2024_01、order_2024_02
优点:范围查询高效
缺点:存在热点(最新月份访问量最大)
分库分表引入的问题:
- 所有查询必须带分区键,否则需要全库全表扫描
- 跨库 JOIN 不再可用,需在业务层拼接
- COUNT 等聚合操作需要额外存储(如 Redis 计数)
实践原则:
- 性能没有瓶颈就不做
- 一次到位(如 16 库 × 64 表),避免多次迁移
- 考虑 HBase / MongoDB 等原生支持 auto sharding 的 NoSQL
消息队列削峰填谷
三大核心作用
graph TD
MQ["消息队列"] --> A["削峰填谷<br/>瞬时高峰写流量缓冲到队列<br/>由有限消费者平稳处理"]
MQ --> B["异步处理<br/>主流程同步<br/>次要流程异步(发优惠券/积分)"]
MQ --> C["解耦合<br/>生产者不直接依赖消费者<br/>消费者可独立扩展"]
秒杀场景下的削峰
问题:秒杀开始瞬间,1 万个数据库写请求同时到达,数据库濒临崩溃。
解决方案:
用户下单请求 → 写入消息队列 → 返回"排队中"
↓
有限个消费者线程(如 10 个)
↓
依次处理:校验库存 → 扣减 → 生成订单
↓
通知用户秒杀结果
容量规划:
商品数量:1000 件
单次处理时间:500ms
部署 10 个消费者 → 总处理时间:50s(用户等待上限)
使用消息队列需考虑的问题
| 问题 | 处理思路 |
|---|---|
| 消息丢失 | 生产者确认 + 消费者 ACK + 持久化 |
| 消息重复消费 | 消费端幂等设计(见稳定性篇) |
| 消息延迟过高 | 增加消费者数量;监控队列堆积量 |
| 消息顺序性 | 同一业务 key 路由到同一分区 |
CDN 静态资源加速
原理:将静态资源(图片、JS、CSS、视频)分发到全球各地的 CDN 节点,用户就近访问,减少源站压力和网络延迟。
graph LR
U1["北京用户"] --> CDN_BJ["北京 CDN 节点"]
U2["上海用户"] --> CDN_SH["上海 CDN 节点"]
CDN_BJ -->|"缓存未命中"| ORIGIN["源站服务器"]
CDN_SH -->|"缓存未命中"| ORIGIN
适用内容:
- 图片、视频、音频等媒体文件
- JS、CSS 等前端静态资源
- 下载类文件
不适用内容:动态接口(需要实时计算的 API)
连接池与线程池
池化技术的本质
核心思想:空间换时间。预先创建好对象,避免频繁创建/销毁的开销,同时对对象进行统一管理。
适用条件:对象创建耗时(如 MySQL 连接建立需要 TCP 三次握手 + 认证,约 4ms)且会被频繁使用。
数据库连接池
连接获取流程:
1. 当前连接数 < 最小连接数 → 创建新连接
2. 有空闲连接 → 复用空闲连接
3. 无空闲连接 && 当前连接数 < 最大连接数 → 创建新连接
4. 当前连接数 = 最大连接数 → 等待(checkoutTimeout)
5. 等待超时 → 抛出错误
关键配置(经验值):
- 最小连接数:10
- 最大连接数:20~30
连接有效性维护:
- 定期发送
SELECT 1探活(推荐,C3P0 方式) - 获取时校验(testOnBorrow,有额外开销,生产环境慎用)
线程池
JDK ThreadPoolExecutor 的特点:优先入队而非创建线程,适合 CPU 密集型任务。
Web 系统(IO 密集型)的改造思路(参考 Tomcat):
超过 coreThreadCount 时,优先创建线程(而非入队)
直到达到 maxThreadCount,再开始入队
这样可以在 IO 等待时充分利用 CPU
关键注意事项:
- 监控队列堆积量(重要告警指标)
- 禁止使用无界队列(会导致内存耗尽触发 Full GC)
- 核心线程数需要预热(系统启动时预先初始化)
// 合理的线程池配置示例
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, // corePoolSize
30, // maximumPoolSize
60L, TimeUnit.SECONDS, // keepAliveTime
new LinkedBlockingQueue<>(1000), // 有界队列!
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:调用方执行
);
系统演进路径
高并发系统的演进应该是循序渐进,以解决系统中存在的问题为驱动力的。
flowchart TD
A["单体架构<br/>1台 Web + 1台 DB"] -->|"DB 查询慢"| B["连接池 + 索引优化"]
B -->|"读流量大"| C["主从读写分离"]
C -->|"数据量大"| D["分库分表"]
D -->|"写流量峰值"| E["消息队列削峰"]
E -->|"静态资源慢"| F["CDN 加速"]
F -->|"服务耦合严重"| G["服务化拆分<br/>(微服务)"]
G -->|"服务治理复杂"| H["服务网格<br/>Service Mesh"]
style A fill:#fcc,stroke:#c00
style H fill:#cfc,stroke:#060
淘宝演进参考:购买现成系统 → DB 引擎升级 → 分库分表 → 缓存层 → 中间件研发 → "五彩石"服务化 → 支撑亿级 QPS。
核心原则:
- 最简单的设计满足当前需求
- 发现瓶颈再优化,而不是预先过度设计
- 引入新组件前评估团队的运维能力
存储架构的演进(许式伟视角)
许式伟从操作系统和基础平台的角度,提供了一套理解存储架构的底层视角。
存储层次与访问特性
寄存器(ns级)→ L1/L2 Cache(ns级)→ 内存(100ns级)
→ SSD(100μs级)→ 机械硬盘(10ms级)→ 网络存储(ms~s级)
每跨一个层次,访问延迟约上升 10-100 倍。架构设计的本质之一,就是把热数据尽量推向更快的存储层次。
外存管理的架构含义
文件系统是"自描述"的数据格式——它把存储设备中的数据组织成一棵树,让数据可以被随时查看和访问。这个设计思想直接影响了现代存储系统:
| 存储类型 | 特点 | 典型场景 |
|---|---|---|
| 关系型数据库 | B+Tree 索引,支持复杂查询 | 事务性业务数据 |
| 时序数据库 | LSM-Tree,写优化,TTL 自动清理 | 监控指标、日志数据 |
| 对象存储 | 扁平命名空间,RESTful API | 大文件、媒体资源 |
| KV 存储 | 极低延迟,简单数据模型 | 缓存、会话、计数器 |
对象存储与 CDN 的关系
对象存储(如 S3、七牛云)使用 HTTP RESTful API,天然与 CDN 配合:
用户请求 → CDN 边缘节点(命中缓存,就近返回)
↓ 缓存未命中
回源到对象存储(拉取并缓存)
为什么对象存储用 HTTP:HTTP 协议的开放性(可扩展的 Header、规范的 CURD 语义、基于域名的路由)使它成为最通用的应用层协议,CDN 节点天然支持 HTTP 缓存语义(ETag、Cache-Control)。
过载保护与容量规划(许式伟视角)
过载的成因与雪崩效应
过载的四大成因:
- 用户增长超预期:资源规划没跟上
- 部分资源下线:双机房容灾需要 2 倍资源储备,否则一机房下线就会过载
- 数据库随时间劣化:数据量增大导致延迟上升、并发下降
- 重试放大:失败重试 2 次 × 多层调用 = 请求数放大 9~81 倍
雪崩效应的正反馈循环:
超出容量 → 请求失败 → 重试放大请求数 → 更多失败 → 互备节点也被压垮 → 服务全挂
过载应对策略
服务端:
- 主动拒绝请求(快速失败,而非让请求排队等死)
- 基于 CPU 使用率而非 QPS 来判断过载(更稳定可靠)
- 优雅降级:按请求重要性分级(可丢弃→可延后→重要→非常重要)
客户端:
- 限制重试次数(通常不超过 2 次)
- 指数退避 + 随机抖动(防止重试风暴)
- 自适应节流:当
requests > K × accepts时,客户端开始本地丢弃请求(K=2 时服务端过载仍保持 50% 处理率)
权衡法则:成本、效率、可靠性三角
郭东白在实战中总结了一个反直觉但极其重要的权衡框架:在高速增长期,挣钱比省钱更重要;在成熟期,省钱才值得投入。
性能优化的商业价值量化
郭东白在 AliExpress 的性能优化案例展示了如何把技术工作归因到商业价值:
关键洞察:不是优化所有页面,而是找到性能损耗 / 优化成本比值最大的页面,依次优化。
性能损耗(Lpage) = 因加载慢而损失的转化率
预期订单增量 = f(Lpage, 流量, 转化漏斗)
优化 ROI = 预期订单增量 / 研发人日成本
结果:6 人兼职半年,全站订单数提升 10.5%,相关专利 13 项。
通用方法论:
- 把所有技术优化动作归因到同一个业务指标(如订单数)
- 按 ROI 排序,只做回报最大的优先项
- 发现性能优化回报不够大时,立即切换赛道(转向监控、分析等)
三角权衡的本质
graph TD
A["成本<br/>(人力/机器/时间)"] --- B["效率<br/>(开发速度/迭代速度)"]
B --- C["可靠性<br/>(稳定性/数据一致性)"]
C --- A
style A fill:#fcc
style B fill:#cfc
style C fill:#ccf
不同阶段的权重:
| 阶段 | 优先级 |
|---|---|
| 创业/高增长期 | 效率 > 可靠性 > 成本 |
| 成熟期 | 可靠性 > 成本 > 效率 |
| 衰退期 | 成本 > 可靠性 > 效率 |
做架构和做业务一样,都不能靠饱和攻击取胜,而是靠对阶段性精确目标的最大化投入来取得进步。
分层高并发设计(李智慧视角)
李智慧在《高并发架构实战课》中,将高并发系统的设计按技术层次分为四层,每一层都有对应的高并发策略。
分层架构总览
flowchart TD
A["前端层<br/>(浏览器/客户端)"] --> B["网关层<br/>(API 网关/Nginx)"]
B --> C["服务层<br/>(微服务集群)"]
C --> D["数据层<br/>(数据库/缓存/消息队列)"]
style A fill:#e3f2fd
style B fill:#e8f5e9
style C fill:#fff3e0
style D fill:#fce4ec
前端层高并发策略
目标:减少请求数量,降低服务端压力。
| 策略 | 说明 | 效果 |
|---|---|---|
| 静态资源 CDN | JS/CSS/图片就近分发 | 消除 90%+ 静态资源请求 |
| 资源合并 | 多个 JS/CSS 合并为一个文件 | 减少 TCP 连接数 |
| 浏览器缓存 | 设置 Cache-Control / ETag | 重复访问无需请求服务器 |
| 懒加载 | 图片/组件按需加载 | 减少首屏请求数 |
| 本地存储 | localStorage 缓存不变数据 | 减少重复 API 调用 |
网关层高并发策略
目标:流量过滤与路由,保护后端服务。
| 策略 | 说明 |
|---|---|
| 全局限流 | 令牌桶/漏桶,控制整体 QPS 上限 |
| 认证鉴权 | 在网关层统一处理,减少后端服务负担 |
| 黑名单过滤 | 拦截已知恶意 IP/用户 |
| 负载均衡 | 将流量均匀分发到后端服务实例 |
| 协议转换 | HTTP → gRPC,减少内部通信开销 |
服务层高并发策略
目标:提升单服务处理能力,实现水平扩展。
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 无状态化 | Session 外置到 Redis,服务可随意扩容 | 所有 Web 服务 |
| 异步处理 | 非核心逻辑通过 MQ 异步处理 | 发送通知、更新统计 |
| 本地缓存 | 热点数据缓存在进程内(Caffeine) | 极热点数据(首页推荐) |
| 线程池隔离 | 不同业务使用独立线程池,防止相互影响 | 微服务多依赖场景 |
| 服务降级 | 非核心依赖故障时返回兜底数据 | 推荐、评论等非核心功能 |
数据层高并发策略
目标:突破数据库单点瓶颈,支撑高并发读写。
| 策略 | 解决的问题 | 引入的复杂性 |
|---|---|---|
| 读写分离 | 读多写少场景,从库承担读压力 | 主从延迟导致读到旧数据 |
| 分库分表 | 单表数据量超千万,查询性能下降 | 跨库 JOIN 困难,需要分区键 |
| 多级缓存 | 数据库并发上限低(千级 TPS) | 缓存一致性问题 |
| 消息队列 | 写入峰值超过数据库处理能力 | 最终一致性,延迟增加 |
| NoSQL | 关系型数据库无法满足特定场景 | 功能有限,无事务 |
高并发三板斧的系统化讲解(李智慧视角)
三板斧的本质
李智慧将高并发系统设计归纳为三个核心手段:
graph TD
A["高并发系统"] --> B["缓存<br/>减少计算,提升读速度"]
A --> C["异步<br/>削峰解耦,提升写吞吐"]
A --> D["水平扩展<br/>突破单机极限"]
style B fill:#cfc,stroke:#060
style C fill:#cfc,stroke:#060
style D fill:#cfc,stroke:#060
缓存:不只是 Redis
缓存的层次与选型:
| 层次 | 技术 | 延迟 | 容量 | 适用数据 |
|---|---|---|---|---|
| CPU Cache | 硬件自动 | 1-10ns | KB 级 | 热点变量(JIT 自动优化) |
| 进程内缓存 | Caffeine / Guava | < 0.1ms | MB-GB 级 | 极热点数据,进程内共享 |
| 分布式缓存 | Redis / Memcached | 1-5ms | GB-TB 级 | 动态数据,跨实例共享 |
| CDN 缓存 | 云厂商 CDN | 5-50ms | 无限 | 静态资源,不变数据 |
缓存的核心权衡:
- 一致性 vs 性能:缓存 TTL 越短,一致性越好,但缓存效果越差
- 内存 vs 命中率:缓存越大,命中率越高,但内存成本越高
- 复杂度 vs 收益:多级缓存命中率高,但维护复杂度高
缓存的适用条件(并非所有数据都适合缓存):
适合缓存:
✅ 读多写少(读写比 > 10:1)
✅ 数据相对稳定(TTL 内变化少)
✅ 计算代价高(复杂查询、聚合计算)
✅ 访问有热点(20% 数据承受 80% 访问)
不适合缓存:
❌ 写多读少(缓存命中率低,维护成本高)
❌ 强一致性要求(如账户余额)
❌ 数据量极大且访问均匀(无热点)
异步:不只是消息队列
异步的三种形式:
| 形式 | 原理 | 适用场景 |
|---|---|---|
| 消息队列异步 | 生产者发消息,消费者异步处理 | 削峰、解耦、跨系统 |
| 线程池异步 | 主线程提交任务,线程池异步执行 | 进程内异步,减少响应时间 |
| 响应式编程 | 非阻塞 IO + 事件驱动 | 高并发 IO 密集型场景 |
异步的代价:
- 最终一致性:异步处理完成前,数据可能不一致
- 错误处理复杂:异步失败需要补偿机制(重试/幂等/死信队列)
- 调试困难:异步链路追踪比同步复杂
水平扩展:不只是加机器
水平扩展的前提:服务必须是无状态的,或状态可以分片。
扩展的三个维度(AKF 扩展立方体):
X 轴扩展:水平复制(加机器)
→ 解决:单机容量不足
→ 局限:所有实例处理相同数据,DB 仍是瓶颈
Y 轴扩展:功能分解(微服务拆分)
→ 解决:单体应用复杂度高,无法独立扩展
→ 局限:引入分布式复杂性
Z 轴扩展:数据分片(分库分表)
→ 解决:数据量超过单机极限
→ 局限:跨分片查询困难
实践建议:优先 Y 轴(服务拆分),再做 X 轴(水平扩容),必要时做 Z 轴(数据分片)。
参考资料
- 《高并发系统设计 40 问》— 唐扬(极客时间专栏)
- 《从0开始学架构》— 李运华(极客时间专栏)
- 《数据密集型应用系统设计》(DDIA)— Martin Kleppmann(O'Reilly)
- 周志明《凤凰架构》— 极客时间专栏
- 郭东白《架构课》— 极客时间专栏
- 许式伟《架构课》— 极客时间专栏
- 李智慧《高并发架构实战课》— 极客时间专栏
评论 (0)