初识高并发:概念、挑战与设计哲学
1.1 一个真实的故事:双十一零点的那一刻
2014 年 11 月 11 日凌晨 0 点 0 分 0 秒,淘宝首页的"立即购买"按钮被同时点下去了多少次?根据阿里巴巴官方后来披露的数据,那一秒钟,支付宝完成支付笔数峰值约为 2.08 万笔/分钟,淘宝下单创建峰值约 8.5 万笔/秒。这不是科幻数字,而是真实发生在我们身边的一个普通夜晚。
如果你把时间拨回到 2010 年代初,12306 铁路购票系统刚刚上线时,每到春运售票高峰,网站就频繁出现"系统繁忙"页面,无数人在寒风中刷着手机,只为抢到一张回家的车票。那时候的工程师形容:服务器像被洪水冲刷的堤坝,随时可能崩溃。
再把视角拉近一点。某明星突然在微博宣布恋情,几十秒内热搜评论数破百万,后台数据库写入延迟从毫级飙升到秒级,部分用户刷到"加载失败"的提示。
这些场景的共同点是什么?是大量用户在同一时间窗口内对系统发起请求,超出了系统的常规处理能力。这种现象,就是本书要讨论的高并发。
那么问题来了:什么是高并发?它和"用户量大"是一回事吗?它和"性能好"又是什么关系?我们做工程的人,到底要解决哪些问题?
这一章会给你一把入门钥匙。读完它,你应该能说清楚高并发的本质、衡量它的核心指标、设计高并发系统的底层哲学,并能用这套思维去分析一个真实的高并发场景。
---
1.2 高并发的定义:不只是"用户多"
1.2.1 从"用户数"到"并发量"的跃迁
很多人会本能地把"高并发"等同于"用户多"。但严格来说,这两者并不等价。
一个电商网站可能有 1 亿注册用户,但凌晨三点可能只有几百人在线下单。另一个 App 只有 100 万用户,但某个"限时秒杀"活动开始的那 1 分钟里,可能有 80 万人同时点击"抢购"按钮。
我们关心的,从来不是注册表里有几亿行数据,而是单位时间内系统需要处理的请求数量。这才是"高并发"中"并发"二字的核心。
从工程角度,可以这样定义:
> 高并发(High Concurrency) 是指系统在同一时间段内需要处理大量同时到达的请求,并且要求系统在可接受的时间、正确性、可用性约束下完成处理的一种状态。
这里有三个关键限定词需要你记住:
1. 同一时间段:并发是时间维度的概念,不是用户数维度。1000 个用户分 10 秒来访问,不是高并发;1000 个用户在 1 秒内同时来,是高并发。 2. 大量:到底多大算"高"?没有绝对的数字。今天 100 QPS(每秒查询率)算高并发;10 年后随着硬件升级和业务增长,1 万 QPS 可能也只是"日常"。"高"是相对的,永远要结合业务场景谈。 3. 约束条件:高并发不是无限制地要求快。系统需要在合理时间内、合理错误率下完成请求。这是工程,不是数学。
1.2.2 并发 vs 并行:两个常被混淆的概念
在深入指标之前,先澄清两个术语:
- **并发(Concurrency)**:多个任务在**同一时间段**内交替执行,从宏观上看是"同时进行",但微观上可能是轮流占用 CPU。
- **并行(Parallelism)**:多个任务在**同一时刻**真正地同时执行,通常需要多核 CPU 或多台机器。
一个形象的比喻:单核 CPU 处理 10 个任务,是并发;10 核 CPU 同时处理 10 个任务,是并行。高并发系统往往同时包含并发和并行——单台机器用多线程处理是并发,多台机器协同处理是并行。
理解了这一点,你就能区分"高并发系统"和"并行计算系统"这两个不同的工程领域。我们这本书聚焦的是前者。
1.2.3 高并发的三层含义
把定义再展开一层,可以从三个角度理解高并发:
第一层:流量层。 来自用户、来自其他服务、来自爬虫或恶意攻击的请求,在短时间内集中涌入。这是"看得见"的并发。
第二层:资源层。 系统的 CPU、内存、磁盘、网络带宽、数据库连接、线程池等资源,在同一时间被大量请求争抢。这是"看不见"的并发。
第三层:数据层。 多个请求同时读写同一份数据,如何保证数据不出错、不重复、不错乱,是"最复杂"的并发。
这意味着,解决高并发问题不是简单地把服务器变多就够了——你需要同时在流量、资源、数据三个层面做文章。这正是本书要讲的主线。
---
1.3 四大核心衡量指标:QPS、TPS、RT、并发数
讨论高并发,必须有量化的尺子。业界有四个最常用的指标,你一定要掌握:QPS、TPS、RT、并发数。
1.3.1 QPS:Queries Per Second
QPS(每秒查询数) 是衡量系统读流量最常用的指标,表示系统每秒能处理的查询请求数量。
一个查询请求通常指一个 HTTP GET 请求或一次数据库的 SELECT 操作。例如:
- "查询商品详情"是 1 个 QPS。
- "用户打开首页"可能触发 5 个 QPS(轮播图、推荐位、公告、用户信息、底部导航)。
QPS 适合衡量读多写少的场景,比如商品浏览、新闻列表、搜索引擎查询。
注意 QPS 的两种含义:
- **狭义 QPS**:严格只算"查询"(读操作)。
- **广义 QPS**:把所有 HTTP 请求都算进去,包括 POST/PUT/DELETE。
在和团队沟通时,要先确认大家用的是哪种定义。
1.3.2 TPS:Transactions Per Second
TPS(每秒事务数) 衡量的是系统写流量或完整业务事务的处理能力。
一个事务通常指一个完整的业务操作,比如:
- "下单"包含:创建订单、扣库存、扣优惠、生成支付单——这整个流程算 1 个事务。
- "转账"包含:扣减账户 A、加账户 B、记录流水——也是 1 个事务。
TPS 比 QPS 更严格,因为一个事务可能包含多次读写。通常说:TPS < QPS。
在双十一那种场景下,阿里巴巴更关注下单 TPS和支付 TPS,因为这才是真正创造业务价值的指标。
1.3.3 RT:Response Time
RT(响应时间) 是指从客户端发起请求到收到响应的总耗时。
一个完整的 RT 通常包括:
- 客户端到服务器的网络传输时间
- 服务器处理时间(业务逻辑、计算)
- 服务器到下游服务(如数据库、缓存、第三方接口)的调用时间
- 下游服务返回后再回传的时间
RT 关注的是单次请求的质量。常见的几个统计维度:
- **平均 RT**:所有请求耗时的算术平均值。容易被极端值拉偏,参考价值有限。
- **P50 RT(中位数)**:50% 的请求都比这个时间快。反映"中等水平"用户的体验。
- **P95 RT**:95% 的请求都比这个时间快。反映"较慢但仍可接受"的体验。
- **P99 RT**:99% 的请求都比这个时间快。关注长尾问题,对核心用户尤其重要。
- **P999 RT**:99.9% 的请求都比这个时间快。高并发系统必须关注的指标,因为哪怕 0.1% 的慢请求,在大流量下也是巨大的绝对数量。
举个例子:1 万 QPS 的系统,P99 是 200ms,意味着每秒有 10 个用户等待超过 200ms。这 10 个用户的体验直接影响他们对产品的评价。
> 工程经验:在设计高并发系统时,不要只盯平均值。P99 和 P999 才是区分"普通系统"和"高并发系统"的关键分水岭。
1.3.4 并发数:Concurrency
并发数 是指同一时刻系统中正在处理的请求数量。
三者之间有一个经典公式(Little's Law,利特尔法则):
$$ \text{并发数} = \text{QPS} \times \text{平均 RT(秒)} $$
例如:系统平均 RT 是 0.1 秒,QPS 是 1000,那么平均并发数 = 1000 × 0.1 = 100。这意味着任意时刻,系统大约同时在处理 100 个请求。
这个公式的工程意义:
- 想要支撑更高的 QPS,要么**降低 RT**(让每个请求处理得更快),要么**提升并发能力**(让系统能同时处理更多请求)。
- 当并发数超过系统处理能力时,请求就会开始排队,RT 急剧上升,最终可能拖垮整个系统。
1.3.5 四大指标的关系
把这四个指标串起来看:
| 指标 | 衡量什么 | 单位 | 关注重点 | |------|---------|------|---------| | QPS | 读吞吐 | 次/秒 | 容量上限 | | TPS | 写吞吐 / 业务事务 | 次/秒 | 业务能力 | | RT | 单次请求耗时 | ms | 用户体验 | | 并发数 | 同时处理能力 | 个 | 资源压力 |
它们不是孤立的,而是相互制约的:
- 提高 QPS,往往需要更多线程/连接,**并发数上升**。
- 并发数上升后,CPU 切换开销增加,**RT 可能上升**。
- RT 上升后,相同时间内能处理的请求变少,**QPS 反而下降**。
这就是高并发系统设计的"不可能三角"——你必须在这四个指标之间做权衡,没有"全部最优"的方案。
1.3.6 一个计算练习
假设你的系统目标是:在 P99 RT ≤ 200ms 的前提下,支撑 1 万 QPS。
根据公式,平均并发数 = 10000 × 0.2 = 2000。
这意味着你的系统需要能在任意时刻处理 2000 个并发请求。如果你用的是 Tomcat,默认线程池只有 200——这就需要重新设计了。通过这个练习,你应该能感受到指标对架构的约束力。
---
1.4 三大挑战:性能、一致性、可用性
光有指标还不够。我们必须清楚,高并发系统到底面临哪些根本性挑战。这是设计哲学的基础。
1.4.1 挑战一:性能(Performance)
性能挑战最容易理解:流量大,资源有限,必须在有限资源下处理更多请求。
但性能问题在高并发下会"变形"——单机测试时跑得好好的接口,一上到大流量就崩。原因通常有这几类:
1. 资源争抢:数据库连接池有限(默认 20 个),1000 个并发来抢时,800 个在等待,连接池被撑满。 2. 锁竞争:多个线程同时改同一行数据,行锁等待导致 RT 飙升。 3. 缓存击穿:热点 key 过期瞬间,大量请求穿透到数据库,DB 直接被打挂。 4. GC 抖动:Java 应用在大量对象创建/销毁时,频繁触发 Full GC,RT 毛刺明显。 5. 线程切换:线程数过多,CPU 大量时间浪费在上下文切换上,反而处理能力下降。
这些问题的共性是:某个局部瓶颈放大了并发问题。性能优化的核心,就是找到并消除这些瓶颈。
1.4.2 挑战二:一致性(Consistency)
一致性挑战是数据层面的:当多个请求同时操作同一份数据,如何保证数据正确?
举个例子:商品库存只有 1 件,两个用户同时下单。
- 不做并发控制:两个订单都创建成功,超卖 1 件——公司亏钱还丢口碑。
- 做并发控制:必须让其中一个订单失败,另一个成功。
这就是典型的"并发读写冲突"。在高并发下,一致性问题会被放大到极致:
- **超卖**:秒杀、抢购、抢券场景的经典问题。
- **重复扣款**:支付场景下,用户点击两次,扣了两次款。
- **数据脏读**:一个事务读到另一个事务未提交的数据。
- **更新丢失**:两个请求同时更新,最后写入的覆盖了前面的有效更新。
解决一致性问题有不同强度的方法,从弱到强依次是:最终一致、读已提交、可重复读、线性一致。没有最好的,只有最适合业务场景的。
1.4.3 挑战三:可用性(Availability)
可用性挑战是:当系统压力大到极限,必须做出选择——是保住系统不挂(牺牲一些功能),还是保住功能完整(承担系统崩溃的风险)?
一个真实案例:双十一零点那一刻,淘宝的"个性化推荐"功能会被降级——首页直接展示默认模板,而不是根据用户画像动态生成内容。原因很简单:推荐系统的计算太重,零点那一刻最宝贵的资源要留给"下单"和"支付"。
这就是服务降级和熔断。它的本质思想是:
> 在极端压力下,放弃非核心功能,保住核心功能,让系统整体可用。
可用性还有一个著名的评价指标:几个 9。
| 级别 | 可用性 | 年停机时间 | 典型系统 | |------|--------|-----------|---------| | 2 个 9 | 99% | 87.6 小时 | 一般内部系统 | | 3 个 9 | 99.9% | 8.76 小时 | 普通商业系统 | | 4 个 9 | 99.99% | 52.6 分钟 | 金融、电信核心系统 | | 5 个 9 | 99.999% | 5.26 分钟 | 基础设施级(云厂商) |
注意,每提升一个 9,难度都是指数级增长。从 3 个 9 到 4 个 9,需要的投入可能是前者的 5-10 倍。
1.4.4 三者关系的本质:CAP 定理
这三个挑战在分布式环境下会互相牵制,这正是 CAP 定理要表达的核心:
- **C(Consistency)一致性**:所有节点在同一时间看到的数据完全相同。
- **A(Availability)可用性**:每个请求都能得到响应(但不一定是最新的数据)。
- **P(Partition tolerance)分区容错性**:节点之间的网络故障时,系统仍能继续服务。
CAP 定理指出:在网络分区不可避免的前提下,一致性和可用性不能同时满足,必须二选一。
工程上的选择通常是这样的:
- **选 C 放弃 A**:强一致系统(如金融转账、库存扣减)。宁愿返回错误,也不能给错数据。
- **选 A 放弃 C**:弱一致系统(如社交点赞、商品评论)。短时间不一致没关系,最终会同步。
- **通过其他机制妥协**:比如通过"重试 + 幂等"在表面上看似两者都满足。
理解 CAP,能让你在设计高并发系统时,不盲目追求"全都要",而是根据业务做出合理取舍。这是工程师走向成熟的重要一步。
---
1.5 三大设计哲学:分而治之、异步处理、空间换时间
现在进入本章最核心的部分:面对性能、一致性、可用性这三大挑战,设计高并发系统的底层思路是什么?
经过业界几十年的实践,沉淀出了三大哲学。它们不是孤立的技术,而是思考问题的方向。
1.5.1 哲学一:分而治之(Divide and Conquer)
核心思想:把大流量拆成小流量,把大系统拆成小系统,把复杂问题拆成简单问题。
这条哲学贯穿了高并发系统的整个演进历程:
- **业务层面**:把"全量用户"拆成"用户分群",把"全量商品"拆成"商品类目",让每个子系统只处理一部分。
- **系统层面**:单体应用拆成微服务,按业务域划分团队和数据库。
- **数据层面**:单库拆成主从、分库分表、读写分离,让每台数据库只承担一部分读写。
- **流量层面**:用 CDN 拆分静态和动态流量,用消息队列拆分同步和异步流量。
- **请求层面**:把一个长流程拆成多个短链路,让每一步可以独立扩展。
分而治之的本质是降低单个节点的压力。它的反面是"把所有鸡蛋放在一个篮子里"——一旦篮子翻了,全盘皆输。
一个具体的例子:12306 的票务查询。
最初的架构是所有人查所有车次,数据库压力极大。后来改成"按出发地-目的地分库"、"按车次类型分片"、"按日期分表"——一次查询被路由到特定的库和表上,单机压力骤降。
分而治之的代价:系统复杂度大幅上升,跨库事务、分布式 ID、数据一致性都成为新问题。这世上没有免费的扩展。
1.5.2 哲学二:异步处理(Asynchronization)
核心思想:把必须"立即完成"的事,分成"现在做"和"以后做",让快的事情不被慢的事情拖累。
这是高并发系统提升吞吐量的核武器。
举个生活中的例子:你去奶茶店点单,如果点单员必须等到你拿到奶茶才能服务下一个顾客,效率极低。但如果点单员只负责"下单收款",然后把单子挂到制作队列,顾客去旁边等,叫号取餐——这就是异步。
高并发系统中,异步处理的经典武器是消息队列(Kafka、RabbitMQ、RocketMQ)。
它的应用模式有两种:
1. 异步解耦:下单成功后,发送一条消息到队列,库存服务、支付服务、积分服务、物流服务各自消费这条消息,互不阻塞。
- **好处**:下单接口 RT 从 500ms 降到 50ms。
- **代价**:消息可能丢失、重复、顺序错乱——需要额外的机制保障。
2. 异步削峰:秒杀活动开始时,请求涌入消息队列,后端服务按自己的处理能力"匀速消费"。
- **好处**:哪怕前端有 10 万 QPS 写入队列,后端稳定在 5000 TPS 处理,系统不会被打挂。
- **代价**:用户下单后要"等待处理",实时性下降。
异步处理的核心收益是让系统可扩展:当流量来临时,可以通过增加消费者数量来提升处理能力,无须修改主链路代码。
1.5.3 哲学三:空间换时间(Space for Time)
核心思想:用额外的存储空间,换取计算时间的减少。
这条哲学在计算机科学中几乎是"银弹"级别的存在。最典型的应用就是缓存。
缓存的逻辑非常简单:第一次查询数据库得到结果,把结果存到内存里;下次同样的查询直接读内存,不再查数据库。
``` 第一次:查询 → 走 MySQL → 耗时 50ms → 返回结果 → 写入 Redis 第二次:查询 → 走 Redis → 耗时 1ms → 返回结果 ```
空间换时间的极致应用:
- **CPU 缓存**:L1/L2/L3 缓存,体积小但速度是内存的几十倍。
- **CDN 缓存**:把静态资源分发到全国乃至全球的边缘节点,用户访问最近节点。
- **本地缓存**:Caffeine、Guava Cache,应用进程内缓存,零网络开销。
- **分布式缓存**:Redis Cluster、Memcached,跨机器共享缓存数据。
- **数据库索引**:本质也是空间换时间,用额外的存储和写入开销,换取查询加速。
但空间换时间不是没有代价:
1. 数据一致性:缓存和源数据可能不一致,需要"缓存更新策略"保障(旁路缓存、读写穿透、异步刷新等)。 2. 空间成本:缓存本身需要内存,内存是稀缺资源。 3. 冷启动问题:缓存刚建立时是空的,需要时间"预热"。 4. 缓存雪崩 / 击穿 / 穿透:缓存异常场