高并发系统全景图:从一道面试题说起
第 1 章:高并发系统全景图:从一道面试题说起
开篇:那道让无人数次折戟的面试题
假设你坐在一家互联网公司的面试室里,面试官放下简历,问了你一个问题:
> “双十一零点时分,淘宝主页有 8 亿用户同时访问,你的服务器只有 10 台 4 核 8G 的机器,请你设计一个方案,确保用户在 3 秒内打开页面。如果系统崩了,你打算怎么排查和解决?”
这道题没有标准答案,但它的每一层都在考察你对高并发系统的理解深度。第一层考的是计算——你能不能估算出需要多少资源;第二层考的是架构——你知不知道如何水平扩展、如何做缓存、如何分流;第三层考的是工程哲学——你如何在性能、成本、稳定性之间做取舍。
有意思的是,这道题的背后,藏着几乎所有高并发系统的核心秘密。
当“8 亿用户”“双十一”“零点”这些词同时出现时,我们讨论的已经不再是某个具体的技术方案,而是一种系统运行状态的本质特征——高并发。它是现代互联网系统的常态,也是工程师职业生涯中迟早要面对的挑战。
但什么是高并发?为什么它如此重要?更重要的是,我们应该从什么角度去理解它,才能在面对具体问题时做出正确的判断?
这就是本章要解决的核心问题。
---
理解并发与并行的本质区别
很多初学者会把“并发”和“并行”混为一谈,认为它们只是同一件事的不同叫法。这是一个危险的误解,因为它会直接影响你对系统行为的判断。
并发:交替执行的艺术
并发(Concurrency)的核心是资源的时分复用。在单核 CPU 时代,操作系统通过快速切换任务,让用户感觉多个程序在同时运行。实际上,在任何一个时间点,CPU 只能执行一个任务。
用一个生活化的比喻:一位咖啡师同时照看三台咖啡机,在等待第一台机器研磨咖啡豆的时候,她去启动第二台机器的萃取程序,然后转身检查第三台机器的牛奶打发情况。虽然只有一位咖啡师,但三台机器都在“同时”工作——这就是并发。
在软件系统中,并发通常指的是多个任务在同一时间段内启动或处于执行状态,但它们可能交替使用共享资源。
并行:真正的同时执行
并行(Parallelism)则完全不同。它需要物理层面的支持——多个执行单元(CPU 核心、多台服务器)真正同时处理不同的任务。
继续用咖啡师的例子:如果店里雇了三位咖啡师,每人负责一台机器,那么三杯咖啡可以同时制作——这就是并行。
并行的关键特征是:任务之间没有资源竞争,它们各自拥有独立的执行单元。
为什么这个区别至关重要
理解了并发与并行的区别,我们就能明白高并发系统设计中一个最基本的问题:我们的瓶颈在哪里?
考虑一个场景:服务器有 8 个 CPU 核心,应用程序是一个单线程程序。即使系统有 8 亿请求涌来,由于程序只能使用一个核心,其他 7 个核心都在闲置。这就是典型的计算资源未被充分利用的问题。
但如果程序改成多线程,充分利用所有 8 个核心,这时候系统的瓶颈可能转移到网络带宽——8 个核心拼命处理请求,却发现网络卡住了。
又或者,网络带宽足够,但数据库连接池只有 100 个连接,8 个核心处理得飞快,100 个连接瞬间被占满,后续请求只能排队等待。
这个层层递进的分析过程,正是高并发系统排查的标准思路:先确认瓶颈在哪一层(CPU、内存、磁盘、网络、数据库),然后针对性地解决。
并发与高并发的关系
这里需要特别澄清一个容易混淆的概念:高并发并不等于“并发”。一个系统可以支持高并发,但它的代码实现未必使用了并发技术。
例如,一个传统的 Java Servlet 容器,在处理每个请求时可能创建新线程(并发),也可能使用线程池复用线程。单个请求的处理是同步的,但系统整体可以同时处理大量请求——这仍然是高并发系统。
关键不在于“是否使用了并发技术”,而在于系统同时处理请求的能力上限。
---
掌握 QPS、并发数、响应时间等核心指标
回到那道面试题。面试官问的是“3 秒内打开页面”,这涉及到一个最基本的问题:我们如何量化系统的性能?
QPS:每秒查询数
QPS(Queries Per Second) 是最常见的性能指标,表示系统每秒能够处理的请求数量。
它的计算公式很简单:
$$QPS = \frac{总请求数}{总耗时(秒)}$$
但这个公式背后隐藏着几个重要的问题需要考虑。
第一个问题:QPS 的测量时间窗口。 假设一个系统在 1 分钟内收到了 6000 个请求,那么平均 QPS 是 100。但如果你仔细看这 1 分钟的分布,可能会发现前 30 秒只有 500 个请求,后 30 秒有 5500 个请求。高峰期的 QPS 达到了 183,远超平均值。这意味着我们在评估系统时,不能只看平均 QPS,还要关注峰值 QPS 和 QPS 的分布曲线。
第二个问题:QPS 和业务的关系。 不同类型的请求消耗的资源差异巨大。一个简单的商品列表查询可能只需要 5ms,一个复杂的个性化推荐计算可能需要 500ms。如果系统 A 的 QPS 是 10000,但每个请求都是轻量级查询;系统 B 的 QPS 是 1000,但每个请求都是复杂计算。我们不能简单地说系统 A 的性能更好。
并发数:同时工作的请求数量
并发数(Concurrent Users) 指的是在同一时刻正在与系统交互的用户数量。注意,这里说的是“正在交互”,而不是“已经打开页面的总用户数”。
一个用户打开淘宝主页,可能在页面加载完成后就去泡茶了,他的浏览器和服务器之间没有正在进行的请求。只有当他点击商品、搜索关键词、加入购物车时,才会产生并发请求。
并发数和 QPS 之间的关系可以用一个简单的公式描述:
$$并发数 = QPS \times 平均响应时间$$
假设系统 QPS 为 1000,平均响应时间为 200ms,那么并发数 = 1000 × 0.2 = 200。这意味着任何时刻,系统大约在处理 200 个请求。
这个公式非常重要,它揭示了性能优化的两个方向:要么降低响应时间,要么降低 QPS。如果业务无法降 QPS(比如双十一的流量是固定的),那就只能拼命优化响应时间。
响应时间:用户体验的直接体现
响应时间(Response Time) 指的是从用户发起请求到收到完整响应的时间。这是用户最能感知到的指标,也是面试题中直接给出的约束条件(3 秒内打开页面)。
响应时间不是一个固定值,而是一个分布。假设我们测量了 10000 次请求的响应时间,可能会得到这样的分布:
| 百分位 | 响应时间 | |--------|----------| | P50 | 120ms | | P90 | 280ms | | P95 | 450ms | | P99 | 800ms | | P99.9 | 2000ms |
这里出现了几个新概念。P50(中位数) 表示 50% 的请求在 120ms 内完成;P99 表示 99% 的请求在 800ms 内完成。
为什么我们需要关注 P99、P999 这些高百分位?
因为在互联网系统中,我们追求的不是“大多数用户满意”,而是“所有用户都满意”。想象一下双十一零点有 1 亿用户,其中 1%(100 万用户)遇到了超过 3 秒的响应时间——这 100 万人很可能直接放弃购买,损失可能是天文数字。
所以高并发系统优化的一个重要目标,就是压低长尾响应时间,让 P99、P999 的表现尽可能接近平均值。
吞吐量的计算与转换
吞吐量(Throughput) 是另一个重要的性能指标,它关注的是单位时间内系统处理的数据量,通常用 TPS(Transactions Per Second,每秒事务数)或 ops(operations per second)来表示。
QPS 和 TPS 的区别在于:QPS 是请求级的统计,TPS 是事务级的统计。一个用户下单操作可能涉及多个后端请求(查询商品、扣减库存、创建订单、发送消息),所以 TPS 通常小于 QPS。
在实际工作中,我们还会遇到几个相关的指标:
- **PV(Page View)**:页面浏览量,一个用户刷新页面 5 次,PV 计为 5
- **UV(Unique Visitor)**:独立访客数,同一个用户访问 100 次,UV 计为 1
- **DAU(Daily Active Users)**:日活跃用户数
- **GMV(Gross Merchandise Volume)**:成交总额
这些指标之间的换算关系是理解业务流量的基础。假设某电商网站的 DAU 是 1000 万,平均每位用户每天访问 10 次,每次产生 5 个后端请求,那么日 PV = 1 亿,日 QPS(平均值)≈ 1157。但峰值 QPS 可能是平均值的 5-10 倍,达到 5000-10000。
---
认识高并发系统的不可能三角
如果高并发系统的问题只是“堆机器”就能解决,那这个领域就不会存在如此多的挑战和权衡。事实上,高并发系统面临的核心矛盾,来自于一个被称为CAP 理论的基本原理。
CAP 的起源与内涵
2000 年,加州大学伯克利分校的 Eric Brewer 教授在 PODC(分布式计算原理研讨会)上提出了一个猜想:分布式系统无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三个特性。
两年后,麻省理工学院的 Seth Gilbert 和 Nancy Lynch 从理论上证明了这个猜想,CAP 理论正式成为分布式系统的基石。
让我们逐一理解这三个概念:
一致性(Consistency):所有节点在同一时刻看到的数据是相同的。换句话说,无论你访问哪个副本,读到的都是最新写入的数据。
可用性(Availability):每个请求都能在有限时间内得到响应。注意,这里并没有要求响应是“正确”的,只要求“有响应”。
分区容错性(Partition tolerance):当网络分区发生时(节点之间无法通信),系统仍然能够继续运行。
为什么三个特性不能同时满足
想象一个简单的场景:数据库被部署在两个数据中心(A 和 B)之间,它们通过网络同步数据。
某时刻,网络连接断开,A 和 B 之间无法通信——这就是网络分区。
此时,系统面临两个选择:
选择一:保证可用性。 两个数据中心都继续服务,用户无论访问 A 还是 B 都能得到响应。但由于无法同步,A 和 B 的数据可能不一致——这违反了一致性。
选择二:保证一致性。 当网络断开时,为了确保 A 和 B 数据一致,系统必须停止服务,直到网络恢复——这违反了可用性。
选择三:同时保证一致性和可用性。 这要求 A 和 B 之间的网络永远不中断——但这意味着系统永远不会发生分区,在分布式系统中这几乎是不可能的。
这就是 CAP 三角的含义:在存在网络分区的情况下,必须在一致性和可用性之间做出选择。
一致性、可用性、性能:另一种不可能三角
对于高并发系统设计来说,CAP 理论更多是一个理论框架。在日常工程实践中,我们更常面对的是一致性、可用性、性能之间的权衡。
考虑一个具体的例子:用户下单购买库存只有 1 件的商品。
方案 A:强一致性。 扣减库存前,先锁定库存,确认扣减成功,再返回下单成功。如果数据库出现故障或响应慢,用户会看到下单失败。这保证了数据一致性,但可用性和响应时间可能不理想。
方案 B:高可用。 允许下单操作异步执行,先返回成功,事后校验库存。如果库存实际上已经售罄,再通知用户取消订单。这保证了可用性,但牺牲了一致性(用户可能收到“惊喜”)。
方案 C:高性能。 在缓存层扣减库存,极快响应。但如果缓存和数据库不同步,可能出现超卖。这追求了性能,但一致性难以保证。
```mermaid flowchart TB subgraph ImpossibleTriangle["高并发系统不可能三角"] direction TB C["一致性 Consistency<br/>数据准确可靠"] A["可用性 Availability<br/>服务持续可用"] P["高性能 Performance<br/>响应时间短"]
C ---|"二选一"| A C ---|"二选一"| P A ---|"需要权衡"| P end
subgraph RealWorld["实际系统选择"] direction LR R1["CP 系统<br/>如:分布式数据库"] R2["AP 系统<br/>如:DNS、CDN"] R3["性能优先<br/>如:日志收集"] end
ImpossibleTriangle --> RealWorld ```
工程实践中的权衡智慧
理解不可能三角不是为了在技术上做单选题,而是为了明确系统设计的目标和边界。
对于不同类型的系统,优先级的排序是不同的:
金融支付系统的首要目标是一致性。转账金额不能出错,库存不能超卖。在 CAP 三角中,这类系统倾向于 CP 模式。
社交Feed系统的首要目标是可用性。用户可以容忍看到稍微过时的内容,但不能容忍页面打不开。这类系统倾向于 AP 模式,通过最终一致性来弥补。
日志收集系统的首要目标是高性能。偶尔丢失一两条日志可以接受,但系统不能成为业务瓶颈。
关键在于:你必须清楚地知道自己的系统优先保障什么,然后在其他维度上做出合理的妥协。
---
高并发系统设计全景图
理解了指标和不可能三角,我们终于可以勾勒出高并发系统设计的完整知识地图。
分层架构:构建系统的基础框架
现代高并发系统几乎无一例外地采用分层架构。每一层负责不同的职责,通过标准化接口与上下层通信。
客户端层:用户直接接触的部分,包括 Web 浏览器、移动 App、小程序等。这一层的优化重点是减少请求次数(合并资源、懒加载)、降低渲染时间(CDN 加速、前端缓存)。
负载均衡层:将流量均匀分发到后端服务。常见的负载均衡策略包括轮询、随机、最少连接、一致性哈希等。这一层还需要处理健康检查、故障转移、灰度发布等能力。
网关层:所有请求的入口,统一处理认证、鉴权、限流、熔断、日志等功能。网关是流量的“守门人”,也是系统安全的第一道防线。
业务服务层:实现核心业务逻辑。高并发系统的业务层通常是无状态的,可以水平扩展。这一层的优化重点是减少不必要的计算、合理使用缓存、异步化非核心流程。
数据访问层:包括关系型数据库、NoSQL 数据库、缓存系统、消息队列等。这一层是系统最复杂的地方,也是性能瓶颈的高发区。
存储层:持久化数据的地方,包括分布式文件系统、对象存储、时序数据库等。对于超大规模系统,存储层本身也需要高并发设计。
核心技术领域一览
高并发系统设计涉及的核心技术,可以划分为以下几个领域:
流量治理:包括负载均衡、限流、熔断、降级、灰度发布等。这些技术的共同目标是保护系统不被突发流量冲垮,确保系统在可控范围内运行。
缓存策略:缓存是提升系统性能最有效的手段之一。但缓存也带来了一致性挑战——如何确保缓存和数据库的数据同步?缓存的淘汰策略如何设计?分布式缓存如何保证一致性?
异步通信:通过消息队列解耦服务,实现流量削峰、提高系统吞吐。但异步也意味着系统的复杂性增加,需要考虑消息丢失、重复消费、顺序性等问题。
数据库优化:包括读写分离、分库分表、索引优化、SQL 优化、连接池管理等。对于高并发系统,数据库往往是最大的瓶颈所在。
容器化与编排:Docker 和 Kubernetes 已经成为现代高并发系统的标配。通过容器化,应用可以快速部署和扩缩容;通过编排系统,可以实现自动化运维。
可观测性:包括日志、监控、链路追踪。高并发系统的问题排查往往非常困难,没有完善的监控和追踪体系,几乎不可能定位问题。
设计原则:从原则到实践
在具体的系统设计中,有几个被反复验证的设计原则:
水平扩展优于垂直扩展:当单机性能达到瓶颈时,应该通过增加机器数量来提升系统能力,而不是不断升级单机配置(CPU、内存、磁盘)。水平扩展意味着无状态设计——每个请求都可以被任意一个服务实例处理。
避免单点故障:高并发系统必须具备容错能力。任何关键组件(数据库、缓存、负载均衡器)都应该有冗余,当某个节点故障时,系统能够自动切换到备用节点。
异步优于同步:同步调用意味着调用方必须等待被调用方返回结果。在高并发场景下,这会快速耗尽线程资源。异步调用允许释放线程去处理其他请求,提高系统整体的吞吐能力。
冗余设计:为峰值流量预留 buffer。通常的做法是按照预估峰值的 2-3 倍来准备资源。宁可资源闲置,不能资源不足。
降级优于崩溃:当系统负载超过设计阈值时,应该主动降级非核心功能,保证核心功能的可用性,而不是让整个系统崩溃。例如,秒杀时可以临时关闭商品评论功能,把资源留给下单流程。
---
案例拆解:电商秒杀系统
纸上得来终觉浅,让我们用一个完整的案例来串联本章的所有知识点。
场景描述
某电商平台计划在双十一举办秒杀活动。活动商品为 1000 件iphone,原价 8999 元,秒杀价 5999 元。预计参与用户 100 万人,活动持续 1 小时。
业务目标:确保活动期间系统稳定,所有抢购请求得到及时响应,不出现超卖,不出现系统崩溃。
问题分析
第一步:估算流量。
- 100 万用户参与
- 活动持续 1 小时 = 3600 秒
- 如果用户均匀分布,平均 QPS = 100万 / 3600 ≈ 278
- 但实际上,用户行为是高度集中的。根据历史经验,秒杀活动 80% 的流量会在前 10 分钟涌来
- 前 10 分钟的 QPS = 100万 × 80% / 600 ≈ 1333
- 如果考虑用户会反复刷新、尝试下单,实际 QPS 可能是估算值的 3-5 倍
所以,系统的设计目标是:支撑 5000-10000 QPS 的峰值流量。
第二步:识别瓶颈。
在这个量级下,可能成为