高并发系统设计的核心目标是要保证系统在处理大量并发请求时,能够保持高性能、高可用性以及良好的可扩展性。这三个方面相互关联,共同支撑着系统能够应对不断增加的流量和需求。具体来说:
- 性能:系统需要在高并发场景下保持低延迟的响应时间,确保用户体验。比如,面对每秒一万次请求的情况下,系统需要保证响应时间在毫秒级,而非秒级,否则用户体验会大打折扣。
- 可用性:高并发系统需要能够保持长时间的稳定运行。即使面对突发的大量流量,系统也应当能够保证服务不中断。例如,一些大型电商平台在“双十一”期间,虽然流量激增,但仍需确保全天候无故障运行。
- 可扩展性:高并发系统必须能够应对峰值流量的挑战,这需要系统具备良好的扩展能力。通常,面对流量的波动,特别是突发流量时,系统应该能够在短时间内扩容,并且不影响正常运行。
在进入如何提升系统性能之前,我们可以从一些常见的技术手段着手,例如:
- 负载均衡:通过分发请求到多个服务器,避免单点瓶颈,提高系统的并发处理能力。
- 缓存:减少对数据库和其他后端服务的依赖,通过缓存热点数据,减轻系统压力。
- 异步处理:对于耗时操作,通过异步任务来处理,不阻塞主流程。
- 数据库优化:通过读写分离、分库分表等策略优化数据库访问效率。
这些方法是提高性能的基础,接下来可以深入探讨如何根据具体情况进一步优化。
性能优化原则
在进行性能优化时,确实需要遵循一些基本原则,尤其是面对高并发系统设计的复杂性。下面总结了几个优化的关键点,帮助我们有效地提升系统性能:
1. 问题导向
盲目优化是效率低下的。优化应当基于实际的性能瓶颈,而不是做无目的的优化。例如,如果系统中某个组件没有遇到性能问题,就不需要对其进行优化。相反,我们应当关注最影响性能的部分,优先解决这些问题。这通常可以通过 性能监控 和 分析工具 来帮助识别瓶颈。
2. “八二原则”
性能优化要遵循“八二原则”——20%的优化措施解决80%的问题。在实际工作中,我们经常会发现,只有少数几个性能瓶颈点在占据了大部分系统资源。因此,找到这些瓶颈并对其进行优化,可以带来最明显的性能提升。
3. 数据支撑
性能优化不仅要有明确的目标,还要通过数据来验证效果。你可以利用 性能测试工具(如 JMeter、LoadRunner、或者自己的自定义监控工具)来进行基准测试,评估响应时间、吞吐量、并发请求数等关键指标。优化之后,及时记录和比较优化前后的数据变化,从而确保优化是有效的。
4. 持续优化
性能优化是一个不断迭代的过程。随着系统的业务逻辑变得越来越复杂,新的瓶颈可能会不断出现。因此,持续的性能测试和优化是非常重要的。在设计高并发系统时,我们不可能一次性解决所有问题,而是要持续监控、分析并逐步优化。
性能的度量指标
性能优化中,性能度量标准至关重要,因为它帮助我们明确系统的实际性能状态,并能用来衡量优化措施的效果。只有通过合理的度量指标,我们才能清晰地知道性能瓶颈在哪里,哪些优化是有效的。单次响应时间是基础指标,但它并不足够全面,因此需要通过一些统计方法来进一步分析数据。
常见的性能度量特征值
- 平均值
平均响应时间是最常见的性能度量指标,它通过将所有请求的响应时间加总并除以总请求数来计算。虽然平均值在一定程度上能反映系统性能,但它对极端值(如偶尔的慢请求)非常不敏感。正如你提到的,在大多数请求响应时间为1ms的情况下,单个100ms的慢请求也不会对平均值产生很大影响,因此可能导致性能问题被低估。
优点:简单易懂,易于计算。
缺点:对偶发的慢请求不敏感,可能掩盖性能问题。 - 最大值
最大响应时间记录了所有请求中最慢的一次响应。它可以告诉我们最糟糕的性能情况,但它对偶发的异常情况(如一次长时间的网络延迟)非常敏感,可能会严重扭曲性能的实际水平。
优点:可以展示最差的性能情况。
缺点:非常敏感,可能不代表大部分请求的实际性能。 - 分位值
分位值是性能度量中更为精确的标准,常见的有 90分位、95分位 和 99分位。以90分位为例,它表示在所有请求中,90%的请求响应时间小于等于某个值,剩余10%的请求响应时间则更长。分位值通过消除极端的慢请求影响,能够更准确地反映大多数请求的性能情况。
图片
在我来看,分位值是最适合作为时间段内,响应时间统计值来使用的,在实际工作中也应用最多。除此之外,平均值也可以作为一个参考值来使用。我在上面提到,脱离了并发来谈性能是没有意义的,我们通常使用吞吐量或者响应时间来度量并发和流量,使用吞吐量的情况会更多一些。但是你要知道,这两个指标是呈倒数关系的。这很好理解,响应时间 1s 时,吞吐量是每秒 1 次,响应时间缩短到 10ms,那么吞吐量就上升到每秒 100 次。所以,一般我们度量性能时都会同时兼顾吞吐量和响应时间,比如我们设立性能优化的目标时通常会这样表述:在每秒 1 万次的请求量下,响应时间 99 分位值在 10ms 以下。
高并发下的性能优化
假如说,你现在有一个系统,这个系统中处理核心只有一个,执行的任务的响应时间都在 10ms,它的吞吐量是在每秒 100 次。那么我们如何来优化性能从而提高系统的并发能力呢?主要有两种思路:一种是提高系统的处理核心数,另一种是减少单次任务的响应时间。
1. 提高系统的处理核心数
提高系统的处理核心数就是增加系统的并行处理能力,这个思路是优化性能最简单的途径。
通过增加并行进程提高吞吐量
以我们之前的例子为基础,假设你将系统的处理核心数增加为两个,并且增加一个进程,使得这两个进程分别在不同的核心上运行。这样,从理论上讲,你的系统吞吐量可以增加一倍。
在这种情况下,吞吐量和响应时间不再呈倒数关系,而是遵循以下公式:
吞吐量 = 并发进程数 / 响应时间
这意味着,增加并发进程数有助于提高系统的吞吐量,然而,响应时间并不一定按比例缩短。
阿姆达尔定律 (Amdahl’s Law)
计算机领域的 阿姆达尔定律(Amdahl’s Law)由吉恩·阿姆达尔在 1967 年提出,用于描述并发进程数与响应时间之间的关系。它主要说明了并行计算的加速比,也就是并行化之后效率提升的情况。
阿姆达尔定律的公式为:
其中:
- W_s 表示任务中的串行计算量
- W_p 表示任务中的并行计算量
- s 表示并行进程数
从这个公式,我们可以推导出另一个公式:
其中:
- s 表示并行进程数
- p 表示任务中并行部分的占比
结论
- 当 p = 1 时,也就是任务完全可以并行化时,加速比与并行进程数 s 成正比,系统的性能可以随之线性提升。
- 当 p = 0 时,任务完全是串行的,无法并行化,加速比为 1,系统性能无法提升。
- 当 s 趋近于无穷大时,加速比趋近于 1 / (1 - p),这意味着即使增加无限多个处理核心,提升的效果也会受到 p(并行部分的占比)的限制。
特别注意
- 当 p = 1 时,任务完全并行化时,理论上加速比是无限的,这也是并行化带来的最大效益。
- 当 p 远小于 1,即任务中大部分是串行计算时,即便增加大量并行进程,性能提升也会受到极大限制。
简化结论
尽管阿姆达尔定律的公式推导有些复杂,实际工作中我们通常只需要记住以下结论:
- 完全并行的任务能够无限加速(p = 1)。
- 任务的串行部分(p < 1)会限制系统的并行化效果。
我们似乎找到了解决问题的银弹,是不是无限制地增加处理核心数就能无限制地提升性能,从而提升系统处理高并发的能力呢?很遗憾,随着并发进程数的增加,并行的任务对于系统资源的争抢也会愈发严重。在某一个临界点上继续增加并发进程数,反而会造成系统性能的下降,这就是性能测试中的拐点模型。
图片
从图中可以看出,当并发用户数处于轻压力区时,响应时间保持平稳,吞吐量与并发用户数呈线性关系。而当并发用户数进入重压力区时,系统资源接近极限,吞吐量开始下降,响应时间略有上升。进一步增加压力时,系统进入拐点区,超负荷状态下吞吐量下降,响应时间急剧上升。因此,评估系统性能时,我们通常通过压力测试来找到系统的“拐点”,以了解系统的承载能力,并定位瓶颈,进行持续优化。
接下来,我们来看看优化性能的另一种方式:减少单次任务的响应时间。
2. 减少单次任务响应时间
想要减少任务的响应时间,首先需要判断系统是 CPU 密集型还是 IO 密集型的,因为不同类型的系统优化方法不同。
CPU 密集型系统
CPU 密集型系统主要处理大量的 CPU 运算。在这种情况下,选用更高效的算法或减少运算次数是提升性能的重要手段。例如,如果系统的主要任务是计算 Hash 值,选择更高性能的 Hash 算法能够显著提升系统性能。
发现这类问题的主要方法是使用 Profile 工具,如 Linux 的 perf 或 eBPF,来找出消耗 CPU 时间最多的方法或模块。
IO 密集型系统
IO 密集型系统的大部分操作都在等待 IO 完成,这里的 IO 指的是磁盘 IO 和网络 IO。大多数系统,如数据库系统、缓存系统、Web 系统,都是 IO 密集型系统。其性能瓶颈可能出现在系统内部,也可能在依赖的其他系统。
发现 IO 性能瓶颈的手段主要有两类:
- 使用工具,Linux 提供了丰富的工具集,如网络协议栈、网卡、磁盘、文件系统、内存等工具,能帮助排查问题。
- 通过监控来发现性能问题,监控可以对任务的每个步骤做分时统计,从而找出哪些步骤消耗了更多时间。
优化方案
优化方案因问题不同而异。例如,如果是数据库访问慢,可能需要检查是否有锁表、全表扫描、索引是否合适、JOIN 操作是否优化、是否需要加缓存等;如果是网络问题,则需要检查网络参数、抓包分析是否有大量超时重传、网卡是否有丢包等。