第 1 讲:高并发系统基础认知 -- 指标、瓶颈与架构演进
核心结论(7 条必记)
- 高并发核心指标 -- QPS(每秒查询数)、TPS(每秒事务数)、RT(响应时间)、并发数 = QPS x RT
- 不要只看平均值要看 P99 -- 平均值被极端值拉高掩盖真实体验,大厂通常要求 P99 < 200ms
- 系统瓶颈五大来源 -- CPU、内存、磁盘 IO、网络 IO、数据库,数据库是最常见瓶颈
- 架构演进是渐进的 -- 单机 -> 集群 -> 缓存+读写分离 -> 分库分表 -> 微服务 -> 多活
- 单机优化优先于分布式 -- 先做好索引优化、SQL 优化、本地缓存,再考虑分布式
- 容量评估要考虑峰值和安全系数 -- 按峰值 2-3 倍设计,至少 50% 冗余
- 每个技术引入都有时机 -- 缓存解决读瓶颈、MQ 解决写削峰、分库分表解决数据量瓶颈
一、高并发系统的度量指标
1. QPS vs TPS vs RT
| 指标 | 全称 | 含义 | 典型场景 |
|---|---|---|---|
| QPS | Queries Per Second | 每秒查询数,衡量系统吞吐 | 首页加载、搜索请求 |
| TPS | Transactions Per Second | 每秒事务数,一次完整业务流程 | 下单、支付、转账 |
| RT | Response Time | 响应时间,一次请求从发出到收到响应 | 所有请求 |
关键区别:
- QPS 是"查询",一次 HTTP 请求就是一个 QPS
- TPS 是"事务",一次下单可能包含多次请求(查库存 -> 创建订单 -> 扣库存 -> 支付)
- 一般情况下 QPS >= TPS,一个事务可能包含多个查询
2. 并发数公式
并发数 = QPS x RT举例:
- 系统QPS = 1000,平均RT = 0.1s
- 并发数 = 1000 x 0.1 = 100
- 意味着任意时刻系统中有约 100 个请求正在处理
这个公式是容量规划的基础,记住它。
3. P95 / P99 的意义
| 指标 | 含义 | 价值 |
|---|---|---|
| 平均值 | 所有请求的算术平均 | 容易被极端值拉高,参考价值有限 |
| P50(中位数) | 50% 的请求在此值以下 | 反映大多数用户体验 |
| P95 | 95% 的请求在此值以下 | 反映绝大多数用户体验 |
| P99 | 99% 的请求在此值以下 | 反映几乎全部用户体验 |
为什么看 P99 不看平均值:
- 100 个请求,99 个 10ms,1 个 10s -> 平均值 109ms,看起来还行
- 但有 1% 的用户体验极差,用户直接流失
- 大厂通常要求 P99 < 200ms,P999 < 1s
4. 三个"并发"的区别
| 概念 | 含义 | 说明 |
|---|---|---|
| 并发用户数 | 同时在线的用户数 | 1万在线用户不一定都在发请求 |
| 并发连接数 | 同时建立的 TCP 连接数 | 一个页面可能开 6 个连接 |
| 并发请求数 | 同时正在处理的请求数 | 最有意义的指标,即"并发数" |
经验比例:
- 并发请求数 / 并发用户数 约 1:10 ~ 1:100
- 1 万在线用户,实际并发请求可能只有 100-1000
5. 可用性(几个 9)
| 可用性 | 年停机时间 | 典型系统 |
|---|---|---|
| 99% | 3.65 天 | 内部工具 |
| 99.9% | 8.76 小时 | 一般互联网应用 |
| 99.99% | 52.6 分钟 | 核心业务(支付、订单) |
| 99.999% | 5.26 分钟 | 运营商级别 |
| 99.9999% | 31.5 秒 | 理论值 |
可用性每提升一个 9,成本指数级增长,业务要根据自身要求选择目标。
二、系统瓶颈定位
1. CPU 瓶颈
表现:load average 持续高于 CPU 核数,us% > 80%
常见原因:
- 计算密集型任务(加密、压缩、排序)
- 频繁 GC(Full GC 导致 CPU 飙升)
- 正则匹配、复杂算法
- 死循环、线程竞争
排查命令:
# CPU 使用情况
top -Hp <pid> # 查看进程内各线程 CPU 占用
vmstat 1 10 # 查看 CPU 上下文切换
mpstat -P ALL 1 5 # 每核 CPU 使用率
pidstat -p <pid> 1 5 # 进程级 CPU 使用优化方向:
- 算法优化、减少不必要的计算
- 异步化、并行化
- JVM 调优减少 GC
- 增加机器(垂直/水平扩展)
2. 内存瓶颈
表现:OOM、频繁 Full GC、swap 使用率高
常见原因:
- 内存泄漏(集合未清理、ThreadLocal 未 remove)
- 大对象(一次性加载大量数据)
- 缓存无限增长(无淘汰策略)
- 线程池过大(每个线程有栈空间开销)
排查命令:
# 内存使用
free -h # 系统内存概况
jmap -heap <pid> # JVM 堆内存分布
jmap -histo <pid> | head -20 # 对象数量排行
jstat -gcutil <pid> 1000 10 # GC 统计优化方向:
- 分页查询、流式处理,避免一次性加载
- 缓存设置上限和淘汰策略
- 修复内存泄漏
- 合理设置 JVM 参数(-Xms、-Xmx)
3. IO 瓶颈
磁盘 IO:
表现:iowait% 高,应用响应慢但 CPU 使用率不高
常见原因:
- 大量随机读写(数据库)
- 日志写入过多
- 文件上传下载
- 数据库 buffer pool 命中率低
排查命令:
iostat -x 1 10 # 磁盘 IO 统计
iotop # 进程级 IO 排行
df -h # 磁盘空间网络 IO:
表现:带宽打满、连接超时、丢包
常见原因:
- 大量小包传输
- 序列化/反序列化开销
- 缺少压缩
- 连接数过多
排查命令:
sar -n DEV 1 10 # 网卡流量
netstat -s # 网络统计
ss -s # 连接数汇总
tcpdump # 抓包分析优化方向:
- 数据库读写分离减少磁盘压力
- 批量操作减少 IO 次数
- 数据压缩减少网络传输
- 使用更高效的序列化协议
4. 数据库瓶颈(最常见)
表现:慢查询日志暴增、连接池耗尽、锁等待超时
常见原因:
- 慢查询(缺索引、全表扫描、索引失效)
- 锁等待(行锁、表锁、死锁)
- 连接数打满
- 大事务
- buffer pool 不够
排查命令:
-- MySQL 慢查询
SHOW PROCESSLIST; -- 当前连接状态
SHOW ENGINE INNODB STATUS; -- InnoDB 状态(含锁信息)
SELECT * FROM information_schema.INNODB_LOCKS; -- 当前锁
SELECT * FROM information_schema.INNODB_LOCK_WAITS; -- 锁等待# 慢查询日志分析
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
pt-query-digest /var/log/mysql/slow.log优化方向:
- 索引优化(最优先)
- SQL 优化(避免 SELECT *、子查询改 JOIN、分页优化)
- 读写分离
- 引入缓存
- 分库分表
5. 应用层瓶颈
常见原因:
- 线程池配置不合理(过小则排队,过大则资源竞争)
- 同步阻塞调用(远程调用无超时)
- 连接池耗尽
- 序列化开销
- 单点问题
排查方式:
# 线程状态
jstack <pid> | grep -A 5 "BLOCKED\|WAITING" # 查看阻塞线程优化方向:
- 合理配置线程池(核心线程数、队列、拒绝策略)
- 所有远程调用设置超时
- 异步化(CompletableFuture、响应式编程)
- 连接池参数调优
三、从单机到分布式的架构演进
阶段 1:单机应用(QPS < 1000)
+-------------------+
| 应用 + 数据库 |
| (一台服务器) |
+-------------------+特点:
- 应用和数据库在同一台机器
- 架构简单,维护成本低
- 单点故障风险
瓶颈:
- CPU、内存、磁盘 IO 互相争抢
- 无法水平扩展
演进时机:QPS 接近 1000,或需要高可用
阶段 2:应用集群化(QPS 1000 - 10000)
+----------+
+--->| 应用 A |
| +----------+
+--------+ | +----------+
| Nginx |-----+--->| 应用 B |
+--------+ | +----------+
| +----------+
+--->| 应用 C |
+----------+
|
+----------+
| 数据库 |
+----------+引入技术:
- Nginx / HAProxy 负载均衡
- Session 共享(Redis / Token)
- 应用无状态化
瓶颈:
- 数据库成为单点(连接数、读写压力)
- 单库数据量增长
演进时机:数据库成为瓶颈,读写比例高
阶段 3:缓存 + 读写分离(QPS 10000 - 50000)
+----------+
+--->| 应用 A |---+
| +----------+ |
+--------+ | +----------+ | +---------+
| Nginx |-----+--->| 应用 B |---+--->| Redis |
+--------+ | +----------+ | +---------+
| +----------+ |
+--->| 应用 C |---+
+----------+ |
| +----------+
+--->| 主库(写) |
+----------+
|
+----------+
| 从库(读) |
+----------+引入技术:
- Redis / Memcached 缓存
- MySQL 主从复制 + 读写分离
- CDN(静态资源)
瓶颈:
- 缓存穿透/击穿/雪崩风险
- 主库写压力、单表数据量过大
- 从库延迟
演进时机:单表数据量超 2000 万、单库 QPS 超 5000
阶段 4:分库分表 + Redis 集群(QPS 50000 - 100000)
+----------+
+--->| 应用 A |---+
| +----------+ |
+--------+ | +----------+ | +------------------+
| Nginx |-----+--->| 应用 B |---+--->| Redis Cluster |
+--------+ | +----------+ | +------------------+
| +----------+ |
+--->| 应用 C |---+
+----------+ |
| +---------+ +---------+
+--->| 分库1 | | 分库2 |
+---------+ +---------+
| 分表1 | | 分表1 |
| 分表2 | | 分表2 |
+---------+ +---------+引入技术:
- ShardingSphere / MyCat 分库分表中间件
- Redis Cluster
- 数据库中间件(路由、聚合)
瓶颈:
- 分布式事务
- 跨库查询困难
- 运维复杂度陡增
- 单个服务过于庞大
演进时机:代码量 > 10 万行、团队 > 20 人、服务之间耦合严重
阶段 5:微服务 + MQ + 服务治理(QPS 100000+)
+------+ +----------+ +----------+ +----------+
| 网关 |-->| 用户服务 | | 订单服务 | | 商品服务 |
+------+ +----------+ +----------+ +----------+
| ^ |
v | v
+---------+ +---------+
| MQ | | 缓存 |
+---------+ +---------+
| |
v v
+----------+ +----------+
| 库存服务 | | 各自DB |
+----------+ +----------+
+--------------------------------------+
| 服务治理(注册/配置/限流/熔断) |
+--------------------------------------+引入技术:
- Spring Cloud / Dubbo 微服务框架
- Kafka / RocketMQ 消息队列
- 服务注册发现(Nacos / Eureka)
- 熔断限流(Sentinel / Hystrix)
- 配置中心、链路追踪、API 网关
瓶颈:
- 服务间调用延迟增加
- 分布式一致性问题
- 运维成本极高
演进时机:需要 99.99%+ 可用性、异地容灾
阶段 6:同城双活 / 异地多活
+-- 北京机房 --+ +-- 上海机房 --+
| | | |
| 应用集群 A |<------->| 应用集群 B |
| | 同步复制 | |
| 数据库 A |<-------->| 数据库 B |
| | | |
+--------------+ +--------------+引入技术:
- 数据同步(Otter / Canal + MQ)
- 全局唯一 ID(雪花算法)
- 异地流量调度
- 单元化架构
什么时候做:
- 业务可用性要求 > 99.99%
- 核心金融/交易业务
- 法规合规要求
架构演进不是越先进越好,而是刚好够用就好。过早引入复杂方案是灾难。
四、什么时候引入什么技术
引入缓存的时机
| 条件 | 说明 |
|---|---|
| 读远多于写 | 读写比 > 10:1 |
| 数据变化不频繁 | 商品信息、配置信息、字典数据 |
| 重复查询多 | 热点数据被大量请求访问 |
| 可接受短暂不一致 | 缓存更新有延迟 |
不适合缓存的场景:实时性要求极高(库存扣减)、写多读少、数据量小且查询简单
引入 MQ 的时机
| 条件 | 说明 |
|---|---|
| 写入峰值明显 | 秒杀、抢购等瞬时高并发写入 |
| 系统解耦需求 | A 系统写完数据,B/C/D 系统都要感知 |
| 异步处理 | 发短信、发邮件、日志处理 |
| 流量削峰 | 瞬时流量远超系统处理能力 |
不适合 MQ 的场景:实时性要求高(必须同步返回结果)、系统简单不需要解耦
分库分表的时机
| 条件 | 具体数值 |
|---|---|
| 单表数据量 | > 2000 万行(或单表文件 > 20GB) |
| 单库 QPS | > 5000 |
| 单表字段过多 | > 50 个字段考虑垂直拆分 |
| 索引效率下降 | 即使有索引查询也明显变慢 |
分库分表前先做:索引优化、SQL 优化、读写分离、缓存 -- 这些成本远低于分库分表
微服务的时机
| 条件 | 具体数值 |
|---|---|
| 代码量 | > 10 万行,单项目编译部署困难 |
| 团队规模 | > 20 人,多团队协作 |
| 独立扩展需求 | 不同模块负载差异大(商品浏览 vs 订单创建) |
| 故障隔离需求 | 一个模块挂了不能影响其他 |
过早微服务的代价:运维复杂度、调试困难、分布式事务、团队沟通成本 -- 小团队不要碰
多活的时机
| 条件 | 说明 |
|---|---|
| 可用性要求 | > 99.99%(年停机 < 53 分钟) |
| 容灾需求 | 机房级故障不能影响业务 |
| 合规要求 | 金融行业监管要求 |
| 用户分布 | 全国/全球用户,就近接入 |
五、容量评估方法
1. 基于 QPS 的机器估算
机器数 = (总 QPS x 安全系数) / 单机 QPS| 参数 | 说明 |
|---|---|
| 总 QPS | 系统需要承载的峰值 QPS |
| 安全系数 | 通常取 2-3,保证冗余 |
| 单机 QPS | 单台机器能稳定承载的 QPS |
举例:
- 总 QPS = 10000,安全系数 = 2,单机 QPS = 2000
- 机器数 = 10000 x 2 / 2000 = 10 台
2. 基于用户数的 QPS 估算
峰值 QPS = (DAU x 人均请求数) / 86400 x 峰值系数| 参数 | 说明 |
|---|---|
| DAU | 日活跃用户数 |
| 人均请求数 | 每个活跃用户每天产生的请求数 |
| 86400 | 一天的秒数 |
| 峰值系数 | 通常取 3-5(早晚高峰集中访问) |
举例:
- DAU = 100 万,人均 50 次请求/天,峰值系数 = 5
- 峰值 QPS = (1000000 x 50) / 86400 x 5 = 2894 QPS
3. 基于并发数和 RT
QPS = 并发数 / RT- 100 并发,RT = 50ms -> QPS = 100 / 0.05 = 2000
- 100 并发,RT = 200ms -> QPS = 100 / 0.2 = 500
RT 越短,同样并发数下的 QPS 越高,所以优化 RT 是提升吞吐的关键。
4. 单机性能参考数据
| 组件 | 单机 QPS 参考值 | 说明 |
|---|---|---|
| MySQL(简单查询) | 2000-5000 | 有索引的单表查询 |
| MySQL(复杂查询) | 200-500 | 多表 JOIN、聚合 |
| Redis(GET/SET) | 50000-100000 | 简单 KV 操作 |
| Redis(复杂操作) | 10000-30000 | SORT、LRANGE 等 |
| Kafka(单分区) | 10000-50000 | 顺序写入 |
| Nginx | 50000-100000 | 静态资源/反向代理 |
| Tomcat(普通接口) | 1000-3000 | 业务逻辑 + 数据库访问 |
| Spring Boot(空接口) | 5000-10000 | 无业务逻辑 |
以上为经验参考值,实际性能受硬件、数据量、业务逻辑复杂度影响,需压测确认。
六、面试高频题
Q1:你们系统的 QPS 是多少?怎么评估的?
DAU 统计 -> 计算日均请求总量 -> 除以 86400 得平均 QPS -> 乘以峰值系数(3-5)得峰值 QPS -> 再乘以安全系数(2)做容量规划 -> 压测验证单机 QPS -> 算出机器数
Q2:系统性能变慢,你怎么排查?
监控告警确认现象 -> top/vmstat 看 CPU -> free/jstat 看内存 -> iostat 看磁盘 IO -> sar 看网络 -> SHOW PROCESSLIST 看数据库 -> 慢查询日志定位 SQL -> explain 分析执行计划 -> 针对性优化
Q3:为什么看 P99 不看平均值?
平均值被极端值拉高 -> 99个 10ms + 1个 10s = 平均 109ms -> 看起来正常但 1% 用户体验极差 -> P99 反映绝大多数用户的真实体验 -> 大厂要求 P99 < 200ms
Q4:什么时候该做分库分表?
单表 > 2000 万行 -> 索引优化和 SQL 优化已做到极致 -> 读写分离和缓存已经上了 -> 单库 QPS > 5000 或单表文件 > 20GB -> 此时才考虑分库分表 -> 不是上来就分
Q5:如何保证高可用?
消除单点(集群部署) -> 故障自动转移(主从切换) -> 限流降级(保护核心链路) -> 熔断(快速失败不拖垮上游) -> 超时控制(不阻塞线程) -> 监控告警(快速发现) -> 容灾演练(验证预案)
Q6:微服务和单体怎么选?
看团队规模(< 20 人单体优先) -> 看代码量(< 10 万行单体优先) -> 看部署频率(各模块独立发布需求强则微服务) -> 看扩展需求(模块负载差异大则微服务) -> 小团队小项目用单体 -> 逐步拆分不要一步到位
练习题(待完成)
- [ ] 练习 1:你的系统 DAU = 50 万,人均每天 30 次请求,峰值系数 = 5,安全系数 = 2,单机 QPS = 2000。请计算需要多少台应用服务器?
- [ ] 练习 2:一个接口 P50 = 20ms,P95 = 50ms,P99 = 2s。请分析问题可能出在哪里,给出排查思路。
- [ ] 练习 3:电商系统日均订单 100 万,大促期间峰值 QPS 是日常的 10 倍。当前架构是 3 台应用 + 1 主 2 从 MySQL。请给出架构演进方案,说明每个阶段引入什么技术、解决什么问题。