服务端性能优化之最大QPS推算及验证

2020-04-18      作者:小鱼在线

4.1 最大QPS推算及验证

影响QPS(吞吐量)的因素有哪些,一直以来都众说纷纭。大家一般是这么说的:

● QPS受编程语言的影响。

● QPS主要受编程模型的影响,比如是不是coroutine、是不是NIO、有没有阻塞。

● QPS主要由业务逻辑决定,业务逻辑越复杂,QPS越低。

● QPS受数据结构和算法的影响。

● QPS受线程数的影响。

● QPS受系统瓶颈的影响。

● QPS和RT关系非常紧密。

● ……

以上描述好像都有点对,又有点不对,好像总是“东一点、西一块”,不太完整。接下来要阐述的就是如何通过一些现有的理论和方法来考察系统中可以提高的性能点,将这些“东一点、西一块”的东西总结归纳起来,形成一个体系,用这个体系指导我们更好地对服务端性能进行优化。

在对某电商的活动页面进行优化时,一开始很多人认为Gzip不是影响CPU的最大因子,直到拿出一次又一次的实验数据,团队成员才开始慢慢地接受。这里笔者将从另一个角度分析,为什么在某电商活动页面中Gzip是影响CPU的最大因子。从本节也可以看出,性能优化的相关知识和数据都是体系化的。

4.1.1 RT

公式一:RT=Thread CPU Time+Thread Wait Time

RT(Response Time,响应时间)可以简单地理解为系统从输入到输出的时间间隔,系统是指一个网站或者一个其他类型的软件应用,或者指某个设备,比如手机,手机界面也有响应时间。所以RT是一个比较广泛的概念,不过在接下来的场景中,RT都特指互联网应用。服务器端 RT 的含义是指从服务器接收请求到该请求响应的全部数据被发往客户端的时间间隔。客户端 RT 的含义是指从客户端(比如浏览器)发起请求到客户端(比如浏览器)接收该请求响应的全部数据的时间间隔。需要注意的是,服务器端RT+网络开销≈客户端RT。也就是说,一个差的网络环境会导致两个RT差距悬殊(比如,从俄罗斯到美国的RT远大于国内网络环境中的RT)。

客户端的RT直接影响用户体验,要想降低客户端RT,提升用户体验,必须考虑两点,一个是服务器端的RT,另一个是网络。对于网络来讲,常见的优化方式有CDN、AND和专线,分别适用于不同的场景。

对于服务器端的RT来说,主要看服务器端的做法。从公式一中可以看到,要想降低RT,就要降低Thread CPU Time或者Thread Wait Time。所以接下来着重讨论服务器RT、Thread CPU Time、Thread Wait Time等相关知识。

在后面的内容中,将用CPU Time替代Thread CPU Time,用Wait Time替代Thread Wait Time。

4.1.2 单线程QPS

在上一节中,简单地定义了RT由两部分组成,一个是CPU Time,另一个是Wait Time。如果系统里只有一个线程或者一个进程(该进程中只有一个线程),那么最大的QPS是多少呢?假设RT是199ms(CPU Time为19ms,Wait Time是180ms),那么1000ms以内系统可以接收的最大请求数就是1000ms/(19ms+180ms) ≈5.025。

网站建设-毕亮:

呢?假设RT是199ms(CPU Time为19ms,Wait Time是180ms),那么1000ms以内系统可以接收的最大请求数就是1000ms/(19ms+180ms) ≈5.025。

所以单线程的QPS变成了:

单线程QPS=1000ms /RT

4.1.3 最佳线程数

还是从举例开始,延续上面的例子(CPU Time为19ms,Wait Time是180ms),假设CPU的核数是1。

假设只有一个线程,这个线程在执行某个请求时,CPU真正花在该线程上的时间就是CPU Time,可以看作19ms,那么在整个RT生命周期中,还有180ms的Wait Time,CPU在做什么?在理想情况下,抛开系统层面的问题,可以认为 CPU 在这180ms 里没做什么,至少对业务来说没做什么。

● 一核的情况

由于每个请求的接收,CPU只需要工作19ms,所以在180ms的时间内,可以认为系统还可以额外接收180ms/19ms ≈9个请求。由于在同步模型中,一个请求需要一个线程来处理,因此,我们需要额外的9个线程来处理这些请求。这样,总的线程数就是:

(180ms+19ms)/19ms ≈10个

多线程之后,CPU Time从19ms变成了20ms,这1ms的差值代表多线程之后线程上下文切换、GC等带来的额外开销(如果是JVM),这里的1ms代表一个概数,你也可以把它看作n。

● 两核的情况

一核的情况下可以有10个线程,那么两核呢?在理想情况下,可以认为最佳线程数为:

2 ×(180ms+20ms)/20ms=20个

● CPU利用率

上面举的都是CPU在满载情况下的例子。有时由于某个瓶颈,导致CPU得不到有效利用,比如两核的CPU,因为某个资源,只能各自使用一半的效能,这样总的CPU利用率变成了50%,在这样的情况下,最佳线程数应该是:

50% ×2 ×(180ms+20ms)/20ms=10个

根据上面的分析,最佳线程数公式如下:

最佳线程数=(RT/CPU Time)×CPU核数×CPU利用率

当然,最佳线程数公式不是任意推测的,在一些权威著作上都有论述,所以接下来,假设最佳线程数的公式是正确的。

4.1.4 最大QPS

1.最大QPS公式推导

假设知道最佳线程数,也知道每个线程的QPS,那么线程数乘以每个线程的QPS即这台机器在最佳线程数下的QPS。所以可以得到如图4-1所示的推算过程。

1.jpg

图4-1

把分子和分母去约数,如图4-2所示。

2.jpg

图4-2

于是公式简化成如图4-3所示。

3.jpg

图4-3

从公式可以看出,决定QPS的是CPU Time、CPU核数和CPU利用率。


网站建设-毕亮:

CPU核数是由硬件决定的,很难操纵,但是CPU Time和CPU利用率与代码息息相关。

虽然宏观上是正确的,但是推算的过程中还是有一点小小的不完美,因为多线程下的CPU Time(比如高并发下的GC次数增加消耗更多的CPU Time、线程上下文切换等)和单线程下的CPU Time是不一样的,所以会导致推算出来的QPS和实际的QPS有误差。

尤其是在同步模型下的相同业务逻辑中,单线程时的 CPU Time 肯定会比大量多线程的CPU Time小,但是对于异步模型来说,切换的开销会变小,对此后面将会详细描述。

既然决定QPS的是CPU Time和CPU核数,那么这两个因子又是由什么决定的呢?

2.CPU Time

CPU Time不只是业务逻辑所消耗的CPU时间,而是一次请求中所有环节上消耗的CPU时间之和。比如在Web应用中,一个请求过来的HTTP的解析所消耗的CPU时间,是CPU Time的一部分,另外,这个请求中请求RPC的encode和decode所消耗的CPU时间也是CPU Time的一部分。

那么CPU Time是由哪些因素决定的呢?两个关键字:数据结构+算法。举几个例子:

● 均摊问题。

● Hash问题。

● 排序和查找问题。

● 状态机问题。

● 序列化问题。

3.CPU利用率

CPU 利用率不高的情况是时常发生的,以下因素都会影响 CPU 利用率,从而影响系统可以支持的最大QPS。

(1)I/O能力。

● 磁盘I/O

● 网络I/O

○ 带宽,比如某大促压力测试时,由于某个应用放在Tair中的数据量大,导致Tair的机器网卡跑满。

○ 网络链路,还是这次大促,借用了其他核心交换机下的机器,导致客户端RT明显增加。

(2)数据库连接池(并发能力=PoolWaitTime[1]/RT(client)×PoolSize[2])。

(3)内存不足,GC 大量占用 CPU,导致给业务逻辑使用的 CPU 利用率下降,而且 GC时还满足Amdahl定律锁定义的场景。

(4)共享资源的竞争,比如各种锁策略(读写锁、锁分离等),各种阻塞队列,等等。

(5)所依赖的其他后端服务QPS低造成瓶颈。

(6)线程数或者进程数,乃至编程模型(同步模型、异步模型,某些场景适合同步模型,某些场景适合异步模型)。

在压力测试的过程中,出现最多的是网络I/O层面的问题,GC大量占用CPU Time之类的问题也经常出现。

4.CPU核数,Amdahl定律,Gustafson定律

1)Amdahl定律(安达尔定律)

Amdahl定律是用来描述可伸缩性的,什么是可伸缩性?按照权威资料解释:

可伸缩性是指,当增加计算资源的时候,如CPU、内存、带宽等,QPS能够相应地进行改进。

那么Amdahl定律是如何来描述可伸缩性的呢?

Amdahl在自己的论文中指出,可伸缩性是指在一个系统中,基于可并行化和串行化的组件各自所占的比例,当程序获得额外的计算资源(如CPU或者内存等)时,系统理论上能够获得的加速值(QPS或者其他指标可以翻几倍)。用一个公式来表示,如果F表示必须串行化执行的比例,那么在一个N核处理器的机器中,加速:

这个公式代表的意义是比较广泛的,在项目管理中也有一句类似的话:

一个女人生一个孩子,需要9个月,但是永远不可能让9个女人在一个月内就生一个孩子。

我们拿公式套用一下,这里F=100%,9个女人表示N=9,于是就有 1/(100%+(1-100%)/9)=1,所以9个女人的加速比为1,等于没加速。

这里要提醒大家,这个公式描述的是,在增加资源的情况下系统的加速比,而不是在资源不变的情况下优化数据结构和算法之后带来的提升。优化数据结构和算法带来的提升要看前文中的最大QPS公式。不过这两个公式也不是完全没有联系的,在增加资源的情况下,它们的联系还是比较紧密的。

2)Gustafson定律(古斯塔夫森定律)

这个定律名字很长,它是Amdahl定律的补充:

S(P)=P-α⋅(P-1)

P是处理器个数,α是串行时间占总执行时间的比例。

生孩子的案例再次套上这个定律,P为女人个数,等于9,串行比例是100%。Speedup=9-100% ×(9-1)=1,也就是9个女人是无法在一个月内把孩子生出来的……

两个定律有关系吗?有,它们是相辅相成的关系。前者从串行和并行执行时间的角度来推导,后者从串行和并行的计算量角度来推导,不管哪个角度,最终的结果是一样的。

3)CPU核数和Amdahl定律的关系

通过最大QPS公式,笔者发现,在CPU Time和CPU利用率不变的情况下,核数越多,QPS就越大。比如核数从1到4,在CPU Time和CPU利用率不变的情况下,加速比应该是4,所以QPS应该增加4倍。

这是资源增加(CPU核数增加)的情况下的加速比,也可以通过Amdahl定律来衡量,考虑串行和并行的比例在增加资源的情况下是否会改变。也就是要考虑在N增加的情况下,F受哪些因素的影响:

只要F大于0,最大QPS就不会翻4倍。

一个公式说要增加4倍,一个定理说没有4倍,互相矛盾?

其实事情是这样的,通过最大QPS公式,我们得知,如果CPU Time和CPU利用率不变,核数从1增加到4,QPS会相应地增加4倍。但是在实际情况下,当核数增加时,CPU Time和CPU利用率大部分时候是变化的,所以前面的假设不成立,即一般场景下最大QPS不能增加4倍。

而Amdahl定律中的N变化时,F也可能会变化,即一般场景下,最大QPS并不能增加4倍,所以不矛盾,相反它们是相辅相成的。这里一定要注意,这里说的是一般场景,如果你的场景完全没有串行(程序没有串行,系统没有串行,上下文切换没有串行,什么串行都没有),那么理论上还是可以增加4倍的。

为什么增加计算资源时,最大QPS公式中的CPU Time和CPU利用率会变化,F也会变化呢?我们可以从宏观上分析一下,增加计算资源时,达到满载:

● QPS会更高,单位时间内产生的对象会更多。在同等条件下,minor GC被触发的次数增加,还有些场景发生过对象多到响应没返回它们就进了“老年代”,从而full GC被触发。宏观上,这是属于串行的部分,对于Amdahl公式来说F会受到影响,对于最大QPS公式来说,CPU Time和CPU利用率也受到影响。

● 在同步模型下大量的线程在完成一次请求中,上下文被切换的次数大大增加。

● 尤其是在有串行模块的时候,串行的执行和等待时间增加,F会变化,某些场景下CPU利用率也达不到理想效果,这取决于你的代码。这也是要做锁分离、为什么要缩小同步块的原因。当然还有锁自身的优化,比如偏向、自旋、读写分离等技术,都是为了不断地减少Amdahl定律中的F,也是为了减少CPU Time(锁本身的优化),提高CPU利用率(使用锁的方法的优化)。

○ 锁本身的优化最为津津乐道的是自旋、偏向、自适应,这些知识点请看网上的synchronized分析,还有reetrantLock的代码及AQS论文。

○ 使用锁的优化方法最常见的是缩小锁区间、锁分离、无锁化、volatile。

所以在增加计算资源时,更高的并发产生,会引起最大QPS公式中两个参数的变化,也会引起Amdahl定律中F值的变化,同时公式和定律变化的趋势是相同的。Amdahl定律是得到广泛认可的,也是得到数据验证的。最大QPS公式好像没有人验证过,这里引用一个比较有名的测试结果,如图4-4所示。

4.jpg

图4-4

从图4-4中可以看到:

● 当计算资源(处理器数量)增加时,在串行部分比例不变的情况下,CPU利用率下降。

● 当计算资源(处理器数量)增加时,串行占的比例越大,CPU利用率下降得越多。

4.1.5 实验数据验证公式

截至目前,笔者都在讲公式,但是这个公式是不是正确,应该如何测量?能否做到精确测量,还是做一个概要测试即可?

注意:在这个讲解中,CPU Time包含了操作系统对CPU的消耗,比如进程、线程调度等。

1.数据准备

之前在某电商活动页面的优化过程中,笔者做了大量测试,由于测试中使用了localhost方式,所以PHP进程在I/O上的Wait Time是非常小的。接下来,由于最佳线程数接近CPU核数,所以在两核的机器上使用了10个PHP进程,客户端发起了10个并发请求,可以认为这是在最佳线程数下(最佳线程数在一个区间里,在这个区间里QPS总体变化不大,笔者也用5、15个并发,QPS值基本相同)得出的大量结果,接下来就分析一下这些测试结果。

1)测试出来的QPS(表4-1)

表4-1

5.jpg

只需要关注表4-1中各页面优化前的QPS和优化后的QPS即可。

2)CPU利用率

由于Apache Bench和PHP部署在同一台机器上,所以CPU利用率应该减去Apache Bench的CPU资源消耗。根据观察,优化前Apache Bench的CPU消耗在1.7%到2%之间,优化后Apache Bench的CPU资源消耗在20%左右。为什么优化前后有这么大的差距呢?因为优化后响应能够及时返回,所以导致Apache Bench使用的CPU资源多了。

在接下来的计算中,笔者将优化前的CPU利用率设置为98%,优化后的CPU利用率设置为80%。

3)CPU Time计算公式

根据QPS的计算方法,把QPS挪到右边的分母中,CPU Time移到等号左边,就会得到如图4-5所示的公式。

6.jpg

图4-5

4)CPU Time计算示例

根据上面列出的3点(CPU利用率、QPS和CPU核数),可以推算出优化前和优化后的CPU Time,推算方法如表4-2所示。

2.计算得到CPU Time

根据上面表格的计算方法,利用QPS计算出各页面理论上的CPU Time,计算后的结果如表4-3所示。

表4-2

11.jpg

表4-3

12.jpg

请大家着重注意优化前的CPU Time及优化后的CPU Time,接下来将两个值做减法,然后和实测程序中Gzip的CPU Time进行对比。

3.实测CPU Time

1)5个页面的Gzip所消耗的CPU Time

实测5个页面做Gzip所消耗的时间,然后跟公式计算出来的CPU Time做一个对比,如表4-4所示。

表4-4

13.jpg

可以看到,计算出来的CPU Time值要比测量出来的Gzip的时间多几毫秒,这是为什么呢?

因为在优化前,有两个消耗 CPU Time 的阶段,一个是执行 PHP 代码时,另一个是执行Gzip时。而优化后,整个逻辑变成了从缓存获取数据后直接返回,只有非常少量的PHP代码在消耗CPU Time(10行以内)。

2)PHP页面执行消耗的CPU Time

大体上可以认为:

优化前的CPU Time-优化后的CPU Time=GZIP CPU Time+全页面PHP代码的CPU Time

在实验中,一开始只统计了Gzip本身的消耗,而在PHP文件中PHP代码执行的时间并没有包含在内,所以两者差距比较大。于是,笔者单独统

计了5个页面的PHP代码的执行时间,发现文件中PHP代码执行的时间为3~6ms。实际测量的Gzip CPU Time加上3~6ms的PHP代码执行时间,和使用公式计算出来的CPU Time基本吻合。

根据上面的计算和测量结果,笔者发现Gzip的CPU Time消耗加上PHP代码的CPU Time消耗,与公式测量出来的总的CPU Time消耗非常接近,误差为1~2ms。考虑到CPU Time测量是单线程测量,而压力测试QPS是并发情况下(会多出进程切换的开销和GC等的开销),笔者认为这点误差是合理的,测试结果表明公式在宏观上是正确的。

4.1.6 压力测试最佳线程数和QPS的临界点

前面讲到了公式的推导,并在一个固定的条件下验证了公式在该场景下的正确性。

假设在一个thread-per-client的场景,有一个Ajax请求,这个请求返回一个Json字符串,每个请求的CPU Time为1ms,Wait Time为300ms(比如读写Socket和线程调度的等待开销)。那么最佳线程数是(300+1/1)×4×100%=1204。尤其在广域网上,Wait Time=300ms是正常的数值。在国际环境下,300ms就更加常见了。这意味着如果是4核的机器,需要1204个线程,如果是8核的机器则需要2408个线程。实际上,有些HTTP服务的CPU Time是远小于1ms的,比如上面的场景中将页面压缩并缓存起来之后,CPU Time基本为0.8ms,如果Wait Time还是300ms,那么需要数以千计的线程啊!

当线程数不断增加的时候,到达某个临界点之后对系统就开始产生负面影响了。

(1)大量线程上下文切换的开销,引起CPU Time的增加及QPS的减少。所以,有时候还没有达到最佳线程数,而QPS已经开始略微下降了。因为CPU Time发生变化、线程多了之后,调度引起的CPU Time提升的百分比和QPS下降的百分比成正比(详见QPS公式),上下文切换带来的开销如下。

● 上下文切换(微秒级别)。

● JVM本身的开销。

● CPU Cache加载。

(2)线程的栈空间会占用大量内存,假设每个线程的栈空间是1MB,这么多线程就要占据数GB的内存。

(3)在CPU Time不变的情况下,因为线程上下文切换和操作系统想尽力为线程在宏观上平均分配时间片的行为,导致每个线程的Wait Time都增加了,于是每个请求的RT也增加了,最终导致用户体验下降。

可以用一张简单的示意图(图4-6)来表示临界点的概念。

14.jpg

图4-6

由于线程数增加超过某个临界点会影响CPU Time、QPS和RT,所以很难精确测量高并发下的CPU Time,它随着机器硬件、操作系统、线程数等因素不断变化。笔者能做的就是压力测试QPS,并在压力测试的过程中调整线程数,使QPS达到临界点,这个临界点是QPS的一个峰值点。这个峰值点的线程数即当前系统的最佳线程数,当然如果这个时候 CPU 利用率没有达到100%,那么证明系统中可能存在瓶颈,应该在找到并处理瓶颈之后继续压力测试,并且重新找到这个临界点。

当数据结构发生改变、算法改进或者业务逻辑发生改变时,最佳线程数有可能会跟着变化。

在本节举的例子中,在CPU Time下降到1ms左右而Wait Time需要数百毫秒的场景下,我们需要很多线程。但是当达到这个线程数的时候,有可能早就达到了临界点。所以系统整体已经不是最健康的状态了,但是现有的编程模型已经阻碍了我们前进,那么应该怎么办呢?为使某个系统达到最优状态,我们来看看编程中的同步模型和异步模型问题,以及为什么异步模型只需要这么少的线程,是不是公式在异步模型下失效了。

北京小鱼在线科技有限公司,专业高端网站建设,网站制作,网站设计,手机移动网站建设、微网站制作,网站推广,SEO优化...咨询热线:13269427751  张经理 !


Copyright © 2013-2024 北京小鱼在线科技有限公司 All Rights Reserved   京ICP备14005856号-1   京公网安备11011402054166
友情链接:北京网站建设公司  北京网站制作公司  北京网站设计公司  北京网站开发公司  信托  信托产品  沙特签证