高并发系统如何提升吞吐量?从扩容、缓存、异步到数据库优化

高并发系统如何提升吞吐量?从扩容、缓存、异步到数据库优化

摘要:高并发系统不只是"挡住流量"。真正重要的是,在系统保持稳定的前提下,让更多正常请求能够被处理,让单位时间内完成更多业务。本文从 QPS 与吞吐量的区别开始,逐步讲清横向扩容、缓存、异步化、数据库优化,以及为什么系统能力提升之后仍然需要限流保护。

关键词:高并发、吞吐量、QPS、横向扩容、缓存、异步、数据库优化、容量规划


一、先看全局:高并发系统怎样真正提高吞吐量?

前面讲秒杀时,我们重点讨论了:

  • 漏桶
  • 令牌桶
  • 分层限流
  • Redis 库存准入
  • Kafka / RocketMQ 削峰

这些手段主要是在回答:

流量超过系统能力后,怎样避免系统被打垮?

但如果我们换一个问题:

系统现在只能稳定处理 5,000 QPS,怎样让它稳定处理 20,000、50,000 QPS?

答案就不能只是"继续限流"。

真正提高系统处理能力,一般围绕四个方向:

text 复制代码
扩容
+
缓存
+
异步
+
优化

最后再给新的系统容量设置:

text 复制代码
限流
+
熔断
+
降级

可以把这篇文章先记成一句话:

扩容、缓存、异步、优化是在"提高容量";限流、熔断、降级是在"保护容量"。


二、QPS 和吞吐量是一回事吗?

这是高并发里非常容易混淆的一个概念。

2.1 QPS 是什么?

QPS(Queries Per Second)通常表示:

系统每秒处理多少个请求。

例如:

text 复制代码
某接口 1 秒处理 5,000 个请求

QPS = 5,000

虽然名字是 Queries Per Second,但在 Web 接口、网关、压测场景里,经常直接用它表示"每秒请求数"。

2.2 吞吐量是什么?

吞吐量(Throughput)范围更广:

单位时间内,系统真正完成多少工作。

单位可以是:

text 复制代码
请求 / 秒
订单 / 秒
消息 / 秒
任务 / 秒
文件 / 分钟
MB / 秒
Token / 分钟

例如:

text 复制代码
API 每秒处理 5,000 个请求
→ QPS = 5,000

订单系统每秒成功创建 800 个订单
→ 业务吞吐量 = 800 单 / 秒

所以严格来说:

QPS 是吞吐量的一种请求层指标,但吞吐量不只等于 QPS。

2.3 为什么有时候两者看起来相等?

如果:

text 复制代码
一个请求
=
完成一次完整业务

例如:

text 复制代码
GET /product/1001

1 个请求
=
完成 1 次商品查询

那么:

text 复制代码
5,000 QPS
≈
5,000 次查询 / 秒

此时 QPS 和业务吞吐量数值可能接近。

但秒杀就完全不同:

text 复制代码
入口:50,000 QPS
        ↓
网关准入:5,000 QPS
        ↓
真正抢到库存:1,000 个请求
        ↓
订单消费者:300 单 / 秒

这里:

text 复制代码
入口 QPS = 50,000

最终订单吞吐量 = 300 单 / 秒

两者显然不是一个概念。

所以面试时比较准确的说法是:

QPS 是请求维度的吞吐指标;吞吐量描述单位时间完成的业务量。在"一次请求对应一次业务完成"的场景下,两者可能接近,但并不天然相等。


三、横向扩容:让更多服务器一起处理请求

提高系统吞吐量最直接的方法之一,就是:

增加计算资源。

假设一台 Spring Boot 服务稳定只能处理:

text 复制代码
3,000 QPS

那么可以通过负载均衡把请求分发到多台应用服务器:

text 复制代码
                  Nginx / Load Balancer
                          ↓
          ┌───────────────┼───────────────┐
          ↓               ↓               ↓
       App-01          App-02          App-03
      3000 QPS        3000 QPS        3000 QPS

理论上系统整体处理能力就有机会继续提高。

3.1 为什么服务最好设计成无状态?

如果用户 Session 只保存在某一台服务器内存:

text 复制代码
第一次请求
→ App-01
→ 登录成功

第二次请求
→ App-02
→ 找不到 Session

那么机器越多,问题反而越明显。

所以集群化时通常会:

text 复制代码
Token / JWT

或者:

text 复制代码
Session
↓
Redis

这样任意一台服务器都可以处理用户请求。

3.2 横向扩容并不是无限有效

如果应用服务器:

text 复制代码
App × 20 台

但最终所有请求还是:

text 复制代码
↓
同一个 MySQL

那么应用层扩容后,MySQL 很可能成为新的瓶颈。

所以高并发优化永远不是:

"加机器就结束。"

而是:

每提升一层容量,都要继续寻找下一层瓶颈。


四、缓存:让更多请求不用访问数据库

如果一个商品详情接口有:

text 复制代码
10,000 QPS

每个请求都查 MySQL:

text 复制代码
10,000 QPS
↓
10,000 次数据库查询 / 秒

显然成本非常高。

但很多数据:

  • 商品信息
  • 活动配置
  • 字典数据
  • 首页热点
  • 排行榜

并不会每毫秒变化。

这时就可以通过缓存让请求提前结束:

text 复制代码
用户
 ↓
浏览器 / CDN
 ↓
应用本地缓存
 ↓
Redis
 ↓
MySQL

假设:

text 复制代码
接口 QPS = 10,000
缓存命中率 = 95%

那么访问数据库的大约只有:

text 复制代码
10,000 × 5%
=
500 次 / 秒

这意味着:

系统不是拒绝了 9,500 个请求,而是让 9,500 个请求用更低成本完成。

这就是缓存提高吞吐量的本质。

4.1 多级缓存为什么有效?

不同缓存距离用户不同:

text 复制代码
浏览器缓存
→ 几乎不进入服务器

CDN
→ 不回源应用

本地缓存
→ 不访问网络

Redis
→ 不访问数据库

MySQL
→ 最终数据源

越靠前完成,请求成本越低。

4.2 缓存最大的问题不是"有没有",而是"失效时怎么办"

假设某个热点 Key:

text 复制代码
10,000 QPS

突然过期:

text 复制代码
10,000 请求同时 Cache Miss
↓
一起访问 MySQL

就可能出现缓存击穿。

常见处理方法:

  • 热点数据预热
  • 过期时间加随机值
  • 同 Key 请求合并
  • 互斥回源
  • 限制回源并发
  • 业务允许时返回短时间旧数据

所以:

高性能缓存设计,不只是提高命中率,还要保护缓存失效后的数据库。


五、异步化:缩短核心链路,让请求更快完成

一个订单接口可能最开始写成:

text 复制代码
用户下单
↓
创建订单
↓
扣库存
↓
发优惠券
↓
增加积分
↓
发送短信
↓
写业务日志
↓
返回用户

假设:

text 复制代码
核心下单       120ms
积分           80ms
优惠券         120ms
短信           300ms
其他通知       150ms

如果全部同步:

text 复制代码
总耗时可能达到 700~800ms+

请求占着连接、线程和内存,系统自然很难承受更高并发。

更合理的方式是:

text 复制代码
用户
 ↓
核心事务
订单 + 必要库存处理
 ↓
快速返回
 ↓
Kafka / RocketMQ
 ├→ 积分服务
 ├→ 优惠券服务
 ├→ 短信服务
 └→ 日志 / 通知服务

如果核心链路缩短到:

text 复制代码
150ms

而不是:

text 复制代码
800ms

同样的服务器资源,在单位时间内就有机会处理更多请求。

这就是异步化对吞吐量的帮助:

减少在线请求占用资源的时间。

5.1 异步并不是"把工作消灭了"

任务只是:

text 复制代码
从在线请求
↓
搬到后台消费者

所以消费者仍然需要:

  • 并发上限
  • 重试
  • 幂等
  • 消息积压监控
  • 数据库容量控制

如果消费者无限开线程:

text 复制代码
Kafka
↓
1000 个消费者并发
↓
MySQL

数据库依然可能被打垮。


六、数据库优化:高并发系统最后最容易卡住的地方

应用服务可以:

text 复制代码
App × 2
App × 10
App × 50

但所有请求最终可能仍然进入数据库。

所以很多高并发系统最终真正的瓶颈会变成:

text 复制代码
数据库连接
SQL
索引
IO
锁竞争
热点行

数据库优化一般建议从成本最低的地方开始。

6.1 SQL 与索引优化

例如:

sql 复制代码
SELECT *
FROM orders
WHERE user_id = ?
ORDER BY create_time DESC;

如果没有合理索引,就可能扫描大量数据。

优化思路包括:

  • 合理设计索引
  • 看执行计划
  • 减少 SELECT *
  • 避免不必要的全表扫描
  • 减少无意义 JOIN
  • 控制查询返回量
  • 避免在索引列上做导致索引失效的操作

很多时候,一条 SQL 从:

text 复制代码
500ms

优化成:

text 复制代码
20ms

带来的吞吐量提升可能比增加几台应用服务器更明显。

6.2 连接池和事务时间

假设 MySQL 最大稳定连接能力有限,而应用开了大量线程:

text 复制代码
应用线程:1000
数据库连接:100

那么:

text 复制代码
900 个线程

最终只是排队等待连接。

所以需要:

  • 合理连接池大小
  • 缩短事务
  • 尽快释放连接
  • 避免事务中调用慢 HTTP 接口
  • 避免长事务持锁

6.3 读写分离

如果系统:

text 复制代码
90% 查询
10% 写入

可以考虑:

text 复制代码
写请求
↓
Master
↓
复制
├→ Replica-01
└→ Replica-02
      ↑
     查询

让读取压力分散到多个只读节点。

但要注意:

主从复制存在延迟,强一致读不能机械地全部走从库。

6.4 分库分表

如果单库的数据量和并发确实已经到达瓶颈,可以进一步:

text 复制代码
按 user_id / order_id
↓
拆分多个数据库 / 多张表

这样实现水平扩展。

但它也会带来:

  • 分布式 ID
  • 跨库查询
  • 跨库分页
  • 聚合统计
  • 数据迁移
  • 跨库事务

所以:

分库分表不是高并发的标配,而是单库真实达到瓶颈后的解决手段。


七、代码层优化:让一次请求本身做得更少、更快

高性能不只是架构问题。

例如一个接口依次调用:

text 复制代码
查用户      50ms
查商品      50ms
查库存      50ms
查优惠券    50ms
查积分      50ms

如果完全串行:

text 复制代码
总耗时 ≈ 250ms

如果这些调用彼此没有依赖,可以考虑并行:

text 复制代码
          ┌→ 用户
          ├→ 商品
请求 ─────┼→ 库存
          ├→ 优惠券
          └→ 积分
             ↓
           汇总

Java 中可以考虑:

  • CompletableFuture
  • Reactor
  • 虚拟线程
  • 合理线程池

但要特别注意:

并行不是免费的。

如果最终这些请求全部访问同一个数据库,而数据库连接池只有 50 个连接,那么把应用并发调到 1000:

text 复制代码
应用更并发
↓
数据库连接池更拥堵
↓
接口反而更慢

所以高并发优化必须看:

整条调用链,而不是某一段代码。


八、怎样知道吞吐量真的提高了?靠压测,而不是感觉

系统优化之后,不能只说:

"感觉快了。"

需要用压测证明。

至少关注:

text 复制代码
QPS
P95
P99
错误率
CPU
内存
GC
线程
数据库连接池
Redis 延迟
Kafka Lag
MySQL TPS
慢 SQL
锁等待

例如:

优化前

text 复制代码
稳定 QPS:5,000
P99:800ms
CPU:85%
错误率:1%
数据库连接池:经常满

优化后

text 复制代码
稳定 QPS:20,000
P99:250ms
CPU:65%
错误率:0.05%
数据库连接池:稳定

这才能说明:

系统不仅接收了更多请求,而且仍然保持可接受的延迟和错误率。

真正有意义的"系统支持 2 万 QPS",一定要带上:

text 复制代码
什么接口?
什么机器配置?
多少实例?
持续多久?
P99 多少?
错误率多少?
数据库状态怎样?

否则单独一个 QPS 数字没有太大意义。


九、吞吐量提高了,为什么最后还是要限流?

假设经过:

text 复制代码
扩容
+
缓存
+
异步
+
数据库优化

系统从:

text 复制代码
5,000 QPS

提高到了:

text 复制代码
50,000 QPS

是不是就可以把令牌桶、漏桶、熔断全部删除?

还是不行。

因为系统永远存在最终容量。

如果突然来了:

text 复制代码
300,000 QPS

那么仍然需要:

  • 令牌桶
  • 漏桶
  • 并发上限
  • 熔断
  • 降级

所以最终正确的关系应该是:

text 复制代码
提高系统能力
    ↓
扩容 / 缓存 / 异步 / 优化
    ↓
新的容量边界
    ↓
限流 / 熔断 / 降级
    ↓
保护新的容量

因此:

高性能不是无限处理,高并发也不是拒绝所有高峰。

真正成熟的系统是:

先尽可能提高处理能力,再清楚知道自己的边界,最后保护这个边界。


十、最后总结:高并发的目标,是在稳定前提下获得更高吞吐量

整篇文章其实可以压缩成这条路线:

text 复制代码
                高并发请求
                    ↓
              找到当前瓶颈
                    ↓
       ┌────────────┼────────────┐
       ↓            ↓            ↓
     横向扩容       缓存          异步
       ↓            ↓            ↓
   增加处理资源   降低请求成本   缩短核心链路
       └────────────┼────────────┘
                    ↓
                数据库优化
                    ↓
              提高系统吞吐量
                    ↓
               重新进行压测
                    ↓
              找到新的容量边界
                    ↓
          限流 / 熔断 / 降级保护

最后记住四句话:

QPS 是请求层的一种吞吐指标,但 QPS 不完全等于吞吐量。

高并发系统首先要提高处理能力,而不是先想着拒绝更多请求。

扩容、缓存、异步、数据库优化,是提高吞吐量最常见的四条主线。

系统能力提升之后,仍然需要限流、熔断和降级守住最终边界。

理解这四件事情以后,再看到"高并发、高性能"这样的系统设计题,就不会只想到 Redis、Kafka 或令牌桶,而是能够真正从容量、成本、瓶颈和吞吐量的角度分析整个系统。

相关推荐
凤山老林1 小时前
高并发系统架构设计:Spring Boot 多级限流、库存预扣减与异步下单全链路实战
springboot·高并发·限流·异步
PiaoKe___1 小时前
从端云解耦到可编程 Android 实例:云手机架构解析与 Python 批量管控实战
arm开发·python·架构·自动化
mldong9 小时前
一个 App,十三套后端:手机审批端 uni-jeeflow-app 开源了
java·架构
weixin_BYSJ198711 小时前
【计算机毕设】基于SpringBoot与Vue的文物保护档案管理系统08621
vue.js·spring boot·spring cloud·微服务·架构·django·课程设计
GreenTea11 小时前
7000 万 QPS、500 PB:OpenAI 如何用一个 Python 存储平台撑住 10 亿用户
后端·架构
西安栈上月明软件科技11 小时前
从业务黑话到本体图谱:OAG本体建模五步法(西安老系统AI化改造实战)
数据库·人工智能·架构
海宇AI13 小时前
零信任架构实战:基于海宇柠檬查出险-登记证构建自动化车抵贷核保网关
java·人工智能·架构·自动化
白远山15 小时前
家政服务家政社区派单实战:调度系统架构设计与实现指南
java·开发语言·小程序·架构·需求分析
故作春风17 小时前
elpis-core 核心从入门到理解
后端·架构·node.js