结合“性能监视器” 排查、处理性能瓶颈导致应用吞吐率等指标上不去的问题
双11备战前夕,总绕不过性能压测环节,TPS 一直上不去 / 不达标,除了代码上的问题外,服务器环境、配置、网络、磁盘、CPU 亦是导致性能瓶颈的重要一环,本文旨在分享最近项目性能压测过程中的排查经验,文中的表单你可以作为排查手册保存,如有不对之处,还请在评论区分享、交流你的经验和观点:)
通过本文,你可以了解和掌握:
-
了解常见的系统瓶颈的可能原因。
-
通过性能探查器定位性能瓶颈。
-
几点关于性能优化的策略。
-
一份关于 windows 性能监视器的部分计数器翻译及对应的经验结论。
吞吐量 和 延时的关系
关于吞吐量/吞吐率、延时,你可以通过 Jmeter中的”聚合报告“和”用表格查看报告“来获取。
-
Throughput 越大,Latency 越差:因为请求过多,系统繁忙导致响应速度降低。
-
Latency 的值越小说明能支持的 Throughput 越高:Latency 数值小说明系统处理速度快,自然便可以处理更多的请求。
-
Throughput “不用” 通过降低 latency 的方式来提高,排查性能问题的时候,勿在降低 Latency 值上消耗过多时间。
常见系统瓶颈:
-
类型转换:除了装箱拆箱外,还要着重看下 JSON 的一些转换类库,如 newtown,fastJson 等等,可能会引起 CPU 维持在高位。
-
异步操作:有些异步操作会非常影响性能,尤其是在网络较差的情况下,很可能阻塞业务。
- 如异步下的状态通知通常会影响性能。通常而言,异步操作会让”吞吐率“提升,但会牺牲 延时(latency)。
定位性能瓶颈
定位的方式不一定是程序级别的,一开始可以先从操作系统的 CPU 使用率,内存使用率,系统 IO 和 网络 IO,网络连接数 着手分析。
-
CPU 使用率不高,但是 throughtput 和 latency 上不去: 说明程序没有忙于计算,可能问题在 I/O 上。
- 一般 CPU 和 IO 是反着来的: CPU 没问题,问题可能在 IO,反之亦然。
-
如果 CPU、IO、内存、网络带宽使用都不高,但是系统性能上不去: 说明程序有问题,可能是为资源被锁,存在锁竞争关系,程序被阻塞;或者是在上下文切换等等。
-
关于 IO,要看 3 个方面:磁盘IO,网络IO 以及 内存换页率。
-
程序级别的性能瓶颈定位:
- 分段注释代码 / 让一些函数空转 / 做一些硬编码的 Mock,然后再测试下 Throughput 和 latency,看是否有好转,如果有,说明函数是瓶颈,再进一步在这个函数体内注释代码,直到找到最耗性能的语句。
-
分析内存:需要用到的计数器:Memory 类别 和 Physical Disk 类别的计数器,步骤如下:
- 查看 Memory:Available Mbytes 指标:如果该指标的数据较小,系统可能出现了内存方面的问题,需要继续下面步骤进一步分析。
- 注意 Memory:Pages/sec、Pages Read/sec 和 Page Faults/sec 的值:操作系统会利用磁盘较好的方式提高系统可用内存量或者提高内存的使用效率。这 3 个指标直接反映了 OS 进行磁盘交换的频度。
- Pages/sec 值 持续高于几百,可能内存有问题。Pages/sec 值大不一定就表明内存有问题,可能是运行使用内存映射文件的应用导致。
- Page Faults/sec 越高说明每秒发生页面次数越多,说明 OS 向内存读取的次数越多。此时需要查看 Pages Read/sec 的计数值,该值阈值是 5,超过 5,则可以判断存在内存方面的问题。
- 根据 Physical Disk 计数器的值分析性能瓶颈:需要分析 Page Reads/sec 和 %Disk Time 及 Average Disk Queue Length 的分析。如果 Pages Read/sec 很低,同时 %Disk Time 和 Average Disk Queue Length 的值很高,则可能有磁盘瓶颈。但是,如果队列长度增加的同时 Pages Read/sec 并未降低,则是内存不足
-
分析处理器:
- 排查 System:%Total Processor Time 计数器的数值:该值体现的是服务器 CPU 的整体利用率,对于多核系统而言,该值体现的是所有 CPU 的平均利用率。
- 如果该值持续超过 90%,说明整个系统面临着处理器方面的瓶颈,需要增加处理器来提高性能。
- P.S.:多核下,如果该数据不大,但是各个 CPU 的 负载不均衡,也可以认为是 CPU 产生了瓶颈。
- 排查每个 CPU 的 Processor:%Processor Time 和 %User Time 和 %Privileged Time:
- %Processor Time 很高时,一般 CPU 都阻塞着,但是反之并不亦然。
- %User Time:非系统内核操作消耗的 CPU 时间(如调用系统本身资源–网络、IO等),若该值较大,可以考虑优化代码、优化算法;如果该服务器是数据库 Server,则该值较大的话可能是数据库的”排序“或是”函数操作“消耗了过多的 CPU 时间,此时可考虑对 DB 进行优化。
- %Privileged Time:系统内核操作消耗的 CPU 时间
- 验证是否系统 CPU 瓶颈:
- 查看 System:Processor Queue Length 计数器:如果该值大于 CPU 数量的总数 + 1 的时候,说明产生了处理器阻塞。
- 排查 System:%Total Processor Time 计数器的数值:该值体现的是服务器 CPU 的整体利用率,对于多核系统而言,该值体现的是所有 CPU 的平均利用率。
-
分析磁盘I/O:
- 如果计算得出每个磁盘的I/O 超过了磁盘本身的I/O能力,则可以确认磁盘是引起瓶颈的因素之一。
- 与 Processor:%Privileged Time 联合分析:如果 Physical Disk:%Disk Time 较大,其他值比较适中,则硬盘可能是瓶颈,若几个值都比较大,且持续超过 80%,则可能是内存泄漏。
- 分析 Disk sec/Transfer:一般来说,该值小于 15ms 为最佳,15~30ms 为良好,30~60ms 为可接受,超过 60ms 则需要考虑更换硬盘或者更换 raid 方式了。
-
分析进程:
- 查看 Process:%Processor Time的值:每个进程的该值反映的是进程消耗 CPU 的时间。
- 查看 Process:%Page Failures/sec 和 Memory:%Page Failures/sec 的比值,过滤出是哪个进程产生的最多的页错误,一般这个进程是需要大量内存的进程,或者是非常活跃的进程(即在压测情况下,就是你要压测的进程)
- Process:%Private Bytes:该计数器指进程所占有的私有数据(单位字节),即无法与其他进程共享的数据量,可以利用该值来判断应用是否存在内存泄漏。
- 对于 IIS 进程,可以重点监控下 INetInfo进程的 Private Bytes,如果在压测过程中,该值不断增加,或是在压测结束后,该值仍然处于一个高水平,则说明应用存在内存泄漏
-
分析网络:
- Network Interface:Bytes Total/sec 为发送和接收字节的速率,可以通过该计数器值来判断网络链接速度是否是瓶颈,具体操作方法是用该计数器的值和目前网络的带宽进行比较。
- 联合 Processor:%Privileged Time 进行分析:如果 Physical Disk:%Disk Time比较大,其他值比较适中,则硬盘可能是瓶颈,若几个值都比较大,且持续超过 80%,则可能存在内存泄漏。
性能优化的几个策略
-
应用层面:
- 善用 CDN,缓存,冗余数据,SLB。
- 如果瓶颈在网络传输,那么需要对传输数据进行压缩(需要注意,压缩算法是很耗时的,只在瓶颈是网络传输的时候再考虑,你需要根据测试数据自行权衡。)。
- 并行处理的时候需要注意下宿主机是否是多核。如果宿主机是单核的,而程序代码是多进程、多线程的,那么对于高计算密集型的应用会适得其反,反而更慢。
-
优化代码:
- 减少循环层数、减少递归。
- 在循环体中少做声明变量、分配 / 释放内存的操作:把循环体内的表达式抽离到循环体外。
- 注意函数调用在栈上的开销。
- 合理使用 try-catch:不要用抛异常作为常规业务的失败流程(如进行业务报错)。
- 字符串处理需注意:减少不必要的声明实例(.net core 出了一个 Span 类型,可以用来替代 Substring。)
- 不同的语言和代码库,对于复杂度是不一样的,这个需要注意:如应该用 List.Count==0 来代替List.Any() 来判断是否有数据。
- 关于这点,你可以使用计数器来判断、测试自己写的代码在”耗时、Cpu Cycle,0/1/2代 GC回收“等数据的差异,择优而定。
-
算法调优:
- 哈希算法并不高效,使用时候还需注意。
- 善用预处理和分量分次分批处理:像月报表之类的执行频率低,但每次执行都很耗资源的,你可以尝试预先每天/每周处理,不用等到每月才执行。
-
多线程调优:
- 多线程的瓶颈主要在互斥和同步锁上,以及线程上下文切换的成本上:你应尽量少用甚至不用锁,或者用乐观锁替代现有直接用 Lock 的锁。
-
内存分配:当内存出现碎片时,会相当耗时。
- 在编码的时候,意识上尽可能少的进行内存的分配。
-
池化技术对于一些短作业来说相当有效:如 HttpClientFactory 就是用了 http 池,可以用来减少对象创建、线程创建的开销。
-
网络调优:
- TCP 很耗资源,对系统开销很大:你可以搜索关键字:TCP Tuning 进行相关调优
- TCP 和 HTTP 要配置下 Keep-Alive,尤其是像 http 这样的短连接,这也可以在一定程度上防止 DDoS攻击。
- 对于 TCP 的 TIME_WAIT,这个状态默认会持续 4 分钟(持续 2 个 MSL–Max Segment Lifetime),TIME_WAIT 状态下的资源不能回收,有大量 TIME_WAIT 连接的情况一般是在 HTTP 服务器上。
- 你可以在注册表中新建、设置 TCP 的 TcpTimedWaitDelay 和 MaxUserPort 项,来增加 TCP 连接释放时间和临时端口数。
- TCP 一旦发生丢包,TCP 的带宽使用率会受到影响(盲目减半),再丢包,再减半;什么时候不丢包了,就会逐步恢复。
-
CPU 调优:
- CPU0 很关键, 它一般担任着调节功能(如内核和非内核操作,上下文切换等),如果 0 号 CPU 被用得过狠的话,别的 CPU 性能也会下降。
- windows 下可在“任务管理器”中,右键“进程”选择“设置相关性”来设置该进程可以运行在哪些核上。
- linux:使用 taskset 命令来设置(可以通过安装 schedutils 来安装这个命令) 。
- windows 下可在“任务管理器”中,右键“进程”选择“设置相关性”来设置该进程可以运行在哪些核上。
- CPU0 很关键, 它一般担任着调节功能(如内核和非内核操作,上下文切换等),如果 0 号 CPU 被用得过狠的话,别的 CPU 性能也会下降。
性能监视器
在服务器上最直观监视性能的方式就是直接使用系统自带的”性能监视器“。
1 | >perfmon #直接在 "运行" 中输入 perfmon 即可打开 |
windows 下计数器说明:
类别 | 计数器名称 | 描述 | 结论 |
---|---|---|---|
Memory | Available M bytes | 当前空闲物理内存。 | 当这个数值变小时,说明 windows 开始频繁地调用磁盘页面文件,如果这个数值很小(如小于 5Mb,系统会将大部分时间消耗在操作页面文件上),一般要保留 10% 的可用内存,此值过小可能是内存不足或者内存泄漏。 |
Pages/sec | 是 Pages Input/sec 和 Pages Output/sec 总和。 | Pages/sec 推荐 0-20,如果服务器没有足够的内存处理其工作符合,此值数值将会一直很高,如果大于 80 ,表示有问题(太多的读写数据要访问磁盘,可考虑增加内存或优化读写数据的算法),该系列的值比较低,说明请求响应比较快,否则可能是服务器内存短缺引起(也可能是缓存太大,导致系统内存太少。)一般如果Pages/sec 持续高于几百,那么应该进一步研究页交换活动。有可能需要增加内存,以减少换页的需求。Pages/sec 的值很大不一定表明内存有问题,而可能是运行使用内存映射文件的程序所致。计数器的比率高表示分页过多。 | |
Page Faults/sec | 指处理器处理所有页面错误的总速度。 当处理器向内存指定位置请求一页(可能是数据,也可能是代码)出现错误时,这就构成了一个“页错误”。如果该页在内存的其他位置,该错误就被称为软错误(用 Transition Fault/sec衡量);如果该页必须从硬盘上重新读取时,被称为硬错误。 | 许多处理器可以在有大量软错误的情况下继续操作,而硬错误会导致明显的拖延。当进程使用的数据所处的内存页不在内存中时,就会产生该值。如果某页已经在主内存中,或者它正被共享此页的其他进程使用,那么就不会从磁盘调入该页。 | |
Page Reads/sec | 读取磁盘,为了解决硬页面错误而需要执行磁盘读操作的总数。 | 其阈值为 5,该值越低越好(越低,说明响应时间越短);该值大表示磁盘读,而非缓存读。 如果 Page Reads/sec 持续保持为 5,表示可能内存不足。 | |
Pages Input/sec | 指单位时间内为了解决硬页面错误而从磁盘读入的页面总数 | 如果硬页面错误数量比较大,就需要增加内存或者减少服务器中缓存的大小,IIS 使用的内存由 MaxCachedFileSize 注册表项控制,默认值为 256Kb。 | |
Cache Bytes | 分配在RAM中的驻留页面数。 | 默认情况下为 50% 的可用内存。 | |
Committed Bytes | 指以字节表示的确认虚拟内存,是磁盘页面文件上保留空间的物理内存。 | 不超过物理内存的 75% 。 | |
Process | %Processor Time | 处理器消耗的处理器时间,如果专用于某种特定应用(如数据库服务器和应用服务器),则可用应用相关进程 %Process Time 进行衡量。 | 可接受的上限一般不超过 85% 。 |
Page Faults/sec | 将进程产生的页故障与系统产生的相比较,以判断该进程对系统页故障产生的影响。 | ||
Working Set | 表示进程正在使用的物理内存的量。(至于是具体进程还是所有进程,需要看监控实例是具体的还是所有的。) | 系统在工作集中的内存页进行寻址的时候,不会引发 Page Fault。另外,如果服务器有足够的空闲内存,页就会留在工作集中,而当空闲内存少于一个特定的阈值时,页就会被清除出工作集中。 | |
Private Bytes | 此进程所分配的无法与其他进程共享的当前字节数量。如果系统性能随着时间而降低,则此计数器可以是内存泄漏的最佳指示器。 | ||
Processor | %Processor Time | 指处理器执行非闲置线程时间的百分比。此计数器可以作为处理器活动的主要指示器。(%Processor Time = 100% - Idle Process时间比例) | 如果该值持续超过95%,表明瓶颈是 CPU,可以考虑增加或更换更快的处理器。正常情况下,保持在 80%±5% 比较好,过低说明 CPU 利用率不高,过高表示是瓶颈是 CPU。虽然该计数器高不一定是坏事,但如果其他处理器相关的计数器(如 Privileged Time 或者 Processor Queue Length)线性增加的话,高 CPU 使用率就值得调查了。,一般地,如果该值持续大于75%,但是磁盘IO和网络的吞吐比率很低,就需要升级/增加处理器了。 |
%User Time | 非内核操作耗费的CPU时间。一般来说,如果系统中使用了大量的算法或者复杂的计算操作,该值就会比较大。 | ||
%Privileged Time | 这个计数器表示一个线程在特权模式下所使用的时间比例,当你的程序调用操作系统的方法(如文件操作,I/O 或者分配内存)时,这些操作系统的方法就是在特权模式下运行的。 | 如果数值持续大于 75% 就表示存在瓶颈。 | |
%DPC Time | CPU 消耗在网络处理上的时间。 | 该值越小越好。如果持续高 %DPC 时间,则可能存在 CPU 瓶颈或应用程序或硬件相关问题。 | |
%Interrupt Time | 表示 CPU 接收、处理硬件中断所使用的时间比例。 | 阈值取决于处理器。一般,当该值 >15% 的时候说明可能存在硬件问题。 这个值间接指出产生中断的硬件设备活动,比如网络变化。这个计数器显著增加的话表示硬件可能存在问题 | |
Interrupts/sec | 中断率,表示每秒设备中断 CPU 的次数,可以产生中断的装置包括:系统定时器,鼠标,数据通讯联网,网络卡以及其他外部设备等。中断操作在后台完成。 | 该值阈值取决于处理器,但越低越好,不宜超过 1000,如果该值显著增加而系统活动没有相应的增加,则表明存在硬件问题,需要检查引起中断的网络适配器、磁盘或其他硬件。 | |
Physical Disk | %Disk Time | 指所选磁盘驱动器忙于读/写入请求所用的时间百分比。 | 正常值<10,此值过大表示耗费太多时间来访问磁盘,可考虑增加内存、更换更快的硬盘、优化读写数据的算法。若数值持续超过 80(此时处理器和网络并没有饱和),则可能是内存泄漏。 |
Current Disk Queue Length | 是在收集性能数据时磁盘上当前的请求数量。它还包括在收集时处于服务的请求。这是瞬态的快照,不是时间间隔的平均值。此计数器会反映暂时的高或低的队列长度,但是如果磁盘驱动器被迫持续运行,它有可能一直处于高的状态。 | 请求的延迟与此队列的长度减去磁盘的轴数成正比。为了提高性能,此差应该平均小于 2。 | |
Average Disk Queue Length | 指读取和写入请求的平均数。该值不应超过磁盘数的 1.5~2倍。要提高性能,可增加磁盘。注意,一个Raid Disk 实际有多个磁盘。 | 正常值应小于 5,此值持续过大表示磁盘 IO 太慢,要更换更快的硬盘。建议结合 Pages /sec 一起分析,看是内存分页过多导致磁盘一直在读写还是就是磁盘问题。 | |
Average Disk Read/Write Queue Length | 指读取/写入请求(队列)的平均数。 | ||
DiskRead(Writes)/sec | 物理磁盘上每秒磁盘读、写的次数。 | 两者相加,应该小于磁盘设备最大容量。 | |
Average Disk sec/Read | 指以秒计算的在磁盘上读取数据所需的平均时间。 | ||
Average Disk sec/Write | 指以秒计算的在磁盘上写入数据所需的平均时间。 | ||
Network Interface | Bytes Total/sec | 为发送和接受字节的速率,包括帧字符在内。判断网络连接速度是否是瓶颈,可以用该计数器的值和目前网络的带宽比较。 | 建议不要超过带宽的 50% 。 |
System | %Total Processor Time | 系统上所有处理器都忙于执行非空闲线程的平均时间的百分比,该值反映了用于有用作业上的时间的比率。对单处理器系统来说,该值很容易理解;对多处理器来上,该值体现了所有处理器的平均繁忙程度。eg:如果所有处理器都繁忙,此值为 100%,如果有一半的处理器繁忙,另一半处理器完全空闲,此值为 50%。 | |
File Data Operation/sec | 计算机对文件系统设备执行读取和写入操作的速率。本计数器的计数不包括文件控制文件。 | ||
Processor Queue Length | 等待执行的线程的数目,该计数器显示的是等待中的线程数量,不包括正在运行的线程数量。 | 在 CPU 利用率 80~90% 的系统中,该值应为 “[1,3] * 处理器数量”:如在一台 8 核处理器,该值在 [8, 24] 区间范围内算正常;而在 CPU 利用率较低的系统上,该值应为 [0,1],若持续大于 2,就有可能碰到了问题资源,需要进一步排查。 | |
Call/sec | 指运行在计算机上的所有处理器调用操作系统服务例行程序的综合速率,这些例行程序执行所有在计算机上的如安排和同步活动等基本的程序,并提供对非图形设备、内存管理和名称空间管理的访问。 | 该值跟 Processor.Interrupts/sec 联合使用,如果 Processor.Interrupts/sec 大于 Call/sec,则说明系统中某一硬件产生了过多的终端。 | |
Context Switches/sec | 进程切换率,指计算机上的所有处理器全部从一个线程切换到另一个线程的综合速率。产生上下文的可能情况:当正在运行的线程自动放弃处理器时出现上下文切换;一个有更高优先级的线程取代一个正在运行的低优先级线程的时候会发生上下文切换;在用户模式和内核模式之间切换时产生上下文切换。 | 一般,该值小于 5000/秒/CPU 是不需要担心的。如果Context 该值达到 15000/秒/CPU 的话就是一个制约因素了,需要看下是否代码导致(如过多的异步操作)。P.S.:上下文切换同样会发生在许多线程拥有相同优先级的情况,如果 CPU 使用率不高且 Context Swtich 非常低,那么可能线程被堵塞。 | |
Web Service | Current Connections | 当前连接数(针对到 IIS 实例)。 | 结合压测用户/线程数进行分析。 |
Current Anonymous Users | 当前匿名连接数。 | 结合压测用户/线程数进行分析。 | |
Current NonAnonymous Users | 当前非匿名用户/匿名连接数。 | 结合压测用户/线程数进行分析。 | |
Get/Put/Post Requests/sec | 使用Get/Put/Post 方式 HTTP 请求的速率。 | ||
ASP.NET | Requests Executing | 正在被 ASP.NET 执行的请求数 | 随着连接数的增加,这个值也增加的话,说明执行中的线程是正常的,没有被阻塞。 |
Request Queued | 等候处理的请求数。 | 你可以用这个计数器来判断排队的请求数(应用程序池队列中的请求数量)。间接可以用这个来评估吞吐能力。 | |
ASP.NET vX(X=版本) | Request Wait Time | 最近的请求在队列中等待的毫秒数。 | 越低越好。 |
Request Queued | 等待处理的请求个数。 | 理想情况应为0,可以用此值来预估 IIS 应用程序池队列长度。 | |
Request Rejected | 因资源不足而无法执行请求的总数。 | 理想情况应为 0。该计数器表示返回503状态的请求的总数。 | |
Asp.Net Application | Requests/Sec | 每秒执行的请求数。 | 你可以用这个来判断 QPS |
.NET CLR Memory | %Time in GC | 可用该计数器来确定自己的应用程序在 GC 上消耗的时间 | 根据自身的项目经验:如果 GC 占用了应用程序执行时间的 50%,说明应用本身可优化的空间就比较大,前期可在程序代码上做优化调整;相反,如果应用程序几分钟才执行一次回收,你就需要从其他方面来进行优化了,我个人是更偏向于硬件+配置 |
W3SVC_W3WP | Requests/sec | 该计数器一般需要跟QPS或者TPS保持一致 | 该数值正常的话,说明请求已经正常到达 w3wp进程 |
HTTP Service Request Queues | Arrival Rate | 请求抵达率(抵达HTTP.sys) | 用这个计数器可以监视底层 HTTP.sys 的指标,你可以用这个来判断到达 IIS 的当前要处理的并发请求。 |
.NET CLR Exceptions | # of Exceps Thrown/sec | 每秒产生的异常数 | 包含常规异常和未捕获的异常,包含托管和非托管代码的异常。如果这个值大于100s的话,说明不正常,需处理。 |
参考
Processor queue length:https://social.msdn.microsoft.com/Forums/vstudio/en-US/356b87a3-e8b1-48ad-9355-e68ce3eef754/processor-queue-length?forum=vstest
Interrupt Time 说明:https://docs.microsoft.com/en-us/previous-versions/technet-magazine/cc718984(v=msdn.10)
性能计数器:http://www.appadmintools.com/documents/windows-performance-counters-explained/
《IIS 7.5 开发与管理完全参考手册》:Ken schaefer、Jeff Cochran
《.NET 性能优化》:Sasha Goldstein,Dima Zurbalew,Ido Flatow