系统的思考性能问题
本文翻译自《Thinking Methodically about Performance》
作者
Brendan Gregg, JOYENT
日期
2012/12/10
摘要
提出了一种针对性能问题的 USE 方法,它指出了其他通常使用的方法学的缺陷。
性能问题可能是复杂和神秘的,几乎没有什么线索来找到它的根源。 由于缺少一个切入点(或者说缺少方法学来提供一个切入点)性能问题常常被随机的分析:猜猜问题可能在哪里,然后调整一些东西直到问题消失。 这种方式可能也是有效的(假如你猜的正确的话),同时它也非常耗时,混乱而且可能最终会忽略掉一些特定的问题。 本文讲述了一些系统性能问题和今天用于分析这些问题的方法学,并提出了一种逼近和解决一类问题的新方法学。
因为在一个典型系统中的组件和交互众多,所以系统性能问题分析是很复杂的。 系统环境可能由数据库、web服务器、负载均衡器和自有应用程序组成,所有这些都运行在操作系统之上(裸机或者虚拟机)。 这还仅仅是软件。硬件和固件,包括外部存储系统和网络基础设施,增加了更多的组件到系统环境中,其中任何一个都可能是潜在的问题根源。 这些组件中的任何一个都需要其特定领域的专业知识,一个公司可能没有员工能对这个环境中的所有组件都很了解。
性能问题也可能在组件间进行复杂的交互时出现,而这些组件单独工作时却一切正常。 解决这类问题需要多领域的专家一起工作。
在这种环境下复杂性的一个例子是,Joyent公司一个云计算用户碰到的一个神秘的性能问题:这个问题似乎是内存泄漏,但却不知道发生在什么位置。 在实验室各组件相互隔离的环境下,这个问题并不能重现。 生产环境包括操作系统和相应的系统库,用户基于node.js的应用代码和一个Riak数据库运行在Erlang的虚拟机上。 找到问题的根源需要对客户的代码,node.js,Riak,Erlang和操作系统知识都有所了解,其中任何一个组件都是由一个或多个不同的工程师完成的。 最终一个操作系统领域专业工程师找到了问题根源,是出在系统库上。
另外一个复杂的因子是,“好”或“坏”的性能判断可能是主观的。 对一个用户不可接受的延迟,对另一个用户来说可能就可以接受。 没有一种方法可以很清晰的认定性能问题,不仅很难知道问题是否出现,而且更难的是问题什么时候被修复。 测量性能问题的能力(例如:基于响应时间),让不同的问题可以被量化并按重要性排序。
性能分析方法学能提供一些有效的方法来分析系统或组件并找到问题的根源,而这并不需要很深入的专业领域知识。 方法学也能提供多种方式来认定和量化问题,让这些问题被理解并排序。
性能相关文献提供了多种方法学用于不同的工作活动,例如容量规划、基准测试和系统建模。 但是,这些用于找到性能问题根源的各种方法学并不通用。 例如,在《Solaris Performance and Tools》书中介绍的一种“深入分析”(drill-down)法,它描述了一个三阶段的过程,从高层次的问题表象症状到问题根源的分析。 通过传授操作系统内部原理和工具,一些性能相关文献通常已经包含了采用最近的调优技巧的特定检查表来进行分析的方法。 这让一些性能分析师能开发出他们自己的方法学,虽然这可能会耗费可观的时间去完成。
特定的性能检查表很流行。 例如,《Sun Performance and Tuning》一书就包含了“通用性能调优快速指南”,它列出了11项很容易遵循的标准调优技巧,用于尝试发现磁盘瓶颈,网络文件系统,内存和CPU的问题。 技术支持组员工通常使用这些列表来对所有事项进行一致的检查,包括了大部分极端的问题。 但是这种方法会引起一些问题。 对列表中的一些特定事项的可观测性是有限的,而且它们通常是特定时间点的建议,会随着时间过期需要及时更新。 而且检查表项多集中于那些有已知的修复方式,可以很容易地记录的问题,例如一些可调整的参数设置而非基于源码或环境的自定义修补程序。
接下来的章节总结了一些其他系统性能分析的方法学,包括 USE 方法,它将被详细的解释。 我们从描述两个常用的“反方法”学开始(“责怪他人反方法”和“路灯反方法”),它们用作和其他方法学进行一个比较。
反方法学
第一个反方法,“责怪他人”,遵循一系列简单的步骤:
1. 找一个不属于你负责的系统或环境组件。
2. 猜测问题就出在那个组件上。
3. 把问题转给负责该组件的团队。
4. 当证明猜测错误时,回到第1步。
例如:“可能是网络的问题。你能让网络组检查下网络是否在丢包么?”
这种方法不是在检查性能问题,而是在给其他人制造问题,这浪费了其他团队的资源。 这是缺乏数据分析(甚至是以数据为出发点)的猜测。 请问如屏幕截图所示,哪些工具在运行以及它们的输出如何解释? 这可以作为请教他人时的补充参考。
使用工具收集数据好过胡乱的猜测,但这并不足以进行有效的性能分析,“路灯反方法”展示了这一点。 这不是一个深思熟虑的方法。 用户在互联网上或随机的选择一些他们熟悉的观测工具来分析性能,看看是否有什么明显的发现。 这种随意的方式可能忽略了许多类型的问题。
找到正确的工具是需要花时间。 熟悉的工具能够第一时间运行起来,尽管它们并不是最适合的。 这是一种观察偏倚,又称“路灯效应”,来自一则寓言:
一个警察看见一个醉汉在路灯下寻找什么东西,然后问他到底在找什么呢。 醉汉说他丢了钥匙。 警察于是和他一起找但也找不到,于是问他是否是在路灯下丢的。 醉汉回答:“不是,但只有这里的光线最好。”
在性能方面类似的情形是用TOP命令查看,并不是因为它有用,而是因为用户不能理解其他工具的输出。
学习很多的工具会有帮助,但仍然是一种受限的方法。 特定的系统组件或资源或许会因为缺乏观察和测量工具而被忽略。 进一步来说,用户不会知道它们的视图是不完整的,那就没法识别出那些“未知的未知问题(unknown unknowns)”。
已有的性能分析方法学
更好的性能分析方法学是存在的,它们能在你运行任何工具之前解决问题。 这些方法包括,问题陈述法(problem statement),负载界定法(workload characterization)和深入(drill-down)分析法。
1. 问题陈述法
问题陈述法通常被技术支持组员工使用来收集关于一个问题的信息,它已被改进适用于性能分析。 针对性能问题它应当是第一个被尝试使用的方法学。
问题陈述的目的是收集关于问题更详细的描述以指导更深入的分析。 问题陈述本身甚至可能就把问题解决了。
典型的通过问如下一些问题进入一个问题反馈系统的处理:
- 什么让你认为这是一个性能问题?
- 系统之前表现正常么?
- 最近改变了什么?(软件?硬件?负载?)
- 性能退化可以使用延迟或运行时间来表述么?
- 这个问题影响了其他人或其他应用么(或者仅仅是你一个人)?
- 环境是怎样的?使用的是什么样的软件和硬件?版本多少?配置?
这些问题可以根据环境来定制。 虽然问题看起来没什么特别的,但答案就能解决一类问题,而不需要深入的方法学。 当这时还解决不了时,我们才需要其他的方法学,包括负载界定法和深入分析法。
2. 负载界定法
可以通过回答如下问题来进行负载界定:
- 谁引起的负载?进程ID,用户ID,远程IP地址多少?
- 为何负载会被引发?代码路径是?
- 负载的其他特征是什么?每秒IO次数(IOPS),吞吐量,类型?
- 负载是如何随时间变化的?
通过问题鉴别,这些有助于从架构问题中分离出负载问题。 最好的性能来源于减少不必要的工作。 有时性能瓶颈是由于应用程序故障引发的(例如,线程陷入循环)或者错误的配置(白天进行的系统备份)。 通过重新配置和维护,这些不必要的工作可以被清除。 界定负载可以识别这一类问题。
3. 深入分析法
深入分析包括软件和硬件层的剥离并找到问题的核心,从高层次视角到更深层次的细节中。 这些更深层次的视角包括检查内核,例如采样分析内核堆栈跟踪,或者动态跟踪内核函数的执行。 《Solaris Performance and Tools》一书提供了系统性能深入分析方法学。 它包含三个步骤:
- 监控:在多个系统间连续记录一些高层次的统计信息,当问题发生时可以识别或报警。
- 鉴别:假设一个系统存在一个可疑的问题,这缩小了使用系统工具对特定资源或相关领域的调查范围,并鉴别出可能的瓶颈。
- 分析:这个阶段进一步对特定系统领域进行检查,鉴定问题根源并量化问题。
分析阶段可能又遵从其自身的深入分析方法,从应用软件栈开始,深入到系统库,系统调用,内核深处,设备驱动和硬件。
深入分析法通常回准确的定位问题的根源,但它也非常耗时,而一旦深入到错误的方向,那就会浪费大量的时间。
需要一种新方法学
我最近在 Joyent 公有云上分析了一个数据库性能问题,有一个问题反馈单包含了前面章节所述的问题陈述。 问题陈述中提示了存在真正的问题需要深入分析。
问题是间断性出现的,一些数据库查询会耗时数秒才能完成。 客户抱怨网络有问题,猜测查询的延迟是因为网络丢包。 这不是胡乱的猜测,因为问题单中包含了 ping 命令的输出,显示了偶尔会出现的高延迟。 ping 是一个很常见并为大家熟悉的工具,在没有其他证据支持的情况下,这就像是一个路灯反方法的例子。
技术支持组使用各种工具对网络进行了细致的检查,包括检查 TCP/IP 栈网络计数器,也没有发现任何问题。 这类分析十分耗时,因为有几十种统计指标,其中一些很难解释,必须要随着时间变化来检查其相关性。 登入系统后,支持人员也依据他们特定的常见问题检查表检查了 CPU 利用率以及云强加的一些限制。 他们的结论是在观察期间没有任何问题,网络和CPU都很正常。
此时,大部分的系统组件和成千上万的系统指标还未被检查,因为它们被认为与问题不相关。 缺乏方向指引,在客户的云环境上检查系统的方方面面需要耗费数天。 分析到目前为止还没有发现任何一个真正问题的证据,真令人失望。
下一步就是去动态跟踪原先报告的问题(网络丢包),寄希望于发现一些标准网络计数器错过的问题。 我使用 DTrace 工具反复进行深入到 TCP/IP 栈的分析。 这比标准网络可视化工具集提供了更多的细节,包括内核丢弃包的检查和 TCP的内部状态。 尽管如此,这也花费了数小时来捕获这些间歇性的问题。 我开始考虑深入分析数据库查询的延迟,万一问题并不在网络上,或者随着时间界定数据库的负载情况,万一这个问题是因为瞬间的负载峰值引发,但是这些方法也都十分耗时。
在开始进一步深入分析之前,我想执行一个对系统所有组件的快速检查,而不仅仅是网络或CPU,寻找瓶颈或错误。 为了尽快完成,对每一个系统我只需要检查有限的统计指标,而不是全部(数千种)。 出于完整考虑,需要检查所有的组件,包括那些因为没有默认的可视化工具或统计指标而可能遗漏的。
USE 方法提供一种方式可以达到这个目标。 它快速发现了数据库系统因为内存耗尽导致系统换页,而磁盘间歇性的运行在饱和状态下。 早期聚焦在解决网络问题,意味着这些领域在团队的分析中被忽视了。 真正的问题出现在系统内存和磁盘上,它们能被更快检查和解读。
在我教授操作系统性能这门课时,我发明了 USE 方法。 目标是帮助学生找到常见的问题,并确保他们不会忽略一些重要的领域。 我成功的在企业和云环境下使用了多次,但它不能解决所有的问题,可以作为你工具箱中的一种方法学。
USE 方法
USE(Utilization-利用率, Saturation-饱和度, Errors-错误)方法应在性能分析的早期被使用,在问题称述法之后它能快速的定位系统瓶颈。 该方法概括如下:
检查每一种资源的利用率、饱和度和错误。
这里提到的资源指的是所有需被独立检查的物理服务器的功能组件(例如:CPU,磁盘、总线等)。 一些软件资源也能使用同样的方法学进行检查以提供有意义的测量指标。
利用率是指在特定的时间段内,资源处于忙碌状态所占时间的百分比。 即使资源处于繁忙状态,依然可以接受更多的工作。 到达那种程度它不能再接受更多的工作可以通过饱和度来识别。 额外的工作任务通常在队列中等待。
但对一些资源类型,包括内存,利用率都是指其容量的使用情况。 这区别于基于时间的定义。 一旦某容量资源达到了100%的利用率,那么它就不能再接受更多的工作了,要么将这些工作放入队列中等待(饱和度)或者返回错误,其中任何一种都可以被USE方法鉴别出来。
错误,在USE的语义中指错误事件的次数。 错误是需要调查的因为它们会导致性能退化,当处于可恢复的错误模式下,错误可能不会一出现就被注意到。 这种情况包括一些失败重试操作,一些冗余设备池中的故障设备。
和“路灯反方法”相比较,USE方法遍历所有的系统资源,而不是以某种工具作为开始。 这创建了一个完整的问题列表,然后再去寻找工具来回答它们。 即使不能找到工具来回答这些问题,这些不能解答的问题的知识对性能分析也是很有用的:它们现在称作“已知的未知问题”。
USE方法指引分析一些关键的指标,所以所有的系统资源可以被尽可能快的检查。 在这之后,如果依然没找到问题,那么你可以再尝试其他的方法学。
1. 描述指标
USE方法使用的关键指标如下所述:
- 利用率,用随时间段的百分比来描述(例如:一个CPU运行在90%的利用率)
- 饱和度,用等待队列长度来描述(例如:CPU平均运行队列长度为4)
- 错误,用报告的错误数来描述(例如:网卡已经产生了50次延迟冲突)
描述测量的时间区间也是很重要的。 这似乎有些反直觉,一个短暂的高利用率脉冲也可能引发饱和度和性能问题,即使从一个较长的时间段看整体利用率不高。 一些监控工具平均5分钟报告一次利用率。例如,CPU利用率可能每秒都在发生剧烈的变化,平均5分钟的报告可能隐藏了短期的100%利用率导致的饱和情形。
2. 资源列表
USE方法的第一步是创建一个资源列表。尽可能的完整。下面是一些通用的关于服务器硬件资源的列表:
- CPUs:套接字,核心,硬件线程(虚拟CPU)。
- 主存:DRAM(动态随机存储器)。
- 网卡:以太网端口。
- 存储设备:磁盘。
- 控制器:存储控制器,网络控制器。
- 连接器:CPU,内存,I/O。
通常来说,每一个组件都属于唯一一种资源类型。例如,主存属于容量资源,网卡属于I/O资源,它能通过IOPS(每秒I/O操作)或吞吐量进行测量。 但有些组件能表现出多种资源类型,例如,存储设备既是I/O资源也是容量资源. 考虑到所有类型的资源都可能引起性能瓶颈。另外,I/O资源会在“队列系统”中进一步研究,它先将请求放入队列然后再处理。
有一些物理组件可以从你的检查表中去除,如硬件缓存(例如:MMU-Memory ManagementUnit,TLB-Translation Look-aside Buffer-/TSB-Translation Storage Buffer,CPU Level-1/2/3)。 当资源处于高利用率或饱和度引发瓶颈导致的性能退化,使用USE方法是最有效的。而缓存在高利用率下会提升性能。
在USE方法实施之后,你可以检查缓存的命中率和其他一些性能相关属性(在系统上的瓶颈被排除了之后)。 假如你不确定是否包含某个资源到检查表中时,那么先把它放上去,然后看看你的测量工作在实践中表现如何。
3. 功能模块图
另一种遍历资源的方式是找到或画出系统的功能模块图。这种类型的图会展示系统组件间的关系,这对于在数据的流动中寻找瓶颈很有帮助。 图1展示了一个双CPU插槽的功能模块图:
确定各种总线利用率的同时,在图中标注出每个的最大带宽。最终绘制出的图可能在采取任何测量之前就能精确指出系统瓶颈。(在硬件产品设计中这也是很有用的练习,那时你还有时间去改变物理上的组件)。
CPU、内存和I/O连接器常常被忽略。 幸运的是,它们通常也不是系统瓶颈的原因。 不幸的是,当它们成为系统瓶颈时,问题就变的棘手了(也许你可以升级主板或减少负载,例如:”零拷贝“技术有助于降低内存总线负载)。 至少USE方法,将这种组件间的连接器纳入了考虑(参考《Analyzing the HyperTransport》一书中的例子,它用这种方式找到了一个连接器的问题)。
4. 指标
一旦你有了资源列表,需要考虑你需要的每种资源的指标类型(利用率、饱和度和错误)。 表1列出了一些资源和指标类型的例子,和一些可能的指标(基于通用的Unix/Linux)。 这些指标要么可以用每时间段的平均值或计数来描述。
重复所有的组合,包括获取每个指标的命令。留意那些当前无法获得的指标:这些属于“已知的未知问题”。 最终你会得到大约30个指标的列表,其中一些测量起来很困难,还有一些完全没法测量。 示例检查表是基于Linux和Solaris系统的。
幸运的是,大部分常见的问题都可以通过一些简单的指标被发现(例如,CPU饱和度,内存饱和度,网卡利用率,磁盘利用率),所以首先检查这些指标。
5. 更难的指标
列表2给出了一些更困难的指标组合。其中一些指标无法通过标准操作系统工具去获得。我常编写自己的软件去获取这些指标,使用静态或动态(DTrace)跟踪或者CPU性能计数器。
6. 软件资源
有些软件资源也可以进行类似的检查。这通常使用在软件中的一些小组件上,而非整个应用。例如:
- 互斥锁:利用率可能被定义为锁被持有的时间,饱和度是在队列中等待锁的线程数。
- 线程池:利用率可能被定义为线程处于繁忙工作的时间,饱和度是等待线程池服务的请求数。
- 进程/线程容量:系统拥有的进程或线程数是有限的,当前使用量可被定义为利用率;待分配数标示了饱和度;当分配失败则为错误(例如:不能fork)。
-文件描述符容量:和上面类似。
如果这些指标有效,那么使用它们。否则软件问题的定位就留给其他方法学吧。
7. 建议的解释
USE方法识别使用哪些指标。在你学会了从操作系统中解读它们,下一个任务是解释它们的当前值。 对一些指标来说,解释是很明显的(完备的文档)。另外一些则不那么明显了,它们可能依赖负载要求或一些假设。 下面是针对指标类型一些通用建议:
- 利用率:100%的利用率通常是到达瓶颈的一种标识(检查饱和度和它的影响以确定)。高利用率(例如:大于60%)成为一个问题有两个原因。首先,当利用率的测量是基于相对比较长的周期(数秒或分钟),对总利用率来说,60%可能隐藏了一些短期100%利用率飙升。其次,一些系统资源,例如硬盘通常在一个操作过程中不能被打断,即使是更高优先级的工作。相较而言,CPU几乎能在任何时候被中断(抢占)。一旦磁盘利用率超过60%,队列延迟就变得更加频繁和明显,因为队列变的更长了。这可以通过排队理论建模响应时间和利用率的关系来量化(例如,磁盘符合 M/M/1 模型)。
- 饱和度:任意程度的饱和都是一个问题(非0)。这可以通过等待队列的长度或者在队列中等待的时间来测量。
- 错误:非0的错误计数值得仔细调查,特别是当性能变差时这些错误计数还在增长时。
那么相反的例子就很容易解释了:低利用率、未饱和、无错误。这比听上去的更有用。缩小调查的范围帮助你更快的聚焦在问题领域。
8. 云计算
在云环境下,软件资源需要合适的控制以供十个租户共享一个系统。在Joyent,我们主要使用操作系统虚拟化(基于Smart-OS的SmartMachine),它对CPU、内存和存储I/O都强加了限制。每一项资源的限制都可以通过USE方法来进行检查,和检查物理资源类似。
例如,内存容量利用率是所有租户的内存使用量与总内存容量之比。内存容量饱和度可以通过匿名换页活动来观察,尽管传统的UNIX页面扫描可能还处于空闲中。
9. 策略
图2展示了USE方法的流程图。首先检查错误,因为它通常比利用率和饱和度更容易解释,更省时间。
USE方法识别出的问题可能是系统的瓶颈。不幸的是,一个系统可能不止一个性能问题,你找到的第一个问题可能是一个问题,但不一定是你认为的那个问题。 如果有必要,你可以使用其他方法学逐个进一步调查每个问题,然后再回到USE方法上去遍历更多资源。 或者你可能发现先完成USE方法的检查表把所有问题找到,然后再根据可能的优先级去深入调查会更有效率。
进一步分析的方法学包括之前总结的“负载界定法”和“深入分析法”。 在完成这些之后(假如有需要的话),你应该有证据确定正确的修正行为是调整负载还是调整资源本身。
其他方法学
前面那些方法学可能解决大部分服务器问题,基于延迟的方法学(例如,Method R)几乎能够找到所有的问题。 当然,假如你对软件内部机制不熟悉,这些方法学会花费大量的时间,它们可能更适合于DBA或者熟知软件的应用开发者。
总结
系统性能分析很复杂,问题可能出在任意的组件,包括组件之间的交互上。 今天普遍使用的方法学有些像是猜测:尝试熟悉的工具或者在没有可靠的证据下提出假设。
USE方法指出了其他通常使用的方法学的缺陷,它是一种执行完整的系统健康检查的简单策略。 它考虑了所有的资源以避免忽略问题,它只使用有限的指标所以可以很快的执行。 这在分布式环境下,包括云环境下,是特别重要的,在这类环境下许多的系统需要被检查。 但是,这种方法学仅能发现特定类型的问题—瓶颈和错误,所以它应当被认为是更大方法学工具集中的一种。
参考
略......
致谢
略......
关于作者
Brendan Gregg是Joyent公司首席性能工程师,他在软件栈的任意层面上进行性能和可扩展性分析。
他也是《DTrace》(Prentice Hall, 2011出版)一书的主要作者以及《Solaris Performance and Tools》(Prentice Hall, 2006年出版)一书的合著者,另还写了很多关于系统性能方面的文章。
之前他是SUN公司首席性能和内核工程师,在那里他开发了ZFS L2ARC。
他还开发了许多性能分析工具,其中一些默认随Mac OS X 和 Oracle Solaris 11发布。
他最近的工作包括为illumos和Linux内核提供性能可视化分析。