2.3 概念
下面是系统性能的一些重要概念,这些概念将贯穿本章的剩余内容以及本书始终。此处会用概括的方式进行讲解,详细的内容会出现在架构和后续各章的分析部分。
2.3.1 延时
对于某些环境,延时是唯一关注的性能焦点。而对于其他环境,它会是除了吞吐量以外,数一数二的分析要点。
图2.3 网络连接延时
延时是操作执行之前所花的等待时间。在这个例子里,所说的操作是网络服务的传输数据请求。在这个操作发生之前,系统必须等待网络连接建立,这即是这个操作的延时。响应时间包括了延时和操作时间。
因为延时可以在不同点测量,所以通常我们会指明延时测量的对象。例如,网站的载入时间由三个从不同点测得的不同时间组成:DNS 延时、TCP 连接延时和TCP 数据传输时间。DNS延时指的是整个DNS 操作的时间。TCP 连接延时仅仅指的是初始化(TCP 握手)。
在一个更高的层级,所有这些,包括TCP 数据传输时间,会被当作另外一种延时。例如,从用户点击网站链接起到网页完全载入都可以当作延时,其中包括了浏览器渲染生成网页的时间。
由于延时是一个时间上的指标,因此可能有多种计算方法。性能问题可以用延时来进行量化和评级,因为延时都用的是相同的单位来表达的(时间)。通过考量所能减少或移除的延时,预计的加速也可以计算出来。这两者都不能用IOPS 指标很准确地描述出来。
时间的量级和缩写列在了表2.1 中,作为参考。
如果可能,其他的指标也会转化为延时或者时间,这样就可以进行比较。如果你必须在100个网络I/O 和50 个磁盘I/O 之间做出选择,你怎样才能知道哪个性能更好?这是一个复杂的选择,因为包含了很多因素:网络跳数、网络丢包率和重传、I/O 的大小、随机或顺序的I/O、磁盘类型,等等。但是如果你比较的是100ms 的网络I/O 延时和50ms 的磁盘I/O 延时,差别就很明显了!
表2.1 时间单位
2.3.2 时间量级
我们可以用数字来作为时间的比较方法,同时可以用时间的长短经验来判断延时的源头。系统各组件的操作所处的时间量级差别巨大,大到了难以体会的地步。表2.2 提供的延时示例,从访问3.3GHz 的CPU 寄存器的延时开始,阐释了我们所打交道的时间量级的差别,表中是发生单次操作的时间均值,等比放大成为想象的系统,一次寄存器访问0.3ns(十亿分之一秒的三分之一)相当于现实生活中的1 秒。
表2.2 系统的各种延时
正如你所见,CPU 周期的时间是很微小的。这段时间光只能走0.5 米。很可能你眼睛到这页书的距离,光大约走了1.7ns。这段时间里,现代的CPU 已经执行了5 个CPU 循环,处理了若干个指令。
关于CPU 循环和延时的更多信息,参见第6章、第9章。因特网延时的内容在第10章中有更多的示例。
2.3.3 权衡三角
你应该知道某些性能权衡三角关系。图2.4 展示的是好/快/便宜“择其二”的权衡三角,旁边是对应于IT 项目的术语。
图2.4 权衡三角:择其二
许多的IT 项目选择了及时和便宜,留下了性能在以后的路上解决。当早期的决定阻碍了性能提高的可能性,这样的选择会变得有问题,例如,选择了非最优的存储架构,或者使用的编程语言或操作系统缺乏完善的性能分析工具。
一个常见的性能调整的权衡是在CPU 与内存之间,因为内存能用于缓存数据结果,降低CPU 的使用。在有着充足CPU 资源的现代系统里,交换可以反向进行:CPU 可以压缩数据来降低内存的使用。
与权衡相伴而来的通常的是可调参数。下面是一些例子。
● 文件系统记录尺寸(或块的大小):小的记录尺寸,接近应用程序I/O 大小,随机I/O工作负载会有更好的性能,程序运行的时候能更充分地利用文件系统的缓存。选择大的记录尺寸能提高流的工作负载性能,包括文件系统的备份。
● 网络缓存尺寸:小的网络缓存尺寸会减小每一个连接的内存开销,有利于系统扩展,大的尺寸能提高网络的吞吐量。
当对系统做调整的时候,寻找这类权衡。
2.3.4 调整的影响
性能调整发生在越靠近工作执行的地方效果最显著。对于工作负载驱动的应用程序,所执行的工作就是应用程序本身。表2.3 展示了一个软件栈的例子,说明了性能调整的各种可能。
应用程序层级的调整,可能通过消去或减少数据库查询获得很大的性能提升(例如,20 倍)。在存储设备层级做调整,也可以精简或提高存储I/O,但是性能提升的大头是在更高层级的系统栈代码,所以存储设备层级的调整能够达到的应用程序性能提升有限,是百分比量级(例如,20%)。
表2.3 调整示例
在应用程序层级寻求性能的巨大提升,还有一个理由。如今许多环境都致力于特性和功能的快速部署,因此,应用程序的开发和测试倾向于关注正确性,留给性能测量和优化的时间很少甚至没有。之后当性能成为问题时,才会去做这些与性能相关的事情。
虽然发生在应用程序层级的调整效果最显著,但这个层级不一定是观测效果最显著的层级。数据库查询缓慢要从其所花费的CPU 时间、文件系统和所执行的磁盘I/O 方面来考察最好。使用操作系统工具,这些都是可以观测到的。
在许多环境里(尤其是云计算),应用程序承受的是快速部署,每周或每天都有软件变更发生在生产环境。大的性能增长点(包括修复回归问题)和软件变化一样频繁地被发现。在这些系统里,操作系统的调整以及从操作系统层面观测问题这两点都容易被忽视。谨记一点操作系统的性能分析能辨别出来的不仅是操作系统层级的问题,还有应用程序层级的问题,在某些情况下,甚至要比应用程序视角还简单。
2.3.5 合适的层级
不同的公司和环境对于性能有着不同的需求。你可能加入过这样的公司,其分析标准要比你之前所见过的深得多,或者甚至可能听都没听过。或者是这样的公司,你觉得很基本的分析被认为很高端甚至从未使用过(这是好消息:事情简单轻松!)
这并不意味着某些公司做的是对的,某些做的是错的。这取决于性能技术投入的投资回报率(ROI)。拥有大型数据中心或者云环境的公司可能需要一个性能工程师团队来分析所有事情,包括内核内部和CPU 性能计数器,并且经常使用动态跟踪技术(dynamic tracing)。这些中心或公司也会正式建立性能模型对将来的增长做预测。小的创业公司可能只能做到浅层次的检查,用第三方的监控方案来检测性能和提供报警。
最极端的环境,像股票交易所和高交易率的电商,性能和延时对它们很关键,值得投入大量的人力和财力。举一个例子,一条新的横跨大西洋的连接纽约交易所和伦敦交易所的光缆正在规划之中,花费预计3 亿美金,用以减少6ms 的传输延时[1]。
2.3.6 性能建议的时间点
环境的性能特性会随着时间改变,更多的用户、新的硬件、升级的软件或固件都是变化的因素。一个环境,受限于速度1Gb/s 网络基础设施,当升级到10Gb/s 时,很可能会发现磁盘或CPU 的性能变得紧张。
性能推荐,尤其是可调整的参数值,仅仅在一段特定时间内有效。一周内从性能专家那里得到的好建议,可能到了下一周,经过一次软件或硬件升级,或者用户增多后就无效了。
通过网上搜索找到的可调参数值对于某些情况能快速见效。如果它们对于你的系统或者工作负载并不合适,它们也会损害性能,可能合适过一次,就不合适了,或者只是作为软件的某个bug 修复升级之前暂时的应急措施。这和从别人的医药箱里拿药吃很像,那些药可能不适合你,或者可能已经过期了,或者只适合短期服用。
如果仅仅是出于要了解有哪些参数可调以及哪些参数在过去是需要调整的,那么浏览这些性能推荐是有用的。针对你的系统和工作负载,这项工作就变成了考虑这些参数是不是要调,以及调整成什么值。如果其他人不需要调整那个值,或者调整了但并未将经验分享出来,那么你是可能漏掉重要参数的。
2.3.7 负载vs.架构
应用程序性能差可能是因为软件配置和硬件的问题,也就是它的架构问题。另外,应用程序性能差还可能是由于有太多负载,而导致了排队和长延时。负载和架构见图2.5。
如果对于结构的分析只是显示工作任务的排队,处理任务没有任何问题,那么问题就可能出在施加的负载太多。在云计算环境里,这是需要引入更多的节点来处理任务的征兆。
举个例子,架构的问题可能是一个单线程的应用程序在单个CPU 上忙碌,从而致使请求排队的情况,但是其他的CPU 却是可用且空闲的。在这个例子里,性能就被应用程序的单一线程架构限制住了。
图2.5 负载vs.架构
负载的问题可能会是一个多线程程序在所有的CPU 上都忙碌,但是请求依然排队的情况。在这个例子里,性能可能限制于CPU 的性能,或者说是,负载超出了CPU 所能处理的范围。
2.3.8 扩展性
负载增加下的系统所展现的性能成为扩展性。图2.6 是一个典型的系统负载增加下的吞吐量变化曲线。
图2.6 吞吐量vs.负载
在一定阶段,可以观察到扩展性是线性变化的。随着到达某一点,用虚线标记,此处对于资源的争夺开始影响性能。这一点可以认为是拐点,作为两条曲线的分界。过了这一点后,吞吐量曲线随着资源争夺加剧偏离了线性扩展,内聚性导致完成的工作变少而且吞吐量也减小了。
这个情况可能发生在组件达到100%使用率的时候——饱和点。也可能发生在组件接近100%使用率的时候,这时排队频繁且比较明显。
一个可以用于说明这种情况的系统示例是执行大量计算的应用程序,更多的负载由线程承担。当CPU 接近100%使用率时,由于CPU 调度延时增加,性能开始下降。在性能达到峰值后,在100%使用率时,吞吐量已经开始随着更多线程的增加而下降,导致更多的上下文切换,这会消耗CPU 资源,实际完成的任务会变少。
如果把X 轴的“负载”替换成资源(诸如CPU),你会看见一样的曲线。关于这一点更多的内容见2.6 节。
性能的非线性变化,用平均响应时间或者是延时来表示,参见图2.7[Cockcroft 95]。
图2.7 性能下降
当然,过长的响应时间不是好事情。发生性能“快速”下降可能是由于内存的负载,就是当系统开始换页(或者使用swap)来补充内存的时候。发生性能“慢速”下降则可能是由于CPU 的负载。
还有一个“快速”性能下降的情况是磁盘I/O。随着负载(和磁盘使用率)的增加,I/O 可能会排队。空闲的旋转的磁盘可能I/O 服务响应时间约1ms,但是当负载增加,响应时间会变成10ms。这种情况在2.6.5 节中有建模,在M/D/1 和60%使用率的情况下。
如果资源不可用,应用程序开始返回错误,响应时间是直线变化的,而不是将工作任务排队。举个例子,Web 服务器很可能会返回503“Service Unavailable”而不是添加请求到排队队列中,这样服务的请求能用始终如一的响应时间来执行。
2.3.9 已知的未知
我们在前言中介绍过,已知的已知、已知的未知、未知的未知在性能领域是很重要的概念。下面是详细的解释,并有系统性能分析例子。
● 已知的已知:有些东西你知道。你知道你应该检查性能指标,你也知道它的当前值。举个例子,你知道你应该检查CPU 使用率,而且你也知道当前均值是10%。
● 已知的未知:有些东西你知道你不知道。你知道你可以检查一个指标或者判断一个子系统是否存在,但是你还没去做。举个例子,你知道你能用profiling 检查是什么致使CPU 忙碌,但你还没去做这件事。
● 未知的未知:有些东西你不知道你不知道。举个例子,你可能不知道设备中断可以消耗大量CPU 资源,因此你对此并不做检查。
性能这块领域是“你知道的越多,你不知道的也就越多”。这和学习系统是一样的原理:你了解的越多,你就能意识到未知的未知就越多,然后这些未知的未知会变成你可以去查看的已知的未知。
2.3.10 指标
性能指标是由系统、应用程序,或者其他工具产生的度量感兴趣活动的统计数据。性能指标用于性能分析和监控,由命令行提供数据或者由可视化工具提供图表。
常见的系统性能指标如下。
● IOPS:每秒的I/O 操作数。
● 吞吐量:每秒数据量或操作量。
● 使用率
● 延时
吞吐量的使用取决于上下文环境。数据库吞吐量通常用来度量每秒查询或请求的数目(操作量)。网络吞吐量度量的是每秒比特或字节数目(数据量)。
IOPS 度量的是吞吐量,但只针对I/O 操作(读取和写入)。再次重申,上下文很关键,上下文不同,定义可能会有不同。
开销
性能指标不是免费的,在某些时候,会消耗一些CPU 周期来收集和保存指标信息。这就是开销,对目标的性能会有负面影响。这种影响被称为观察者效应(observer effect)。(这通常与海森堡测不准原理混淆,后者描述的是对于互为共轭的物理量所能测量出的精度是有限的,诸如位置和动量。)
问题
我们总是倾向于假定软件商提供的指标是经过仔细挑选,没有bug,并且有很好的可见性的。但事实上,指标可能会是混淆的、复杂的、不可靠的、不精确的,甚至是错的(由bug 所致)。有时在某一软件版本上对的指标,由于没有得到及时更新,而无法反映新的代码和代码路径。
关于指标更多的问题,参见第4章内容。
2.3.11 使用率
术语“使用率”经常用于操作系统描述设备的使用情况,诸如CPU 和磁盘设备。使用率是基于时间的,或者是基于容量的。
基于时间
基于时间的使用率是使用排队理论做正式定义的。例如[Gunther 97]:
服务器或资源繁忙时间的均值
相应的比例公式是:
此处U 是使用率,B 是T 时间内系统的繁忙时间,T 是观测周期。
操作系统性能工具得到的“使用率”也是这个。磁盘监控工具iostat(1)调用的指标%b,即忙碌百分比,这个术语更好地诠释了指标B/T 的本质。
这个使用率指标告诉我们组件的忙绿程度:当一个组件达到100%使用率,资源发生竞争时性能会有严重的下降。这时可以检查其他的指标以确认该组件是不是已经成为了系统的瓶颈。
某些组件能够并行地为操作提供服务。对于这些组件,在100%使用率的情况下,性能可能不会下降得太大,因为它们能接受更多的工作。要理解这一点,可以大楼电梯为例思考一下,当电梯在楼层间移动时,它是被使用的,当它闲置等待的时候,它是不用的。然而,即便当它在100%忙碌的时候,即达到100%使用率的时候,它依然还是能够接受更多的乘客的。
100%忙碌的磁盘也能够接受并处理更多的工作,例如,通过把写入的数据放入磁盘内部的缓存中,稍后再完成写入,就能做到这点。通常存储序列运行在100%的使用率是因为其中的某些磁盘在100%忙碌时,序列中依然有足够的空闲磁盘来接受更多的工作。
基于容量
使用率的另一个定义是在容量规划中由IT 专业人员使用的[Wong 97]:
系统或组件(例如硬盘)都能够提供一定数量的吞吐。不论性能处于何种级别,系统或组件都工作在其容量的某一比例上。这个比例就称为使用率。
这是用容量而不是时间来定义使用率的。这意味着100%使用率的磁盘不能接受更多的工作。若用时间定义,100%的使用率只是指时间上100%的忙碌。
100%忙碌不意味着100%的容量使用。
回到电梯的例子,100%容量意味着电梯满载,装不下更多的乘客了。
在理想的世界里,我们应该对设备做两种使用率的测试,这样做,你就能知道何时磁盘100%忙碌,性能开始下降,也能知道何时达到100%的容量,磁盘无法再接受更多的工作。不幸的是,这通常是不可能的。对于磁盘而言,这不仅需要了解主板的磁盘控制器的行为,还要对磁盘的容量使用有预测。目前,磁盘并不提供这一信息。
本书中,使用率通常指的是基于时间的定义。基于容量的定义会用于某些基于容量的指标,诸如内存使用。
非空闲时间
定义使用率的问题是我们公司开发一个云监控项目时碰到的。首席工程师Dave Pacheco 问我如何定义使用率,我做了上述的回答。由于对可能造成的混淆不满,他创造了一个新的术语来让事情不言自明:非空闲时间。
虽然这更加精确,但是现在用得还并不普遍(参照对比之前说的指标,忙碌百分比)。
2.3.12 饱和度
随着工作量增加而对资源的请求超过资源所能处理的程度叫做饱和度。饱和度发生在100%使用率时(基于容量),这时多出的工作无法处理,开始排队。图2.8 描绘了这种情况。
图2.8 使用率vs.饱和度
随着负载的持续上升,图中的饱和度在超过基于容量的使用率100%的标记后线性增长。因为时间花在了等待(延时)上,所以任何程度的饱和度都是性能问题。对于基于时间的使用率(忙碌百分比),排队和饱和度可能不发生在100%使用率时,这取决于资源处理任务的并行能力。
2.3.13 剖析
剖析(profiling)本意指对目标对象绘图以用于研究和理解。在计算机性能领域,profiling通常是按照特定的时间间隔对系统的状态进行采样,然后对这些样本进行研究。
不像之前讲过的指标(包括IOPS 和吞吐量),采样所能提供的对系统活动的观察比较粗糙,当然这也取决于采样率的大小。
举一个profiling 的例子,对CPU 程序计数器采样,以一定频率间隔进行栈回溯跟踪来收集消耗CPU 资源的代码路径的统计数据,通过这样的方式我们才能了解CPU 的使用。这个专题可以参见第6章。
2.3.14 缓存
缓存被频繁使用来提高性能。缓存是将较慢的存储层的结果存放在较快的存储层中。把磁盘的块缓存在主内存(RAM)中就是一例。
一般使用的都是多层缓存。CPU 通常利用多层的硬件作为主缓存(L1、L2 和L3),开始是一个非常快但是很小的缓存(L1),后续的L2 和L3 就逐渐增加了缓存容量和访问延时。这是一个在密度和延时之间经济上的权衡。缓存的级数和大小的选择按CPU 芯片内可用空间为准以达到最优的性能。
在系统中还有不少其他的缓存,许多都是利用主内存做存储软件实现的。参见第3章的3.2.11 节,那里有一张系统缓存的列表。
一个了解缓存性能的重要指标是每个缓存的命中率——所需数据在缓存中找到的次数(hits,命中)相比于没有找到的次数(misses,失效)。
命中率 = 命中次数/(命中次数 + 失效次数)
命中率越高越好,更高的命中率意味着更多的数据能成功地从较快的介质中访问获得。图2.9 表示的是随缓存命中率上升,性能预期的提升曲线。
图2.9 缓存命中率和性能
98%和99%之间的性能差异要比10%和11%之间的性能差异大很多。由于缓存命中和失效之间的速度差异(两个存储层级),导致了这是一条非线性曲线。两个存储层级速度差异越大,曲线倾斜越陡峭。
了解缓存性能的另一个指标是缓存的失效率,指的是每秒钟缓存失效的次数。这与每次缓存失效对性能的影响是成比例(线性)关系的,比较容易理解。
举个例子,工作负载A 和B 执行相同的任务,但用的是不同的算法,都用内存作为缓存以避免直接从磁盘读取数据。工作负载A 的命中率为90%,工作负载B 的命中率是80%。单独分析这个信息会觉得工作负载A 执行得更好。但如果A 的失效率是200/s,B 的失效率是20/s 呢?这样看的话,B 执行的磁盘读取次数比A 少10 倍,这会使得B 完成任务的时间远远少于A。可以确定的是,工作负载总的运行时间可以用以下公式计算:
运行时间 =(命中率×命中延时)+(失效率×失效延时)
这里用的是平均命中延时和平均失效延时,并且假定工作是串行发生的。
算法
缓存管理算法和策略决定了在有限的缓存空间内存放哪些数据。
“最近最常使用算法”(MRU)指的是一种缓存保留策略,决定什么样的数据会保留在缓存里:最近使用次数最多的数据。“最近最少使用算法”(LRU)指的是一种回收策略,当需要更多缓存空间的时候,决定什么数据需要移出缓存。此外,还有“最常使用算法”(MFU)和“最不常使用算法”(LFU)。
你可能碰到过“不常使用算法”(NFU),这个是LRU 的一个花费不高但吞吐量稍小的版本。
缓存的热、冷和温
下面这些词通常用来表达缓存的状态。
● 冷:冷缓存是空的,或者填充的是无用的数据。冷缓存的命中率为0(或者接近0,当它开始变暖的时候)。
● 热:热缓存填充的都是常用的数据,并有着很高的命中率,例如,超过99%。
● 温:温缓存指的是填充了有用的数据,但是命中率还没达到预想的高度。
● 热度:缓存的热度指的是缓存的热度或冷度。提高缓存热度的目的就是提高缓存的命中率。
当缓存初始化后,开始是冷的,过一段时间后逐渐变暖。如果缓存较大或者下一级的存储较慢(或者两者皆有),会需要一段较长的时间来填充缓存使其变暖。
例如,我工作过的一个存储服务器,有128GB 的DRAM 作为文件系统的缓存,600GB 的闪存作为二级缓存,物理磁盘作为存储。在随机读取的工作负载下,磁盘每秒的读操作约有2000次。按照8KB 的I/O 大小,这意味着缓存的变暖速度仅有16MB/s(2000 x 8KB)。两级缓存从冷开始,需要2 小时来让DRAM 缓存变得温起来,需要超过10 小时来让闪存缓存变暖。