高并发世界入门:理解并发的本质与挑战
第 1 章 高并发世界入门:理解并发的本质与挑战
1.1 从一场双十一说起:高并发为何成为时代刚需
2023 年 11 月 11 日 0 点整,某头部电商平台的交易系统接收到峰值 583 万笔/秒 的订单创建请求;另一侧,12306 在春运抢票高峰期的瞬间点击量同样冲到了亿级。再把镜头拉近——当我们打开抖音、今日头条,请求一个视频流的瞬间,背后可能正有上百万用户同时在拉取相同或不同的内容。
这些场景都指向同一个关键词:高并发。它不是一个抽象的学术名词,而是直接决定一家企业能否承接流量红利、能否避免雪崩、能否把用户体验做出来的工程现实。
那么问题来了:高并发究竟难在哪里? 我们从一个最朴素的小例子开始。
假设你经营一家只有 1 个收银员的便利店,顾客平均每单需要 30 秒结账。当只有 1 个顾客时,一切顺畅;当同时来了 10 个顾客,最坏情况下第 10 个顾客要等 5 分钟——这就是串行排队。当顾客数量继续增长,等待时间会无限放大,体验会彻底崩溃。
把这个例子映射到互联网系统:
- **收银员** = 服务器的 CPU 核心(或一个工作线程)
- **顾客** = 用户请求
- **每单 30 秒** = 一次请求处理耗时
- **队列等待 5 分钟** = 用户感受到的响应延迟
让系统从“勉强能用”走向“稳如磐石”的过程,本质上就是让有限的服务能力去承接无限增长的请求量。这个过程涉及三个层面的难题:
1. 吞吐瓶颈:单位时间内能处理多少请求(QPS/TPS),如何随着流量增长而水平扩展; 2. 响应延迟:单个请求从发起到拿到结果需要多久,特别是尾部延迟(P99、P999); 3. 数据一致性:在多份数据、多台机器、多个副本同时被访问时,如何保证不丢、不重、不错。
这三个难题并不是孤立的。在真实生产环境中,它们往往互相牵制:为了提升吞吐而引入缓存,可能让数据一致性变差;为了降低延迟而做异步化,又可能引入额外的复杂度。这正是高并发系统设计的迷人之处——它本质上是一门在约束下做权衡的工程艺术。
理解了“为什么要学”,我们接下来回答“究竟什么是并发”。
---
1.2 串行与并发:最直观的对比实验
为了建立对并发的直觉,我们先用一张图对比串行执行与并发执行的任务处理模型。
```mermaid flowchart LR subgraph Serial[串行执行: 1 个工人] S1[任务1] --> S2[任务2] --> S3[任务3] --> S4[任务4] end subgraph Concurrent[并发执行: 4 个工人] C1[任务1] C2[任务2] C3[任务3] C4[任务4] end ```
串行执行意味着:所有任务排成一列,前一个任务完成之前,后一个任务只能等待。即使你的电脑有 8 个 CPU 核心,一次也只能跑一个任务——其余 7 个核心在“打酱油”。
并发执行意味着:多个任务可以在重叠的时间窗口内被推进。这里的关键是“重叠”——并不要求多个任务真正同时运行,而是让它们看上去在同时进行。
如果你有 4 个独立任务,每个任务耗时 1 秒:
| 执行模式 | 理论耗时 | 备注 | | --- | --- | --- | | 串行(1 个执行单元) | 4 秒 | 所有任务排队 | | 并发(4 个执行单元) | ≈ 1 秒 | 任务并行处理 | | 完美并行(4 核 CPU) | ≈ 1 秒 | 真正同时执行 |
可以看到:并发/并行的核心收益,是把任务的总耗时摊薄。这种摊薄在两种情况下特别有价值:
- **I/O 密集型任务**:比如调用数据库、调用第三方 API,CPU 大量时间都在等待网络往返。如果用单线程串行,每次 I/O 等待都是“空转”;改用多线程,可以让线程 A 等待 I/O 时,线程 B 去干别的事。
- **计算密集型任务**:比如图像处理、加密解密、批量计算。在多核 CPU 上,把一个大任务拆给多个核心一起算,速度随核心数近似线性增长。
读者可以用一个简单判断标准决定是否值得引入并发:如果你的程序瓶颈在等待(I/O 等待、锁等待、网络等待),并发往往收益巨大;如果瓶颈在 CPU 计算本身,并且已经是单核跑满,那并发要先解决“能不能让多个核心同时算”的问题。
> 💡 侧边栏:为什么说“并发 ≠ 并行”? > > Rob Pike 有一句被引用无数次的解释:“并发是关于处理(dealing with)很多事情,并行是关于同时做(doing)很多事情。” 并发是一种程序结构,并行是一种程序执行状态。一个并发设计良好的程序,在单核机器上也能跑(只是不会真并行),但同一个程序放到多核机器上就能立刻获得并行加速。本书会反复回到这个区分上来。
---
1.3 并发与并行的关键差异:CPU 多核时代的执行模型
在 2005 年之前,大多数程序员写单线程就够了,因为 CPU 主频每年都在涨,“摩尔定律”让代码“跑得更快”几乎是免费的。然而从 2005 年前后开始,单核主频增长陷入停滞,CPU 厂商转向了多核。
这意味着一个严酷的事实:如果你的程序只用 1 个核心,再新的 CPU 也只能榨出那 1/8、1/16 的算力。剩余的核心对你是完全无感的。这就是为什么“会写并发程序”在今天变成了工程师的必备技能。
从执行模型上看,三者有清晰的层级关系:
- **串行(Sequential)**:任务按时间顺序执行,不重叠。
- **并发(Concurrent)**:任务结构上允许重叠地推进,但物理上仍可能在单核上交替执行。
- **并行(Parallel)**:任务在物理上同时执行,必须有多核或多机支持。
它们之间不是非此即彼,而是层层递进:串行 ⊂ 并发 ⊂ 并行 的可能性。一个好的并发程序具备“结构上的并发性”,可以在不同硬件条件下灵活切换:
```mermaid flowchart TD A[程序源码] --> B{是否多线程?} B -- 否 --> C[串行执行] B -- 是 --> D{运行在几核 CPU?} D -- 1 核 --> E[并发结构, 但物理串行] D -- 多核 --> F[真正并行执行] ```
理解这一点对系统设计至关重要:你写的不只是“在某台机器上的某段代码”,而是一段可扩展、可分发的程序。
举个真实例子。假设你写了一个文件压缩工具,逻辑是“读一个 1GB 文件 → 用 zlib 压缩 → 写到磁盘”:
- **串行版本**:1 个线程,按部就班执行。如果磁盘和 CPU 都只用到 50%,那另外 50% 就浪费了。
- **并发版本**:拆成 4 个块,4 个线程同时压缩每一块。CPU 利用率立刻拉到 80% 以上。
- **并行版本**:在 8 核机器上跑,把块拆成 8 份,8 个线程同时压,进一步压榨多核。
并发给了你一种“按需扩展”的能力——硬件越多,收益越大。
---
1.4 高并发的三大目标:高吞吐、低延迟、数据一致性
任何高并发系统设计,最终都要回答三个核心问题。这三者构成了系统设计的不可能三角:
1. 高吞吐(Throughput):单位时间内能处理多少业务。衡量指标是 QPS(Queries Per Second)和 TPS(Transactions Per Second)。 2. 低延迟(Latency):单个请求从发起到拿到响应的时间。常用 P50、P95、P99、P999 来衡量尾部延迟。 3. 数据一致性(Consistency):多份数据在并发访问下保持逻辑正确的程度。包括强一致性、最终一致性等多种级别。
这三个目标常常互相冲突,需要根据业务场景做权衡:
场景一:电商秒杀——优先吞吐与最终一致性
秒杀场景在双十一、限时抢购中很常见。它的特征是:
- 短时间内涌入的请求量是日常的 10~100 倍;
- 库存只有 100 件,但可能有 100 万人同时抢;
- 用户对“库存显示”和“下单结果”敏感,但对“先下单还是先付款”的强一致性不敏感。
针对这种场景,业界典型的做法是:
- **前端拦截**:验证码、答题、按钮置灰,过滤掉 90% 的无效点击;
- **接入层限流**:用 Nginx、Lua 脚本按 IP/用户限流,把真实流量从百万级降到十万级;
- **缓存预扣**:库存预热到 Redis,用 Lua 脚本原子扣减,挡住 95% 的请求;
- **消息队列削峰**:通过 Kafka/RocketMQ 把瞬时流量拉平,下游消费方按自己的节奏处理;
- **最终一致性**:用户下单后异步通知、异步发货,订单状态走最终一致性。
在这个链路里,吞吐是首要目标,延迟次之,强一致性则被有意弱化(用最终一致性替代)。
场景二:12306 抢票——复杂的一致性与体验平衡
12306 是一个更复杂的高并发系统。它要求:
- 同一张票不能被两个人同时买到(**强一致性**);
- 余票查询要快,不能让用户盯着空白页等(**低延迟**);
- 全国人民同时抢票不能崩(**高吞吐**)。
它的解法体现了工程上的“分而治之”:
- **数据分片**:把全国车票按车次/区间分片,分散到不同数据库节点;
- **排队机制**:用户抢票先进入排队队列,按到达顺序处理,避免热点车厢被反复查询;
- **缓存与异步**:余票查询走 Redis,写操作走主库,读写分离;
- **限流降级**:高峰期关闭“复杂查询”功能,保核心购票流程。
场景三:金融支付——延迟与一致性都不能丢
支付系统对延迟和一致性都非常敏感:
- 用户付款后必须在 2 秒内看到结果(**低延迟**);
- 资金绝不能多扣、错扣、漏扣(**强一致性**);
- 双十一凌晨也要扛得住(**高吞吐**)。
这种场景往往通过事务、分布式锁、两阶段提交、TCC、Saga 等复杂模式实现,但代价是吞吐会有损失。
把这三个场景摆在一起看,我们可以提炼出高并发设计的底层方法论:
> 明确业务优先级 → 选择合适的一致性级别 → 用缓存/异步/分片等手段优化吞吐和延迟 → 在所有关键节点埋点监控
这三个目标之间的关系不是“哪个最重要”,而是“在你的业务里谁先谁后”。读完全书你会发现,几乎所有的架构决策都是在三者之间做有意识的取舍。
---
1.5 衡量高并发的核心指标:QPS、TPS、响应时间、P99
不度量就无法优化。要判断一个系统是不是“高并发”,需要一套公认的可观测指标。下面这些指标会贯穿本书始终。
1.5.1 QPS(Queries Per Second)
QPS 是每秒查询数,衡量系统每秒能响应的请求数量。计算公式:
$$QPS = \frac{\text{一段时间内的总请求数}}{\text{该段时间(秒)}}$$
例如,系统 1 分钟内处理了 60000 次请求,那么 QPS = 60000 / 60 = 1000。
需要注意的是,QPS 有时是“查询次数”,有时是“请求次数”,不同公司口径不同。把它换成更准确的表述——RPS(Requests Per Second)——会更通用。
1.5.2 TPS(Transactions Per Second)
TPS 是每秒事务数,衡量“业务上完整一笔交易”的处理速度。事务的定义取决于业务:对电商来说可能是一笔订单,对支付来说可能是一次转账。
> QPS 与 TPS 的关系:一个事务可能由多个内部请求组成。例如一次下单事务可能包含:查询库存、扣减库存、创建订单、扣减账户余额——4 个内部请求。如果这笔事务每秒能完成 200 次,那么 TPS = 200,而内部 QPS = 800。
1.5.3 响应时间(Response Time,RT)
响应时间是单个请求从发起到收到完整响应所耗费的时间。常见度量:
- **平均 RT**:所有请求耗时的算术平均值。容易被极端值拉偏,对长尾不敏感。
- **P50 RT**:50% 的请求在该时间内完成。中位数,比平均值更稳定。
- **P95 RT**:95% 的请求在该时间内完成。反映绝大多数用户的体验。
- **P99 RT**:99% 的请求在该时间内完成。长尾指标,反映最倒霉的 1% 用户的体验。
- **P999 RT**:99.9% 的请求在该时间内完成。极端尾部,对稳定性要求极高的系统(如支付)会用。
为什么 P99 比平均值重要?看一个例子:
| 场景 | 平均 RT | P99 RT | 结论 | | --- | --- | --- | --- | | 健康系统 | 50 ms | 200 ms | 99% 用户 200ms 内拿到结果 | | 隐患系统 | 80 ms | 3000 ms | 平均看似还行,但 1% 用户要等 3 秒 |
平均 80ms 看起来很美,但 P99 长达 3 秒意味着有相当一部分用户会“卡死”——这往往是事故的前兆。高并发系统必须看 P95/P99,绝不能只看平均值。
1.5.4 并发数(Concurrency)
并发数是系统同时处理的请求数。它和 QPS 的关系是:
$$\text{QPS} = \frac{\text{并发数}}{\text{平均 RT(秒)}}$$
举例:如果系统平均 RT 是 0.1 秒,要支持 1000 QPS,需要的并发数 = 1000 × 0.1 = 100。这意味着服务端需要同时维护 100 个活跃请求。
这个公式非常重要,它告诉我们:当平均 RT 升高时,相同的 QPS 需要更多的并发数——而更多的并发数意味着更多的线程、更大的内存占用、更高的锁竞争概率。所以延迟一旦劣化,往往会引发连锁反应。
1.5.5 其他常用指标
- **错误率(Error Rate)**:失败请求占总请求的比例。一般生产环境要求 < 0.1%。
- **可用性(SLA / Availability)**:通常用几个 9 来表示,99.99% 表示一年最多停机 52.6 分钟。
- **容量(Capacity)**:系统在不超过 SLA 阈值的前提下,能承受的最大 QPS。这是压测要测出来的数字。
记住一句话:所有架构决策的起点,都是这些指标。没有数字支撑的“优化”,要么是耍流氓,要么是赌博。
---
1.6 本书学习路线图:从一根线到一张网
高并发是一个庞大的话题,单凭一本书无法面面俱到。本书的策略是:先打地基,再起高楼,最后封顶。全书 15 章分成 5 大模块:
模块一:基础认知(第 1~3 章)
- 第 1 章:你正在读的,理解并发本质和核心指标。
- 第 2 章:Java 多线程基础,线程的生命周期、锁、volatile、synchronized。
- 第 3 章:JVM 内存模型与线程安全,让你真正理解“可见性、原子性、有序性”。
模块二:并发工具与设计模式(第 4~6 章)
- 第 4 章:JUC 工具类(CountDownLatch、CyclicBarrier、Semaphore、Future)。
- 第 5 章:线程池(ThreadPoolExecutor)的原理与最佳实践。
- 第 6 章:生产者-消费者、Future、Master-Worker 等并发设计模式。
模块三:高性能中间件(第 7~9 章)
- 第 7 章:缓存体系,从本地缓存(Guava、Caffeine)到分布式缓存(Redis Cluster)。
- 第 8 章:消息队列(Kafka、RocketMQ)在削峰、解耦、异步中的角色。
- 第 9 章:数据库分库分表与读写分离。
模块四:分布式高并发架构(第 10~12 章)
- 第 10 章:分布式锁(ZooKeeper、Redis、数据库)。
- 第 11 章:分布式事务(2PC、TCC、Saga、Seata)。
- 第 12 章:限流、熔断、降级(Sentinel、Hystrix)。
模块五:真实战场与生产实践(第 13~15 章)
- 第 13 章:秒杀系统从 0 到 1 的完整设计。
- 第 14 章:监控与可观测性(Prometheus、Grafana、链路追踪)。
- 第 15 章:性能调优方法论与案例复盘。
每章末尾都有 动手实验 与 思考题。强烈建议读者边读边敲代码——高并发的能力不在脑子里,在指尖上。
---
1.7 开发环境搭建:JDK、Maven、IDE
工欲善其事,必先利其器。下面带你一步步搭好环境。所有步骤在 macOS、Linux、Windows 上都通用,建议使用 JDK 17(本书示例代码兼容 JDK 8+,但推荐 17 或 21)。
1.7.1 安装 JDK
macOS(使用 Homebrew):
```bash /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" brew install openjdk@17 echo 'export PATH="/usr/local/opt/openjdk@17/bin:$PATH"' >> ~/.zshrc source ~/.zshrc ```
Ubuntu / Debian:
```bash sudo apt update sudo apt install -y openjdk-17-jdk java -version ```
Windows: 到 [Adoptium](https://adoptium.net/) 下载安装包,按向导安装即可。
验证安装:
```bash java -version javac -version ```
如果能看到版本号(如 `openjdk version "17.0.9"`),说明安装成功。
1.7.2 安装 Maven
Maven 是 Java 生态最主流的构建工具。我们用它来管理依赖、编译、运行测试。
macOS:
```bash brew install maven ```
Ubuntu:
```bash sudo apt install -y maven ```
Windows: 到 [Mave