认清性能问题

本文翻译自 Thinking Clearly About Performance 这是我三年前读到的一篇关于性能问题的好文,读完后还觉不过瘾,怕理解的不够遂又翻译了一遍,这也是当年个人第一次翻译。html

这几年来每次碰到性能问题,我都会想起这篇文章,它并不像不少其余关于性能问题的文章,告诉你利用什么工具怎么去解决性能问题,这类文章更多属于「术」的层面,而术的层面在不一样的技术栈会有很不一样的选择。而本文则高屋建瓴的帮助读者创建起对性能的正确认识,从而可以得到更全面的视角去看待和思考性能问题。这是「道」的层面,正所谓道法天然,术变万千,深入理解了「道」,那么面对性能问题的万千之「术」才不会那么茫然。数据库

文章略长,建议先收藏,稍后合适时抽出一块时间来细细读之,当有所获。浏览器

摘要

对于开发者、技术管理者、架构师、系统分析师和项目经理来讲,建立具有高性能特征的复杂软件都是一件极其困难的事。然而,经过了解一些基本原理,性能问题的解决和预防能够更简单可靠。本文讲述了这些基本原理,涵盖了一系列的目标、术语、工具和决策,综合利用好它们来最大可能的建立一个长期有效的高性能应用。本文的一些例子来自于 Oracle 的经验,但本文的范围并不局限于 Oracle 的产品。缓存

目录

  • 公理化方法安全

  • 什么是性能?性能优化

  • 响应时间 VS. 吞吐量服务器

  • 百分比指标微信

  • 问题诊断网络

  • 序列图架构

  • 性能剖析

  • 阿姆达尔定律

  • 偏斜度

  • 最小化风险

  • 效率

  • 负载

  • 队列延迟

  • 拐点

  • 拐点的相关性

  • 容量规划

  • 随机到达

  • 相关性延迟

  • 性能测试

  • 测量

  • 性能是一项功能特性

  • 尾声:关于「拐点」的公开辩论

  • 关于做者

  • 参考


1. 公理化方法

当我在 1989 年加入 Oracle 公司时,解决性能问题(人们一般说的是 Oracle 调优)是很困难的。 只有少部分人声称他们很擅长这个,不少人都去咨询他们。 当时,我进到 Oracle 调优这个领域时,我彻底没准备好。 最近我又开始对 MySQL 进行调优,这看起来和我 20 年前在 Oracle 公司作的差很少。

它让我想起了当我 13 岁刚接触代数学时是多么的困难。 在那个年龄我只能依靠「数学直觉」来解决相似 3x + 4 = 13 这样的方程。 问题是咱们之中大部分人都没有所谓的「数学直觉」。 我记得当看到这样的问题: 3x + 4 = 13 求解 x,只能采用试错法偶然发现 x 应该是3。

试错法给个人感受虽然能解决一些简单的方程式,但很慢并且不爽。 一旦等式稍有变化如 3x + 4 = 14,试错法就不能适应。 那么该怎么办呢?当时我没有好好思考过,直到 15 岁时 James R. Harkey 指引我走上正确的道路。

Harkey 先生教会我使用公理方法来解决代数方程问题。他给咱们展现了一系列的步骤(还给了我不少家庭做业进行练习)。作做业时除了记录下这些步骤,还要写下咱们是如何思考的。这样咱们不只本身想得很清楚,并且经过一系列可靠的、可重复的步骤来向阅读咱们做业的人证实了咱们确实搞明白了。 Harkey 先生看到的个人做业像下面这样:

3.1x + 4 = 13               
待求解方程
3.1x + 4 - 4 = 13 - 4
减去相等的值
3.1x = 9
加法逆运算,化简
3.1x ∕ 3.1 = 9 ∕ 3.1
除以相等的值
x ≈ 2.903
乘法逆运算,化简求解

这就是 Harkey 先生教导的适用于代数学、几何学、三角学和微积分的公理化方法。 由一系列符合逻辑的、可证实的和可审计的小步骤组成。这是我第一次真正从数学中学到的东西。

天然,当时我没能认识到其中的价值,但证实做为一种技能对我后来的成功相当重要。我发如今生活中,知道一件事很重要,但能向别人讲清楚(证实)更重要。没有好的证实技能,就很难成为一名好的顾问、好的领导甚至好的员工。

我在上世纪 90 年代中期的目标是为 Oracle 性能优化建立一套相似的、严格的公理化方法。后来我将其扩展到了 Oracle 以外,创建了一套适用于全部计算机软件性能优化的公理化方法。好吧,我发现并不是全部人都喜欢这种说法,那咱们换一种说法:

咱们的目标就是帮助你想清楚如何优化你的软件系统性能。

2. 什么是性能?

假如你去 Google 下 Performance 这个关键字,可能会获得 5 亿个连接。 其中涉及的内容范围可能从自行车比赛到可怕的员工审查流程(现在不少公司已经学会了避免这个流程)。但假如我去 Google 下 Performance 这个关键字,大部分的首页连接都会与这篇文章的主题有关:__计算机软件执行不管何种任务所花费的时间。__

任务这个词是一个很适合的开始。任务是一个面向业务的工做单元。任务可以嵌套:打印发货单是一个任务,打印一张发货单(一个子任务)也是一个任务。当一个用户提及性能时,他一般指的是系统执行一系列任务所花费的时间。__响应时间__ 是任务的执行时长,用每一个任务的时间来度量,像每点击秒数。例如我用 Google 搜索关键字 Performance 的响应时间是 0.24 秒。 这个数据来自个人浏览器它渲染完 Google 网页花费的时间,那么很明显,这量化了我对 Google 性能的直觉感知。

一些人对另一个性能指标很感兴趣:吞吐量。 吞吐量 是在一个特定时间段内完成的任务的计数,例如:每秒点击数。一般为一群人提供服务比为个别人提供服务的人更关心吞吐量。例如,一个独立会计会更关心日报的响应时间是否会致使今晚须要加班,而会计部的经理更关心系统的是否能支撑全部的会计处理完今天的数据。

3. 响应时间 VS. 吞吐量

一般来说,响应时间和吞吐量是一个倒数关系(响应时间越长吞吐量越低),但这并不确切。 实际状况更微妙、复杂一些。

例 1
假如,在一些性能基准测试中,你的系统的测量结果是每秒能处理 1000 个任务,那么用户的平均响应时间是多少? 你可能会说平均响应时间等于 1 / 1000 = 0.001 秒/每任务,但它真不是这样的。

假如在你的系统内部拥有 1000 个相同的、独立的、并行的服务执行通道,每一个通道都在等待请求到来并提供服务。 在这种状况下,每一个请求其实花费了 1 秒。

如今咱们知道,平均响应时间其实应该在每任务 0 秒到 1 秒之间。 可是咱们不能仅仅从吞吐量的测量数据中推导出平均响应时间。(事实上存在数学模型从吞吐量推导出平均响应时间,但这个模型要求更多的输入参数,而不只仅是吞吐量) 你必须单独测量它。

反过来讲也是同样的,你应该能从我上面给出的例子中获得启发。 下面是一个更有趣的例子。

例 2
你的客户要求一个新任务必须知足在单核 CPU 的计算机上达到每秒 100 的吞吐量。 假如这个新任务在客户的系统上执行一次须要 0.001 秒。 那么你的程序可以知足客户要求的吞吐量么?

你可能会说,跑一次这个任务只须要千分之一秒,那么在一秒内完成 100 次显然是绰绰有余的。 恩,是的,你很正确,假如这个任务被很好的串行化了。 例如,你的程序处理 100 个任务执行请求是在一个循环中,一个接一个的执行,那就是正确的。

可是若是这 100 个任务到达你的系统是彻底随机的来自 100 个不一样的用户,那该怎么办呢?CPU 调度器和串行资源(Oracle 的闩和锁,内存可写缓冲区访问)这些糟糕的实际状况会严格限制你的并发吞吐量低于每秒 100。 最终,你可能会达到客户的指望也可能达不到。 你不能仅仅从响应时间推导出吞吐量,你必须单独测量吞吐量。

因此,响应时间和吞吐量不是那么简单的互为倒数关系。 你想要知道这两个指标,就必须一块儿测量它们。那么响应时间和吞吐量到底哪个更重要呢? 在一些场景下,说哪个都是合理的。 但在大多数状况下,二者都一样重要。 由于,对系统来讲它的业务需求一般是这样的,在大于 99% 的状况下响应时间要少于 1 秒,而且能支持 10 分钟内持续不低于 1000 的吞吐量。

4. 百分比指标

在上一节,我用了“大于 99%”这样的描述来表达对响应时间的指望。 但大部分人可能更习惯于这样的描述:“平均响应时间少于 r 秒”。 但从经验的角度,使用百分比方式更好。

例 3
假想天天运行在你的计算机上的任务的响应时间的容忍极限是 1 秒。进一步假设「表1」列出了该任务执行 10 次的测量值。 这两个列表的平均响应时间都是 1 秒。哪个你认为更好?

虽然你看到两个列表拥有一样的平均响应时间,但本质上差异很大。ListA 90% 的响应时间是低于 1 秒的,而 ListB 只有 60% 的时间是低于 1 秒的。从用户体验的角度来讲,ListB 代表会有 40% 的用户会感到不满意,而 ListA 仅有 10% 的不满意率,虽然它们平均响应时间相同。

ListA 90% 的响应时间是 0.987 秒,而 ListB 90% 的响应时间是 1.273 秒。 所以使用百分比描述的响应时间比平均响应时间包含更多的信息量。

正如 GE 公司所说:“客户感觉到的是差别变化,而非平均”。(参见GE的《什么是六西格玛》) 可见使用百分比来描述响应时间更符合终端用户的指望:例如,99.9% 的跟踪货运单的任务必须在 0.5 秒内完成。

5. 问题诊断

最近我被邀请去解决的一些性能问题的描述都是些关于响应时间的。 如:“过去只用不到 1 秒的时间就能完成 X 任务,可是如今却须要 20 秒。” 固然,一些真正的问题隐藏在其余一些问题描述的表象背后,例如:“咱们的系统变的很慢,彻底无法用了。”

虽然我常常碰到相似这样的表述,但并不意味着你应该这样去描述问题。 首先你得清晰得描述问题自己,才可能把它们弄清楚。 一个好办法是去询问,你想要达到得目标状态是怎样的呢? 找到一些细节,你能够用量化的方式来表达它们。 例如:执行 X 任务大部分状况下都超过 20 秒,但愿能在 95% 的状况下小于 1 秒。

理论上这听起来很棒,但要是你的用户根本没有很具体的能够量化的目标呢?或者你的用户根本就不知道怎样去量化,更糟糕的状况是你的用户若是还有一些彻底不切实际的指望怎么办?你如何知道到底什么是“可能的”,什么是“不切实际的”?

好吧,下面咱们继续探讨这些问题。

6. 序列图

序列图是一种 UML(统一建模语言)中定义的图形种类,用于表达对象间交互的发生顺序。序列图特别适合用于可视化的表达响应时间。 在「图1」中,咱们展现了一个由浏览器、应用服务器和数据库构成的简单应用系统的序列图。

假如咱们扩展下序列图的表示,让请求和响应之间距离表示服务该请求的消耗时长。 在「图2」中我展现了一个扩展后的序列图。

经过「图2」你能够很直观的看到究竟是哪一个部分消耗了最多的时间。你能直观的感觉到整个响应时间在各个部分的构成。序列图很好的帮助人们从概念上直观的理解一个任务如何在系统各个部分之间顺序流转的。序列图也能很好的表达并行执行的任务。序列图也是一个很棒的工具用于在业务层次分析性能问题。

序列图是很好的描述性能问题的概念工具,但要把性能问题分析清楚咱们还须要其余的。序列图的问题是,假设有个任务花费了 2468 秒才执行完成(大约 41 分 8 秒)。 在这 41 分钟里,应用服务器和数据库大约交互了 322968 次。 把这个过程画成序列图大概就是下面「图3」的样子:

在应用服务器和数据库之间有如此之多的箭头,以致于你彻底看不清细节了。咱们可能须要花费数周才能画完这个图,但这并非一个有效的方法。序列图虽然很好的概念可视化了任务的执行流和时间流,但要仔细分析清楚响应时间的问题咱们还须要别的工具。

7. 性能剖析

对于像上述这种拥有大量调用交互的状况,序列图不能很好的描述。咱们须要一种更方便的聚合视图来更容易的理解到底哪一个部分消耗了最多的时间。 「表2」给出了一个性能剖析的例子。性能剖析是对响应时间的表格化分解,按响应时长倒序排列以下。

例 4
「表2」中的性能剖析是很初级的,但它能告诉你最慢的 8 个任务占用了 2468 秒。从中你大概能够获得每一个函数的响应时长占比。也能够从中算出每一个函数调用的平均响应时间。

性能剖析指出了哪些代码花费了你的时间,也许更重要的是告诉你哪些代码并无花费太多时间。当你不得不去猜想代码的性能瓶颈时,性能剖析是有巨大价值的。

从「表2」的数据代表,大约 70.8% 的响应时间消耗在了 DB:Fetch() 这个方法上。若是你进一步深刻方法调用中会发现 App:await_db_netIO() 方法与 DB:Fetch() 的一一对应关系。因而能知道每一个部分消耗了多少时间,经过性能剖析你开始可以明确的回答像这样的问题:“这个任务须要运行多长时间?”从第 5 节你能够知道,对问题诊断的第一步来讲这是一个很重要的问题。

8. 阿姆达尔定律

性能剖析能帮你分析清楚性能问题。即使吉恩·阿姆达尔(Gene Amdahl)在 1967 年没有告诉咱们阿姆达尔定律,你也能够在看到性能剖析表格时本身概括出来。阿姆达尔定律指出:

系统中对某一部件采用更快执行方式所能得到的系统性能改进程度,取决于这种执行方式被使用的频率,或所占总执行时间的比例。

因此若是你尝试改进的部分只占总响应间的 5%,那么对总响应时间的提升最多不会超过 5%。这意味着你改进的部分在性能剖析列表中排位越高(假设它们按倒序排列),你得到的收益就越大。

但这并不意味着你必定要按照性能剖析列表的顺序从高到低进行改进,这里你还须要考虑改进的成本问题。

例 5
看下「表3」,它基本和「表2」同样。「表3」额外给出了你实施最好的改进方案所能达到的效果以及相应的实施成本。

那么你应该先实现哪一项改进呢?阿姆达尔定律告诉咱们改进第一项的潜在收益最大,大约能够减小851秒(34.5% * 2468秒)。但改进第一项真的很是昂贵,那么改进第二项也许能产生更多的净收益。这才是咱们真正须要改进的瓶颈所在,尽管它仅能节省大约 303 秒。

性能剖析的巨大价值在于你可以确切的了解你预期的投资能得到多大的改进,它为你的改进实施方案提供了决策支持,为你在预测给性能问题的度量时提供了参照。__当你可以找到一种比预期成本更低,减小响应时间比预期更多的改进方式时,这给你了一个很好展现聪明才智的机会。__

你首先实施哪一项改进,归结于你对成本评估有多大把握。简单省事的改进的措施是否考虑了改进可能形成的系统风险?一个很简单的改进,例如调整了某个参数,取消了一个索引可能会潜在的破坏了一些目前性能表现良好的功能,而你又彻底没考虑倒。__靠谱的成本评估则是展示你技术能力的另外一个领域了。__

另外一个因素值得考虑的是,你能够经过一些小的成功来积累政治资本。也许一些便宜低风险的改进并不能带来响应时间的大幅度下降,但能够经过跟踪记录这些小改进来印证你对响应时间提高的预测。在神话和迷信统治了数十年的软件性能领域,这些对性能的预测和印证的跟踪记录,能够影响你的同事(工程师、经理、客户)并创建本身的信誉,而后你才可能实施更昂贵的改进方案。

最后提醒一句:当你不断取得胜利并建议实施成本更高、风险更大的改进措施时,可千万别掉以轻心。信任是很脆弱的,你作了不少事情才取得信任,但可能只是由于一次粗枝大叶的错误就会摧毁它。

9. 偏斜度

当你使用性能剖析时,你会反复遇到相似这样的衍生问题。

例 6
从「表2」中能够看到一共调用了 322,968 次 DB:fetch() 方法,花费了 1748.229 秒。假如咱们将调用量下降一半,那么响应时间会下降多少?答案绝对不会是下降一半,花点时间思考下面这个更简单点的例子。

例 7
调用 4 个方法花费了 4 秒钟,那么减小为调用 2 个方法花费多少时间?答案依赖于咱们省掉的调用究竟是哪些方法。你可能这样假设了,每一个方法的平均调用时间就是 4 / 4 = 1 秒。但我可没在问题描述中告诉你每一个方法的调用耗时是同样的。

例 8
假设下面两种可能性,每一个列表列出了 4 个方法调用的响应时间

A = {1, 1, 1, 1}
  B = {3.7, .1, .1, .1}

在 A 中响应时间是一致的,因此不管咱们省掉了哪两个调用,最后响应时间都能缩短到 2 秒。但在 B 中,到底省掉哪两个方法调用对响应时间的影响是有很大差异的。若是咱们去掉头两个调用,响应时间缩短为 0.2 秒,提高了 95%。但若是咱们去掉的是后两个调用,响应时间变为 3.8 秒,仅仅提高了 5%。

偏斜度表达在一组值中的非一致性程度。正是由于偏斜度的存在,因此你无法准确的回答我在本节开头的问题。 让咱们再回头看看这个例子:

例 9
在不知道偏斜度的前提下,你只能回答响应时间可能减小的范围是在 0 到 1748.229 秒之间,这也是你能提供的最好的回答了。

尽管如此,假设你有一些额外的信息,如「表4」所示,你就能对最好和最坏的状况进行估算。进一步说,假如你有了「表4」中信息就会很聪明的去特别优化响应时间在 0.01 秒到 0.1秒 之间的那 47,444 个调用。

10. 最小化风险

前面的章节我提到过,当修复一个任务性能问题时可能破坏另外一个任务的性能,让我想起了一件曾经在丹麦发生的事。这个故事很短:

场景
在丹麦的巴勒鲁普自治市(Måløv)的一张橡木餐桌前,大约 10 我的围坐一块儿,在用笔记本工做和相互交流。

Cary: 伙计们我快热死了,大家不介意我打开窗户放点冷空气进来吧?
Carel-jan: 为何你不把你的厚毛衣脱了呢?

完。

在这里,有个乐观的人都知道的通常性原则在发挥效力:__当你们都很温馨除了你之外,那么你首先应该确保影响本身的东西是否正常,不然你可能去搞乱一些全局的东西致使每个人都受影响。__

正是这个原则,当由于几个写的很烂的 Java 应用程序有人建议我去调整 Oracle 的网络包大小时让我感到很惧怕。这些很烂的程序产生了不少没必要要的数据库调用,天然也产生了不少没必要要的网络等待。当其余一切正常除了这几个烂程序,那么最安全的作法是将调整的范围本地化,只须要去调整这几个烂程序就行了。

11. 效率

即使依赖此系统进行工做的全部人都很痛苦,你依然须要首先专一于业务上最优先须要修正的程序部分。让程序工做的尽量的高效是一个很好的切入点。在不增长容量,不牺牲必须的业务功能的前提下,效率是可以节省下来的任务总执行时间的倒数。

换句话说,效率就是从反面对浪费进行的度量。下面是一些常常发生在数据库应用中浪费的例子。

  • 一个中间层程序为插入数据库的每条记录建立了一条独立的 SQL 语句。它执行了 10,000 次数据库预编译语句调用,致使了 10,000 次网络 I/O 调用。其实它能够只使用一条预编译语句,从而节省 9,999 次网络 I/O 调用。

  • 一个 SQL 语句访问数据库缓冲区缓存 7,428,322 次得到了 698 行的结果集。使用一个额外的过滤预测,只返回了终端用户真正想要看见的 7 行结果,只需访问缓冲区缓存 52 次。

确实若是一个系统存在一些全局性的问题(不良索引、错误参数、弱弱的硬件配置)致使了一大片任务执行的低效率,你应当修正它。但不要尝试调优系统去知足低效的程序。有不少办法来调优低效的程序自己。即便这个程序是商业的现成的软件,那么和你的软件供应商一块儿去优化程序自己比你去优化系统让其尽量的高效从长期来讲会更受益。

让程序变的更高效会让工做在这个系统上的每个人都受益巨大。很容易看到浪费的减小对任务响应时间的贡献。但依然有不少人不明白为何提高这部分程序的性能会致使一种反作用,让看起来彻底不相关的另外一个程序性能变差。

其实这是系统负载在做祟。

12. 负载

负载(Load)是并发任务执行时引起的资源竞争。负载正是咱们为何不能在性能测试中捕捉到全部性能问题的缘由,而这些问题之后会在生产环境发生。负载的一个测量指标是使用率,使用率反应了资源按时间分片的使用状况。当某个资源使用率上升时,那么请求该资源服务的用户就不得不经历更长的响应时间。任何一个在城市的高峰期开车的人都经历过相似现象。当交通变的严重拥堵时,你不得不在收费站前等待更长的时间。

你的汽车在开阔的道路上能开上每小时 60 英里,但在拥堵的路上只能以每小时 30 英里的速度行驶,而软件不会像汽车这样真的变慢。软件按照固定的一样的速度执行,每一个时钟周期老是执行一样数量的指令,但响应时间会随着系统资源变的繁忙而严重退化。

负载上升系统变慢的缘由有两个:__队列延迟__ 和 __相关性延迟__。下面我会进一步讲述。

13. 队列延迟

负载和响应时间之间在数学上的相关性你们都很熟悉了。一个叫作「M/M/m」的数学模型(译注:「M/M/m」是一个关于队列理论的数学模型,你无需详细搞明白也能从感性认识并理解做者的分析。)将响应时间和负载关联起来应用于一些特定的需求场景下。「M/M/m」模型的一个假设前提是你的系统模型拥有理论上的完美扩展性。这个假设很是相似于咱们在初级物理学课程中常常提到的光滑表面(无摩擦力)假设。

虽然「M/M/m」模型假设的条件有些不现实,如完美的可扩展性,但从中依然能够学到不少。「图4」使用「M/M/m」模型展现了负载和响应时间之间的关系。

从「图4」,你从数学的角度看到了系统在不一样负载条件下给你的感觉。低负载下的响应时间和无负载基本同样。当负载上升时,你能感觉到响应时间有一个轻微、平缓的退化。这种平缓的变化不会形成什么麻烦,但随着负载继续上升响应时间开始以一种急剧的方式退化,这可要形成大麻烦了。

响应时间在具有完美扩展性的「M/M/m」模型下由两个部分组成:__服务时间__ 和 __队列延迟__。

就是这样一个等式:R = S + Q
服务时间(S)就是任务的执行时间。
队列延迟(Q)就是任务在队列中等待机会得到消费某个资源的时间。

因此当你在 Taco Tico(美国和墨西哥边境的快餐连锁)订餐时,你的订单响应时间(R)就包括了等待服务员来餐桌边接收订单的等待时间,这就是队列延迟等待(Q),而服务时间(S)就是从订单交到服务员时到食物送到你手上的等待时间。 一样,任务的响应时间在有负载和无负载的系统之间是有差异的。

14. 拐点

说起性能,你想要达到两个目标:

  • 你想要得到最快的响应时间:你不想任务的完成须要太长的时间。

  • 你想要得到最大的吞吐量:同一时间能支持更多人执行他们的任务。

不幸的是这两个目标是相互矛盾的。__优化达到第一个目标须要你最小化系统的负载,而达到第二个目标则要最大化系统负载,两者不可兼得。__ 在这二者之间的某个负载值就是系统的最优负载。

处于最优负载平衡点的资源使用率的值,我称其为「拐点」。系统中某种资源达到「拐点」后,那么吞吐量被最大化了而对响应时间只有很小的负面影响。从数学上来说,「拐点」就是响应时间除以资源利用率所得结果最小的值。 「拐点」有个很好的属性,就是位于从原点画一条直线正好与响应时间曲线相切的位置。 在一个仔细绘制的「M/M/m」图中,你能很容易的利用这个性质找到「拐点」,以下「图5」所示。

关于「M/M/m」模型「拐点」的另外一个很好的属性是你只须要知道一个参数就能够计算出它。这个参数就是系统中并行的、相同的和独立的服务通道数。服务通道是一种资源,它们共享一个队列,其余资源像收费站或者 SMP(Symmetric multiprocessing 对称多处理)结构的计算机中的 CPU 都是相似的概念。

在「M/M/m」模型中,斜体小写的 m 表示系统建模时服务通道数。对任意一个系统来讲,计算「拐点」都是很困难的,好在我已经给你计算出来了。「表5」中列出了一些常见的服务通道数的「拐点」值。(此时你也许想知道在「M/M/m」队列模型中另外两个 M 表明什么。它们与请求进入时刻和服务时间的随机性假设有关。 更多请参考 Kendall's Notation 或进一步参考 _Optimizing Oracle Performance_)

为什么「拐点」如此重要?对于那些请求随机到达的系统,若是资源负载持续超过「拐点」,那么响应时间和吞吐会由于负载的轻微变化而严重波动。 因此,__对于请求随机到达的系统而言,保持负载低于拐点是相当重要的。__

(译注:从上面「表5」能够看出,为何经验值将 4 核的虚拟化容器 CPU 负载红色报警点在 60%,32或64 核物理机的 CPU 负载红色报警点在 80%。)

15. 拐点的相关性

那么「拐点」的概念是否是真的如此重要呢? 毕竟,我曾经告诉过你「M/M/m」模型创建在一个理想的乌托邦理念之上,那就是系统拥有完美的可扩展性。我知道你正在想什么:你想的都是错的。

「M/M/m」模型告诉咱们,即使你的系统拥有完美的可扩展性,你依然会遭遇巨大的性能问题只要系统的平均负载超过了图表中给出的拐点。那么现实中你的系统不可能比「M/M/m」假设的理论系统更完美。因此,你的系统的真实「拐点」会比我在「表5」中给出的更小。(我在这里对拐点使用了复数形式,由于你能够基于 CPU 来创建拐点模型,同时也能够基于你的磁盘、网络 I/O 等等。)

再次说明:

  • 你的系统中的每一项资源都有一个「拐点」。

  • 你的系统「拐点」都是小于或等于「表5」中给出的理论值,你的系统扩展的完美性越差,「拐点」越小。

  • 对于请求随机到达的系统,若是资源负载持续超过「拐点」,你将遭遇性能问题。

因此,保持负载低于拐点是相当重要的。

(译注:因此性能测试干的就是找出真实系统的负载拐点,并和理论值比较就能够看出系统的横向扩展性是否有瓶颈点。)

16. 容量规划

理解了「拐点」能够减小容量规划的复杂性,能够这样来规划:

  • 某项资源的容量就是在高峰期能轻松的运行你的任务而资源使用率不会超过「拐点」。

  • 保持资源利用率低于「拐点」,那么系统表现就基本不会给你带来大的惊讶。

  • 可是,若是系统中任何一项资源超出了它们的「拐点」,你就会遭遇性能问题,不管你是否意识到。

  • 若是你遭遇性能问题,不要纠结于数学模型上,要修正这些问题要么从新安排下负载分配,要么减小负载,要么增长容量。

这就是将性能管理过程和容量规划结合起来的办法。

17. 随机到达

你可能已经注意到了,我在前文常常说起“随机到达”这个说法,为何它如此重要?如今一些系统拥有的特征你可能不会具有,如:彻底肯定的做业调度。另一些系统被配置为接受任务的方式像是机器人模式,如每秒接受一个任务,十分固定,固然如今这些系统不多见了。我这里说的一秒一个任务,并非说平均一秒一个任务,例如第一秒 2 个任务,而下一秒 0 个任务。我指的是均匀的一秒来一个任务,相似汽车工厂组装线上机器人的工做模式。

若是任务到达系统是彻底肯定的,就是说你彻底能预知下一个请求何时到达,那么让资源的使用率超过「拐点」必然不会引起性能问题。对于一个任务到达很肯定的系统,那么你的目标应该是将资源利用到 100%,而不是让它们排队等待。

「拐点」对于随机到达的系统如此重要的缘由是,随机任务请求每每会汇集并引起短暂的资源使用脉冲式上升。这些脉冲式上升须要足够的剩余容量来消化它们,因此当脉冲发生时可能就会引起队列延迟并致使响应时间的明显起伏。

短暂的脉冲并致使资源使用率超过「拐点」也还好,只要不要持续达到数秒时间。这个数秒到底应该是多少秒呢? 我相信(固然我没试过去证实)这个时间最好不要超过 8 秒。(来自著名的互联网 8 秒原则) 若是你没法知足在特定百分比下响应时间和吞吐量对用户的承诺,那么很显然系统脉冲上升持续时间太长了。

18. 相关性延迟

你的系统确定不具有理论上的完美扩展性。尽管我从没分析过你的系统,但我敢打赌不管你的系统不管是什么样的也不具有「M/M/m」理论模型假设的完美扩展性,而相关性延迟正是你的建模不可能完美的缘由。执行任务时花在对共享资源访问的协商和通讯的时间就是相关性延迟。和响应时间、服务时间、队列延迟同样,相关性延迟也能够在任务的执行中被测量,例如每点击秒数。

这里我并不想描述预测相关性延迟的数学模型,但若是你分析过你的任务执行状况,你能够了解何时相关性延迟会发生。在 Oracle 中,一些同步的事件正是相关性延迟的例子:

  • 入队列(enqueue)

  • 缓冲忙等待(buffer busy waits)

  • 闩锁释放(latch free)

你不能使用「M/M/m」来对相关性延迟进行建模,由于「M/M/m」模型假设了你的 m 个服务通道是彻底并行的、等同的和独立的。这个模型假设在一个先进先出(FIFO)队列中,只要你等待的时间足够长,在你以前的请求已出队列并获得服务,那么最终你也会获得服务,可是相关性延迟不是这样工做的。

例 10
假设在一个 HTML 数据表单上,有个按钮是「更新」,点击它会执行一条 SQL 更新语句。另一个按钮是「保存」,点击它执行事务提交将刚才的更新保存下来。若是一个应用是这样作的,我能够保证它的性能是很是糟糕的。这是由于这样一种设计,让下面的场景成为可能的,实际上这也是必然可能的。一个用户先点击了「更新」,发现到了午饭时间,而后就去吃饭了,过了两小时下午回来再点击「保存」。

对于想要更新同一行的其余任务来讲,这是一个灾难。其余任务不得不等待获取行锁,更糟的状况下甚至是页锁,直到原来锁定的用户想起继续点击「保存」。或者 DBA 来杀掉原来锁定用户的会话,这样的话固然又会给原用户形成错觉,他觉得他更新了一行实际却没有,这可糟透了。

在这个例子中,无论系统繁忙与否,一个任务就在那无所事事的等待锁的释放。它依赖了系统资源利用率以外的一些随机性因素。这就是为何你不能使用「M/M/m」模型来对其进行建模。这也是为何在一个单元测试环境下的性能测试结果不足以用来决策是否应该在生产环境添加一些新的代码。

19. 性能测试

咱们谈到的队列延迟、相关性延迟引起了一个很困难的问题。你如何对一个新的应用进行足够的测试,让你信心满满的认为它不为由于性能问题而对你的生产程序形成破坏。你能够去建模,也能够去测试。可是,在你真正遭遇这些问题以前,为全部你能够预见的生产问题去创建模型和测试是极其困难的。

所以,一些人看到了这样窘境,所以申辩说那么就干脆别测试了。千万别被这样的心态所困扰。下面的观点是很肯定的:

  • 在程序进入生产环境以前,若是你尝试去发现一些问题你确定会比那些彻底不去作的找到更多的问题。

  • 在预发布的测试中,你不可能发现全部的问题,因此你须要一些可靠并有效的方法来解决这些在预发布测试中漏掉的问题。

在彻底不测试和完整的生产环境模拟测试之间,存在一个适度测试量的平衡点。 固然对于一家飞机制造商来讲,适度测试量确定多于一家销售棒球帽的公司。但千万别彻底跳过性能测试。至少,当你在生产环境遭遇不可避免的性能问题时,一份性能测试计划将使你成为一名更称职的诊断专家(更清晰的思考者)。

20. 测量

人们能感知到的就是吞吐量和响应时间。吞吐量很容易测量,相对来讲测量响应时间要稍微困难些。(还记得吧,吞吐量和响应时间可不是简单的倒数关系)用个秒表来计时终端用户的行为并不困难,但你不会从中获得你真正想要的关于为何响应时间如此之大的细节。

不幸的是,人们老是测量他们容易测量的,而不是他们应当测量的。 当咱们须要测量的东西不容易测量时,咱们就把注意力转移到那些容易获得测量数据上了,这是个错误。那些并非你真正须要的测量,但看起来彷佛和你真正须要的有些相关又容易去执行的测量,咱们称之为「替代指标」。 一些「替代指标」例子包括像子程序调用计数和子程序执行耗时的采样数据。对于「替代指标」,很遗憾在个人母语中没有更好的语句来表达个人想法,但有一个你们都熟悉的现表明达方式:__替代指标真是恶心。(Surrogate measures suck.)__

不幸的是,「恶心」在这里并不表示它没用。要是替代指标真的没用就行了,那就没人会使用它们了。问题就在于替代指标有时是有用的,这让使用替代指标的人相信它们老是有用的,但实际并非这样。

替代指标有两个严重的问题:

  • 它们可能在系统不正常时告诉你系统一切正常,这在统计学上叫作第一型错误,假阳性。

  • 它们也可能在系统正常时告诉你系统出问题了,这在统计学上叫作第二型错误,假阴性。

这两类错误浪费了人们许多的时间。

当你去评测一个真实系统方方面面,你可否取得成功在于你能从那个系统中得到多少有效的测量数据。我曾有幸在 Oracle 的市场部门工做过,那时许多软件供应商围绕着咱们积极的参与,这才使得正确的测量系统成为可能。让软件开发者使用 Oracle 提供的工具是另一回事了,至少咱们的产品中具有这样的能力(记录有效的测量数据)。

21. 性能是一项功能特性

性能是软件程序的一项功能特性,就像你在 Bug 跟踪系统中很方便的将「Case 1234」这样一个字符串自动连接到编号 1234 的 Bug 案例上。__性能像全部其余软件功能同样,不是凑巧获得的,你须要去设计和构建它。要想得到好的性能,你不得不去仔细的思考、研究、学习,写出额外的代码来测试和支持它。__

尽管如此,像全部其余功能特性同样,在项目初期你还在调研、设计和编写代码时你不可能知道性能到底会怎样。对大多数应用(多是绝大多数,这个说法可能有争议)而言性能都是未知的,直到它们投入实际使用阶段。那么留给你的问题就是:__由于在上线前你不可能知道你的应用性能表现到底怎样,所以你须要在编写应用时考虑怎样很容易的在生产环境修复性能问题。__

正如大卫·加文(David Garvin)告诉咱们的,容易测量的东西也更容易管理(来自《创建一个学习型组织》1993年发表于《哈佛商业评论》) 那么要写一个在生产环境容易修复问题的应用程序,首先要作的就是要容易在生产环境进行测量。大多数时候,当我提到生产环境的性能测量时人们就会陷入一种焦虑状态,他们很担忧性能测量带来的入侵效应。他们马上对采集哪些数据作出了妥协,只留下那些「替代指标」(更容易采集的)在数据采集表上,拥有额外数据采集代码的软件会变的比没有这些代码的更慢么?

我喜欢汤姆·凯特(Tom Kyte)之前对这个问题的回答。他估计额外的性能测量代码给 Oracle 带来不超过 10% 性能损失。他接着向那些气恼的提问者做出解释,正是由于从这些性能测量代码获取的数据让 Oracle 公司进一步将产品性能改进提高了不止 10%,这超出了性能测量代码自己引起的开销。

我认为不少软件供应商他们一般花费了太多时间来优化他们的性能测量代码路径使其更高效,而不是首先搞清楚怎么让这些代码有效果。 高德钠(Donald Knuth)曾在 1974 说过的一句话印证了这个观点:

过早优化是一切罪恶的根源。

软件设计者将性能测量代码整合进他们的产品中更有可能建立一个高性能的应用,更重要的是这个应用会不断变的更快。

尾声:关于「拐点」的公开辩论

在本文的 14 到 16 节,我描述了「拐点」的性能曲线、它们的相关性和应用。可是,在 20 年前有一场关因而否值得定义一个「拐点」概念的公开辩论。

历史上的一个重要的观点认为我所描述的「拐点」并非真正有意义的。在 1988 年,斯蒂芬·萨姆森(Stephen Samson)争论说至少在「M/M/1」的排队系统的性能曲线中并不存在「拐点」。 他写道:“选择一个具有指导意义的数字并不容易,经验法则仍是最适用的,在大多数状况下都不存在拐点,不管你多么但愿找到一个。”

1999 年,温水煮青蛙的故事启发了我。这个故事是这样的说的,当你把一只青蛙扔进煮沸的开水中,它会马上跳出来。但假如你先把它放在冷水中并慢慢的加热水温,青蛙会安静的呆在水里直到被煮熟了。对于资源使用率,它就像是沸水,有一个清晰的「死亡区间」。在这个区间值内,对于随机到达的请求你的系统将不堪重负。那么「死亡区间」的边界在哪里?若是你尝试用程序来自动管理资源使用率,你就必须知道这个值。

最近,个人朋友尼尔·冈瑟(Neil Gunther)跟我有一场私下的辩论。首先,他认为「死亡区间」这个术语使用在这里是彻底错误的,由于在函数连续性的前提下使用「死亡区间」是错误的。 其次,他认为对于「M/M/1」系统的「拐点」在 0.5 是过于浪费了,你应当更多的利用好系统资源,它应高于 0.5 的资源利用率。最后,他认为你对使用率的明肯定义取决于实际的平均响应时间相对你能忍受的平均响应时间实际超出了多少。所以,冈瑟认为任何有用的使用率阈值的定义都来源于询问人们自身的偏好,而非来自于数学。(图A)

从「图B」中,咱们能够看出这个说法的问题所在。 假设,你对平均响应时间的忍耐限度是 T,那么对应的最大资源利用率是 ρT。你会看到在 ρT 附近资源利用率一个微小的变化都会致使响应时间巨大的波动。

我相信如我在第 4 节所写的,__客户感觉到的是差别变化,而非平均。__ 或许他们会说咱们可以接受平均响应时间达到 T,但我不相信人们能忍受由于系统平均负载发生了 1% 的变化致使平均响应时间达到 1 分钟,换句话说就是平均响应时间翻了 10 倍。我确实了解我在 14 节列出的「拐点」列表比不少人直觉上感觉到地安全值更低一些,特别是对「低阶」的系统如「M/M/1」而言。 尽管如此,但我相信避免由于资源使用率的微小变化引起响应时间的过大波动,这是极其重要的。

话虽如此,我也不知道该如何确切的定义「过大」这个词。像响应时间波动的忍耐度,不一样的人有不一样的底线。或许有一个起伏忍耐的因子适用于全部人。例如,Apdex Standard Application Performance Index 假设了响应时间 FT 的 4 倍就会让人们的态度从「满意」变为「煎熬」。

正如我在 16 节中描述的,「拐点」不管你怎么去定义或称呼它,对于容量规划过程来讲都是一个十分重要的参数。而且我相信它对平常的系统负载管理也是一个重要参数,我会继续保持研究。

关于做者

Cary Millsap 是一家致力于软件性能优化公司 Method R 的创始人和 CEO,是一位在 Oracle 全球社区著名的演讲者、教育者、顾问和做者。曾和 Jeff Holt 合著 Optimizing Oracle Performance 一书,更多详细信息参见做者 LinkedIn 的介绍和我的博客

参考

  1. CMG (Computer Measurement Group, a network of professionals who study these problems very, very seriously); http://www.cmg.org.

  2. Eight-second rule; http://en.wikipedia.org/wiki/...

  3. Garvin, D. 1993. Building a learning organization. Harvard Business Review (July).

  4. General Electric Company. What is Six Sigma? The roadmap to customer impact. http://www.ge.com/sixsigma/Si...

  5. Gunther, N. 1993. Universal Law of Computational Scalability; http://en.wikipedia.org/wiki/...

  6. Knuth, D. 1974. Structured programming with Go To statements. ACM Computing Surveys 6(4): 268.

  7. Kyte, T. 2009. A couple of links and an advert...; http://tkyte.blogspot.com/200...

  8. Millsap, C. 2009. My whole system is slow. Now what? http://carymillsap.blogspot.c...

  9. Millsap, C. 2009. On the importance of diagnosing before resolving. http://carymillsap.blogspot.c...

  10. Millsap, C. 2009. Performance optimization with Global Entry. Or not? http://carymillsap.blogspot.c...

  11. Millsap, C., Holt, J. 2003. Optimizing Oracle Performance. Sebastopol, CA: O'Reilly.

  12. Oak Table Network; http://www.oaktable.net.


写点文字,画点画儿。
微信公众号「瞬息之间」,碰见了不妨就关注看看。

相关文章
相关标签/搜索