从公众号转载,关注微信公众号掌握更多技术动态
---------------------------------------------------------------
一、性能定律和数理基础
1.三个定律法则
(1)帕累托法则 我它也被称为 80/20 法则、关键少数法则,或者八二法则。人们在生活中发现很多变量的分布是不均匀的——在很多场景下,大约 20% 的因素操控着 80% 的局面。也就是说,所有的变量中,比较重要的只有 20%,是所谓的“关键少数”。剩下的多数,却没有那么重要。 举例来讲,在企业销售中,根据帕累托法则,大约“80%的销售额来自 20%的客户”。认识到这一点对企业管理至关重要,比如需要重视大客户的关系。 帕累托法则在IT 界的应用,尤其是怎么指导我们的代码开发和性能优化相关的领域的。
假如先解决 20% 的最重要的问题,就可以达到总体效果的 80%。以后再花费 80% 的努力,也只能解决剩下的 20% 的问题。在应用帕累托法则的时候,需要注意的是,里面的 80% 或者 20% 都是大约数字,实际的场景千差万别,不可能是恰好这两个数字。这个法则的精髓是,我们的生活和自然界万物的分布不是均匀的,总有些因素比其他因素更重要。
(2)阿姆达尔定律 阿姆达尔定律它本来用于衡量处理器进行并行处理时总体性能的提升度。
用洗衣服和晾衣服来举例。这里假设不用洗衣机,而是用传统的方式,先洗再晾。再假设洗衣服和晾衣服各需要 10 分钟,那么整个过程进行完需要 20 分钟。如果我们对晾衣服的过程进行优化,从 10 分钟缩短到 5 分钟,相当于进行了两倍优化。现在整个过程需要多长时间呢?需要 15 分钟,因为洗衣服的模块还是需要 10 分钟。
在这个基础上,继续对晾衣服模块进行优化,速度提升 5 倍,从 10 分钟缩短到 2 分钟。整个过程现在需要 12 分钟完成。在这个基础上继续进行类推,无论对晾衣服模块进行多大的优化,整个洗衣服、晾衣服的过程所需的时间不会小于 10 分钟,也就是整体加速比不会超过 2。
根据阿姆达尔定律描述,科学计算中用多处理器进行并行加速时,总体程序受限于程序所需的串行时间百分比。譬如说,一个程序 50% 是串行的,其他一半可以并行,那么,最大的加速比就是 2。无论用多少处理器并行,这个加速比不可能提高到大于 2。所以在这种情况下,改进程序本身的串行算法可能比用多核处理器并行更有效。 用公式来讲,假设一个系统的整体运行时间是 1,其中要进行优化加速的模块运行用时是P。如果对这个模块的加速比是 N,那么新系统的处理时间可以用下面的公式来表示。
这里面(1-P)是未被加速的其他模块运行时间,而 N 分之 P 是优化后的模块运行时间。它们的和就是新系统的总体运行时间。相对于旧系统,运行时间的加速比就是:
阿姆达尔定律对进行性能优化的指导意义有以下 2 点。
-
优先加速占用时间最多的模块,因为这样可以最大限度地提升加速比。
-
对一个性能优化的计划可以做出准确的效果预估和整个系统的性能预测。
(3)利特尔法则 在一个稳定的系统中,长期的平均客户人数(N)等于客户抵达速度(X)乘以客户在这个系统中平均处理时间(W),也就是说 N=XW。 如下图所示,客户按照一定的速度不断地进入我们的系统,假设这个速度是每分钟 X 个客户。每个客户在系统里的平均处理时间是 W 分钟。一旦处理完毕,客户就不会滞留在系统里。
所以,如果这个状态稳定,也就是说,系统处理速度恰恰好赶上客户到达速度的话,一方面系统没有空闲,另外一方面客户也不需要排队在系统外等待。那么在这个稳定状态下,我们的系统的总容量就恰好等于系统里面正在处理的客户数目。也就是说,N 就等于X 和 W 的乘积。 假定我们所开发的服务器程序可以进行并发处理,同时处理多个客户请求。并发的客户访问速度是每分钟到来 1000 个客户,每个客户在我们的服务器上花费的平均时间为 2 分钟。根据利特尔法则,在任何时刻,我们服务器系统里面将容纳 1000×2=2000 个客户。这2000 个客户都在被服务。过了一段时间,由于客户群的增大,并发的访问速度从每分钟 1000 客户增大到每分钟2000 个客户。在这样的情况下,我们该如何改进我们系统的性能来应对呢? 根据利特尔法则,可以有两种方案来解决这一需求。
-
第一种方案是把客户的处理时间减半,从 2 分钟减到 1 分钟。这样系统容量可以不变,客户滞留在系统的时间减半,刚刚好可以适应访问速率加倍的要求。系统容量就等于 2000 客户每分钟乘以 1 分钟,还是 2000 个客户。
-
第二种方案是扩大系统容量,维持处理时间不变。因为客户访问速度加倍了,所以系统容量也需要加倍,变成 4000。假如原来的系统需要 500 台服务器,那么新系统就需要 1000 台服务器。 从这里可以引申出利特尔法则在性能优化工作中的两种用处:
-
帮助我们设计性能测试的环境。比如当我们需要模拟一个固定容量的系统,那么性能测试的客户请求流量速度和每个请求的延时都需要仔细考虑。
-
帮助我们验证测试结果的正确性。有时候,如果性能测试的工作没有仔细地规划,得出的测试结果会出奇得好,或者出奇得差,从而让我们抓脑壳。这时如果采用利特尔法则,就可以很快地发现问题所在之处。
2.概率统计和排队论
(1)概率和置信区间 概率也称几率和机率,是一个在 0 到 1 之间的实数,是对随机事件发生之可能性的度量。
①贝叶斯定理
概率论中有一个很重要的定理,叫贝叶斯定理,做性能测试和分析中经常需要用到,贝叶斯定理描述的是在已知一些条件下,某事件的发生概率。比如,如果已知某癌症与寿命有关,合理运用贝叶斯定理就可以通过得知某人年龄,来更加准确地计算出他患上该癌症的概率。 具体来讲,对两个事件 A 和 B 而言,“发生事件 A”在“事件 B 发生”的条件下的概率,与“发生事件 B”在“事件 A 发生”的条件下的概率是不一样的。然而,这两者的发生概率却是有确定的关系的。就是 A 事件发生的概率,乘以 A 事件下 B事件发生的概率,这个乘积等于 B 事件发生概率乘以 B 事件下 A 发生的概率。听起来有点拗口,可如果用公式来表示的话其实很简单。
贝叶斯定理的一个用途在于通过已知的任意三个概率函数推出第四个。
②置信区间
置信区间是对产生样本的总体参数分布中的某一个未知参数值,以区间形式给出的估计。相对于后面我们要讲到的点估计指标(比如均值,中位数等),置信区间蕴含了估计精确度的信息。 置信区间是对分布(尤其是正态分布)的一种深入研究。通过对样本的计算,得到对某个总体参数的区间估计,展现为总体参数的真实值有多少概率落在所计算的区间里。比如下图是一个标准正态分布的图,阴影部分显示的是置信区间 [-1.7,1.7],占了 91% 的概率。
不难理解,置信水平越高,置信区间就会越宽。一般来说,如果需要涵盖绝大多数的情况,置信区间一般会选择 90% 或者 95%。
(2)数理统计的点估计指标 做性能测试和优化的过程中会产生大量的数据,比如客户请求的吞吐率,请求的延迟等等。获得这些大量数据后,如何分析和理解这些数据就是一门学问了。通常需要处理一下这些数据来求得另外的指标,以方便描述和理解。 描述性统计分析是传统数据分析的基础,这个分析过程可以产生一些描述性指标,比如平均值、中位数、最大值、最小值、百分位数等。这些描述性指标通常也被称为“点估计”,相对于前面讲到的置信区间,是用一个样本统计 量来估计参数值。
-
平均值:是最常用测度值,它的目的是确定一组数据的均衡点。但不足之处是它容易受极端值影响。需要注意的是,有好几种不同的平均值算法。平时比较常用的是算术平均值,就是把 N 个数据相加后的和除以 N。但是还有几种其他计算方法,分别适用不同的情况。比如几何平均数,就是把 N 个数据相乘后的乘积开 N 次方。
-
中位数:将数值集合划分为相等的上下两部分,一般是把数据以升序或降序排列后,处于最中间的数。它的优点是不受极端值的影响,但是如果数据呈现一些特殊的分布,比如二向分布,中位数的表达会受很大的负面影响。
-
四分位数:是把所有数值由小到大排列,并分成四等份,处于三个分割点位置的数值就是四分位数。从小到大分别叫做第一四分位数,第二四分位数等等。四分位数的优点是简单,固定了三个分割点位置。缺点也正是这几个位置太固定,因此不能更普遍地描述其他位置。
-
百分位数:可以看作是四分位数的扩展,是将一组数据从小到大排序,某一百分位所对应数据的值就称为这一百分位的百分位数,以 Pk 表示第 k 个百分位数。比如常用的百分位数是 P90,P95 等等。百分位数不容易受极端值影响,因为有 100 个位置可以选取,相对四分位数适用范围更广。几个特殊的百分位数也很有意思,比如 P50 其实就是中位数,P0 其实就是最小值,P100其实就是最大值。
-
方差 / 标准差(Variance,Standard Variance),描述的是变量的离散程度,也就是该变量离其期望值的距离。
(3)重要的分布模型 点估计统计指标很简单,但是描述数据的功能很有限。如果需要更加直观并准确的描述,就需要了解分布模型了。 举例来讲,假设我们有一个系统,观察对客户请求的响应时间。如果面对一万个这样的数据,如何对这个数据集合进行描述呢?这时候用分布模型来描述就很合适。①泊松分布
适合于描述单位时间内随机事件发生的次数的概率分布。如某一服务设施在一定时间内收到的服务请求的次数等。具体讲,如果随机变量 X 取 0 和一切正整数值,在 n 次独立试验中出现的次数 x 恰为 k 次的概率 P(X=k)就是:
公式中λ是单位时间内随机事件的平均发生次数。像下面这个图,表示的就是λ=5 的分布。红色部分是 P(X=4) 的概率,约为 0.17。
当 n 很大,且在一次试验中出现的概率 P 很小时,泊松分布近似二项式分布。
②二项分布
是 n 个独立的是 / 非试验中成功的次数的离散概率分布。这里通常重复 n 次独立的伯努利试验(Bernoulli trial)。在每次试验中只有两种可能的结果,而且两种结果发生与否互相对立,并且相互独立。也就是说事件发生与否的概率在每一次独立试验中都保持不变,与其它各次试验结果无关。当试验次数为 1 时,二项分布服从比较简单的 0-1 分布。在 n 重伯努利试验中,假设一个事件 A 成功的概率是 p, 那么恰好发生 k 次的概率为:
比如下图就是一个二项分布的图,图中红色的是 P(X=5) 的概率,约为 0.18。
③正态分布
也叫高斯分布(Gaussian distribution)。经常用来代表一个不明的随机变量。正态分布的曲线呈钟型,两头低,中间高,左右对称,因此经常被称之为钟形曲线。比如下图:
一个正态分布往往记为 N(μ,σ^2)。其中的期望值μ决定了其位置,其标准差σ决定了分布的幅度。概率密度函数如下:
当μ = 0,σ = 1 时的正态分布是标准正态分布。上图就是一个标准正态分布,线段的值代表了置信区间。比如在期望值附近,左右各一个标准差的范围内,差不多可以囊括 68.2%的概率;各两个标准差的范围内,囊括 95.4% 的概率;各三个标准差的范围内,囊括99.7% 的概率。正态分布的重要性在于,大多数我们碰到的未知数据都呈正态分布状。这意味着我们在不清楚总体分布情况时,可以用正态分布来模拟。
(4)排队的理论 计算机系统中的很多模块,比如网络数据发送和接收、CPU 的调度、存储 IO、数据库查询处理等等,都是用队列来缓冲请求的,因此排队理论经常被用来做各种性能的建模分析。排队论也被称为随机服务系统理论。这个理论能帮助我们正确地设计和有效运行各个服务系统,使之发挥最佳效益。 排队论的系统里面有几个重要模块,比如顾客输入过程、队列、排队规则、服务机构等。几个模块之间的关系大体上可以用下面这张图来表示。
主要的输入参数是到达速度、顾客到达分布、排队的规则、服务机构处理速度和处理模型等。排队系统的输出也有很多的参数,比较重要的是排队长度、等待时间、系统负载水平和空闲率等。所有这些输入、输出参数和我们进行的性能测试和优化都息息相关。排队的模型有很多,平时我们用得多的有单队列单服务台和多队列多服务台。系统里面各个模块的模型都可以变化,排队论里面还有很多延伸理论。
3.如何保持应用高性能
-
负载测试和负载场景——具备可用的负载测试和负载场景非常重要。
-
应用监控工具(APM)——诸如 Dyanatrace,AppDynamics 和 Epsagon 等工具。APM 在监控服务上可以帮我们节约大量的时间。因此在生产环境安装至少一个 APM 是非常有必要的。
-
有效的日志——有效的日志是生产服务中断调查和性能问题调查的基本条件。因此你必须确保应用的日志是清晰且有用的。
-
日志分析工具——你不能从很多文件中读取和搜索日志,尤其当你的服务是集群的时候,通过文件读取日志将变得更加困难。因此,花时间投产一个诸如 ELK,Grafana 或 Splunk 的日志收集器和分析工具是非常有必要的。
-
专业的人力支撑——对于上面提到的知识或者工具,如果你的团队没有相关的专业人才,那么你将什么也干不了。
因此,针对复杂的系统,建议投入专门的人和时间来处理。(例如,SRE 团队就能很好的胜任此项工作)
二、性能调优简介
步骤:测试、分析、调优
1.性能调优的时机
不确定的性能调优方案应该在项目开发完成之后进行。所有的系统在开发完后,都会有一些性能问题,做性能调优之前首先要做的就是想办法把问题暴露出来,例如进行压力测试、模拟可能的操作场景等等,再通过性能调优去解决这些问题。当然如果是一些十分成熟的性能调优方案可以直接在项目开发过程中完成(减少磁盘 I/O 操作、降低竞争锁的使用以及使用高效的算法等)。
2.稳定性和业务可持续性,通常比性能更重要
衡量代码质量的标准是可读性、可维护性、可扩展性,但性能优化有可能会违背这些特性,比如为了屏蔽实现细节与使用方式,我们会可能会加入接口层(虚拟层),这样可读性、可维护性、可扩展性会好很多,但是额外增加了一层函数调用,如果这个地方调用频繁,那么也是一笔开销。
这种有损代码质量的优化,应该放到最后,不得已而为之,同时写清楚注释与文档。
3.性能数据展示
(1)数据展示目的和方法
①性能数据展示的目的
-
向上级报告性能趋势和流量预测的结果;
-
向运维部门描述性能问题的根因分析;
-
向开发部门建议性能提升和代码优化。
②展示的目标
-
同意你的理论方法和过程;
-
信服你的推理分析;
-
理解问题的核心;
-
看出问题的根因;
-
同意你的建议和方案;
③数据展示的经验
-
按客人来备菜:既然展示的听众不同,所以千篇一律的用同一种展示方法绝不可行。比如向领导和管理层汇报时,就要注重宏观层次,技术不要钻地太深。
-
有啥菜吃啥饭:要根据手头数据的特点来决定展示方法。比如你有好几组相关的数据,有很有趣的趋势,那就要用线图来展示趋势和相关性。
-
给足上下文:很多工程人员容易犯的错误,就是想当然地以为别人也都了解问题的背景,然后一上来就展示很多细节。其实除了你自己,没有第二个人更了解你要解决的问题和要展示的数据,所以一定要给足背景介绍和上下文信息。
-
用图讲故事:人人喜欢听故事,而且是有趣的故事。如果你能把整个分析和推理的过程,变成一个引人入胜和图文并茂的故事来讲,那我保证你的展示会非常地成功。
-
和听众交互:尽量鼓励听众参与讲故事的过程。有两种类型的数据叙事:叙述型和探索型。叙述型是通过描述告诉观众具体结论;而探索型是鼓励观众探索数据以得出结论。虽然探索型叙事稍微多花一点时间,但若能成功运用,听众更容易被说服;因为结论是他们自己得出的,而不是你告诉他们的。
-
总结重要点:在数据展示的最后,一定要简洁明了地总结你的展示。比如你希望听众最后只记住三句话,是哪三句呢?根据你的展示目的,这三句或是相关数字,或是趋势,或是问题本质,或是解决方案。
(2)火焰图
它是一种 svg 可交互式图形,通过点击和鼠标指向可以展示出更多的信息。下图就是一个典型的火焰图,从结构上,它是由多个大小和颜色各异的方块构成,每个方块上都有字符,它们底部连接在一块,组成火焰的基底,顶部分出许多”小火苗”。
①特性
-
由底部到顶部可以追溯一个唯一的调用链,下面的方块是上面方块的父调用。
-
同一父调用的方块从左到右以字母序排列。
-
方块上的字符表示一个调用名称,括号内是火焰图指向的调用在火焰图中出现的次数和这个方块占最底层方块的宽度百分比。
-
方块的颜色没有实际意义,相邻方块的颜色差只为了便于查看。
②分析
那么一张火焰图,怎么能看出系统哪里有问题呢?由上文中的火焰图特性特性,查看火焰图时,最主要的关注点要放在方块的宽度上,因为宽度代表了调用栈在全局出现的次数,次数代表着出现频率,而频率也就可以说明耗时。但是观察火焰图底部或中部方块的宽度占比意义不大,如上面的火焰图,中部的 do_redirections 函数宽度是 24.87%,也就是说它耗用了整个应用近四分之一的时间,但是真正消耗时间的并不是 do_redirections 函数,而是 do_redirections 内部又调用的其他函数,而它的子调用分为了很多个,每个调用的耗时并没有异常。
更应该关注的是火焰图顶部的一些 “平顶山”,顶部说明它没有子调用,方块宽说明它耗时长,长时间 hang 住,或者被非常频率地调用,这种方块指向的调用才是性能问题的罪魁祸首。
找到了异常调用,直接优化它,或者再根据火焰图的调用链层层向下,找到我们的业务代码进行优化,也就大功告成。
③应用场景
-
代码循环分析:如果代码中有很大的循环或死循环代码,那么从火焰图的顶部或接近项部的地方会有很明显的”平顶”,表示代码频繁地在某个线程栈上下切换。但需要注意的是,如果循环的总耗时不长,在火焰图上不会很明显。
-
IO 瓶颈/锁分析:在我们的应用代码中,我们的调用普遍都是同步的,也就是说在进行网络调用、文件 I/O 操作或未成功获得锁时,线程会停留在某个调用上等待 I/O 响应或锁,如果这个等待非常耗时,会导致线程在某个调用上一直 hang 住,这在火焰图上表现得会非常清晰。与此相对的是,我们应用线程构成的火焰图无法准确地表达 CPU 的消耗,因为应用线程内没有系统的调用栈,在应用线程栈 hang 住时,CPU 可能去做其他事了,导致我们看到耗时很长,而 CPU 却很闲。
-
火焰图倒置分析全局代码:火焰图倒置有时也会很实用,如果我们的代码 N 个不同的分支都调用某一方法,倒置后,所有栈顶相同的调用被合并在一块,我们就能看出这个方法的总耗时,也就很容易评估出优化这个方法的收益。
④实现
brendan gregg 大神已经把生成火焰图的方法用 perl 实现了,开源代码就在上文的 Github 仓库中,根目录下的 flamegraph.pl 文件就是可执行的 perl 文件了。这个命令还可以传入各种参数,支持我们修改火焰图的颜色、大小等 。但 flamegraph.pl 只能处理特定格式的文件,像:
a;b;c 12a;d 3b;c 3z;d 5a;c;e 3
前面是调用链,每个调用之间用 ; 隔开,每行后面的数字是调用栈出现的次数。
(3)其它展示图
①线图 如果希望针对一个变量,显示一段时间内这个变量的变化或趋势,线图就最合适了。比如经常表示的时间序列图就是最直白的线图。另外,它也适用多个变量的情况,可以很直观地显示两个或多个变量之间的关系,比如趋势和相关性。
②散点图和气泡图 散点图显示沿两个轴绘制的两个变量的值,用点的模式揭示它们之间存在的任何相关性。比如两个变量分别是 CPU 使用量和客户吞吐率,那么我们可以期望散点图会显示比较强的同一趋势。 气泡图类似于散点图,但它可以显示三个数据项之间的变化。除了两个变量分别为是横轴和纵轴外,气泡的大小代表第三个变量。
比如一个公司里面有十个互联网产品,每个产品运行在不同数目的服务器上面。这些产品分别有不同的内存使用率和网络使用率。假设我们想分析一下产品的内存和网络的使用情况,然后决定下一个季度购买什么样的服务器。就可以用气泡图来表示,网络和内存的使用率来作为横轴和竖轴,气泡的大小代表每个产品的服务器数目。通过这个气泡图,非常直观地发现,某个产品的气泡比其他产品都大,而且它的两种资源使用率都很低。整体来言,下个季度新采购的服务器可以不需要那么多内存和网络资源。
③饼图和圆环图 当需要显示比例数据或者百分比时,饼图最佳。由于饼图表示零件与整个实体之间的大小关系,因此零件需要总和必须为有意义的整体。一个适合饼图表示的性能数据例子是客户请求的来源分布。但你在使用时需要注意的是,饼图最好用来显示六个或更少的类别。如果太多类别的话反而描述不清楚。
④树形图 树形图对于显示类别和子类别之间的层次结构和比较值非常有用,同时也能保留较多细节。树形图可以帮助我们很直觉地感知哪些区域最重要。另外,还可以通过将颜色编码的矩形嵌套在彼此内部,来更好地实现目的,并用加权以反映它们在整体中的份额。比如下面这个树形图,它描绘了一个国外的产品,采用不同营销渠道的价值,然后按国家 /地区细分
这里的营销渠道包括 Email、Social Media、AdWords 等。从这个图中,你一眼就能看出从营销渠道上来看,AdWords 是最成功的营销渠道,因为它对应的面积最大。但是从国家层面来讲,美国(United States)是所有渠道中最有价值的目的地,同样是因为对应的面积最大。
⑤热图 热表以表格格式来表示数据,其中每个格子是用颜色,而不仅仅是用数字来展示。这些颜色分别对应包含定义的范围,如绿色代表小的值,黄色代表一般的值和红色代表大的值。比如分析几台服务器的在一天里面 CPU 的使用率。我们可以把每台服务器在每个时段的CPU 使用率分为小、较小、中等、较大和大等几个范围。然后相应地着色。我们看下图,横坐标代表时段,纵坐标是服务器,每个值就是 CPU 使用率。
虽然每个使用率其实是数字,但是把数字编码为颜色的好处是,颜色编码格式使数据更容易理解。比如马上就可以看出早上 10 点的时候,服务器 1 和 5 的 CPU 使用率很高,因为它们的格子是红色。