性能优化——性能分析

 从公众号转载,关注微信公众号掌握更多技术动态

---------------------------------------------------------------

一、性能分析简介

    在完成性能测试之后,需要输出一份性能测试报告,分析系统性能测试的情况。其中测试结果需要包含测试接口的平均、最大和最小吞吐量,响应时间,服务器的 CPU、内存、I/O、网络 IO 使用率,JVM 的 GC 频率等。

    通过观察这些调优标准,可以发现性能瓶颈,我们再通过自下而上的方式分析查找问题。首先从操作系统层面,查看系统的 CPU、内存、I/O、网络的使用率是否存在异常,再通过命令查找异常日志,最后通过分析日志,找到导致瓶颈的原因;还可以从 Java 应用的 JVM 层面,查看 JVM 的垃圾回收频率以及内存分配情况是否存在异常,分析日志,找到导致瓶颈的原因。

    如果系统和 JVM 层面都没有出现异常情况,我们可以查看应用服务业务层是否存在性能瓶颈,例如 Java 编程的问题、读写数据瓶颈等等。

1.性能指标

    经常需要对某个性能指标做预测。这种情况就需要研究数据的趋势(Trend)。根据情况做些预测分析,比如根据这个指标的历史数据来进行曲线拟合,经常使用的方法是进行线性回归分析.线性回归是通过拟合自变量与因变量之间最佳线性关系,来预测目标变量的方法。线性回归往往可以预测未来的数据点。比如根据过去几年的每月消费支出数据,来预测明年的每月支出是多少。

    对一个系统而言,如果吞吐率很低,响应时间往往会非常稳定。当吞吐率增高时,响应时间一般会快速增加。

(1)响应时间

性能 = 1/ 响应时间

    响应时间指的就是执行一个程序,到底需要花多少时间。响应时间是衡量系统性能的重要指标之一,响应时间越短,性能越好,一般一个接口的响应时间是在毫秒级。在系统中,可以把响应时间自下而上细分为以下几种:

  • 数据库响应时间:数据库操作所消耗的时间,往往是整个请求链中最耗时的;

  • 服务端响应时间:服务端包括 Nginx 分发的请求所消耗的时间以及服务端程序执行所消耗的时间;

  • 网络响应时间:这是网络传输时,网络硬件需要对传输的请求进行解析等操作所消耗的时间;

  • 客户端响应时间:对于普通的 Web、App 客户端来说,消耗时间是可以忽略不计的,但如果你的客户端嵌入了大量的逻辑处理,消耗的时间就有可能变长,从而成为系统的瓶颈。

(2)吞吐量

吞吐率是指在一定的时间范围内,到底能处理多少事情。这里的“事情”,在计算机 里就是处理的数据或者执行的程序指令。和搬东西来做对比,如果响应时间短,可以来回多跑几趟多搬几趟。所 以说,缩短程序的响应时间,一般来说都会提升吞吐率。还可以多找几个人一起来 搬,这就类似现代的服务器都是 8 核、16 核的。人多力量大,同时处理数据,在单位时间 内就可以处理更多数据,吞吐率自然也就上去了

吞吐率现实中的系统往往有一个峰值极限。超过这个峰值极限,系统就会超载,除了服务延迟超标,还会造成一系列的性能问题(比如系统挂掉)。这个峰值极限往往需要经过仔细的性能测试,并且结合访问延迟标准来确定。有了这个峰值极限值后,系统的设计和运维就需要确保系统的负载不要超过这个值。

在测试中往往会比较注重系统接口的 TPS(每秒事务处理量),因为 TPS 体现了接口的性能,TPS 越大,性能越好。在系统中,也可以把吞吐量自下而上地分为两种:磁盘吞吐量和网络吞吐量。

①磁盘吞吐量

磁盘性能有两个关键衡量指标

  • IOPS(Input/Output Per Second),即每秒的输入输出量(或读写次数),这种是指单位时间内系统能处理的 I/O 请求数量,I/O 请求通常为读或写数据操作请求,关注的是随机读写性能。适应于随机读写频繁的应用,如小文件存储(图片)、OLTP 数据库、邮件服务器。

  • 数据吞吐量,这种是指单位时间内可以成功传输的数据量。对于大量顺序读写频繁的应用,传输大量连续数据,例如,电视台的视频编辑、视频点播 VOD(Video On Demand),数据吞吐量则是关键衡量指标。

②网络吞吐量

指网络传输时没有帧丢失的情况下,设备能够接受的最大数据速率。网络吞吐量不仅仅跟带宽有关系,还跟 CPU 的处理能力、网卡、防火墙、外部接口以及 I/O 等紧密关联。而吞吐量的大小主要由网卡的处理能力、内部程序算法以及带宽大小决定。

(3)计算机资源分配使用率

通常由 CPU 占用率、内存使用率、磁盘 I/O、网络 I/O 来表示资源使用率。这几个参数好比一个木桶,如果其中任何一块木板出现短板,任何一项分配不合理,对整个系统性能的影响都是毁灭性的。

资源使用率的标准也需要考虑其他几个重要因素。一个因素是意外事件的缓冲(Buffer)和灾难恢复(Disaster Recovery, or DR)。一个现实世界中的系统,随时都会有意外事件(比如流量波动)或者部分网络故障,这就需要整个系统资源保留一定的缓冲,来应付这些意外和从发生的灾难中恢复。比如 CPU 的使用率,虽然理论上可以到 100%,但考虑这些因素,实际的使用率指标往往远远低于 100%。

  • 吞吐量逐步上升,而 CPU 变化很小。这种情况说明系统设计得比较好,能继续“扛”更 高的负载,该阶段可以继续增大请求速率。

  • 吞吐量逐步上升,而 CPU 也平稳上升。CPU 使用 随着吞吐量的上升而平稳上升,直到 CPU 成为瓶颈——这里的原因很简单,用于业务处 理的 CPU 是真正“干活的”,这部分的消耗显然是少不了的。如果这种情况下 CPU 已 经很高了却还要优化,那就是说在不能动硬件的情况下,需要改进算法了,因为 CPU 已经在拼命干活了。

  • 吞吐量变化很小甚至回落,而 CPU 大幅上升。这种情况说明 CPU 的确在干活,却不是 用于预期内的“业务处理”。最典型的例子就是,内存泄漏

  • 吞吐量上不去,CPU 也上不去。这种情况是说,CPU 无法成为瓶颈,因为其它瓶颈先出现了,因此 这种情况下就要检查磁盘 I/O、网络带宽、工作线程数等等。因此优化的目的,就是努力找到并移除当前的瓶颈,将 CPU 的这个潜力发挥出来。

(4)负载承受能力

当系统压力上升时,你可以观察,系统响应时间的上升曲线是否平缓。这项指标能直观地反馈给你,系统所能承受的负载压力极限。例如,当你对系统进行压测时,系统的响应时间会随着系统并发数的增加而延长,直到系统无法处理这么多请求,抛出大量错误时,就到了极限。

2.性能的度量方式

  • 平均值是把这段时间所有请求的响应时间数据相加,再除以总请求数。平均值可 以在一定程度上反应这段时间的性能,但它敏感度比较差,如果这段时间有少量慢请求时, 在平均值上并不能如实的反应。假设在 30s 内有 10000 次请求,每次请求的响应时间都是 1ms,那么这 段时间响应时间平均值也是 1ms。这时,当其中 100 次请求的响应时间变成了 100ms, 那么整体的响应时间是 (100 * 100 + 9900 * 1) / 10000 = 1.99ms。你看,虽然从平均值 上来看仅仅增加了不到 1ms,但是实际情况是有 1% 的请求(100/10000)的响应时间已 经增加了 100 倍。所以,平均值对于度量性能来说只能作为一个参考。

  • 最大值就是这段时间内所有请求响应时间最长的值,但它的问题又在于过于敏感 了。还拿上面的例子来说,如果 10000 次请求中只有一次请求的响应时间达到 100ms,那么这 段时间请求的响应耗时的最大值就是 100ms,性能损耗为原先的百分之一,这种说法明显 是不准确的。

  • 分位值有很多种,比如 90 分位、95 分位、75 分位。以 90 分位为例,把这段时间请 求的响应时间从小到大排序,假如一共有 100 个请求,那么排在第 90 位的响应时间就是 90 分位值。分位值排除了偶发极慢请求对于数据的影响,能够很好地反应这段时间的性能 情况,分位值越大,对于慢请求的影响就越敏感。

分位值是最适合作为时间段内,响应时间统计值来使用的,在实际工作中也应用 最多。除此之外,平均值也可以作为一个参考值来使用。

从用户使用体验的角度来看,200ms 是第一个分界点:接口的响应时间在 200ms 之内, 用户是感觉不到延迟的,就像是瞬时发生的一样。而 1s 是另外一个分界点:接口的响应时 间在 1s 之内时,虽然用户可以感受到一些延迟,但却是可以接受的,超过 1s 之后用户就 会有明显等待的感觉,等待时间越长,用户的使用体验就越差。所以,健康系统的 99 分位 值的响应时间通常需要控制在 200ms 之内,而不超过 1s 的请求占比要在 99.99% 以上。

3.性能数据分析的教训和陷阱

  • 第一是数据的相关和因果关系。有时候几个时间序列之间可以很清楚地看出有很强的相关性质,但是对它们之间的因果关系却不能判定。这一点必须非常清楚,因为在很多性能问题讨论和根因分析的场合,非常容易武断地犯这样的错误,而导致走弯路。更复杂的情况是有时候系统性能变坏,是因为几个指标互为因果,或者构成环形因果,也就是互相推波助澜。

  • 第二是数据的大小和趋势。面对性能相关的数据并判断它们是“好”还是“坏”是很难的。经常听到有人问一个问题:“客户平均访问逗留时间多长比较好?”这个问题没有一个简单的答案。这取决于每个网站的特性,以及我们想要实现的目标。对一个网站而言很正常的逗留时间,可能对另一个网站而言非常糟糕。在很多情况下,比单纯数字大小更重要的是数据的趋势,比如某个时期是上升还是下降,变化的幅度有多大等等。

  • 第三是数据干净与否。如果数据集合来自多个数据源,或者来自复杂的测试环境,需要特别注意这些数据里面有没有无效数据。如果不能剔除无效数据,那么整体数据就“不干净”,由此而得出的结论经常会“失之毫厘,谬以千里”。

  • 第四是对性能数据内在关系的理解。性能数据分析的核心,就是要理解各个性能指标的关系,并且根据数据的变化来推断得出各种结论,比如故障判别、根因分析。

二、系统的性能瓶颈影响因素

所有的性能问题,归根结底都是某种资源受到制约,不够用了

图片

1.CPU

CPU 的时钟频率,也就是主频反映了 CPU 工作节拍,也就直接决定了 CPU 周期大小。主频和周期大小。比如基于英特尔 Skylake 微处理器架构的 i7 的一款,其主频为 4GHz,那么每一个时钟周期(Cycle)大约0.25 纳秒(ns)。CPU 运行程序时,最基本的执行单位是指令。而每一条指令的执行都需要经过四步:指令获取、指令解码、指令执行、数据存入。这些操作都是按照 CPU 周期来进行的,一般需要好几个周期。 每个指令周期数 CPI 和每个周期指令数 IPC 其实是孪生兄弟,衡量的是同一个东西。CPI衡量平均每条指令的平均时钟周期个数。它的反面是IPC。虽然一个指令的执行过程需要多个周期,但 IPC 是可以大于 1 的,因为现代 CPU 都采用流水线结构。一般来讲,测量应用程序运行时的 IPC,如果低于 1,这个运行的系统性能就不是太好,需要做些优化来提高 IPC。 MIPS 就是每秒执行的百万指令数。MIPS越高,CPU 性能越高。MIPS 可以通过主频和 IPC 相乘得到,也就是说 MIPS= 主频×IPC。比如一个 CPU 频率再高,IPC 是 0 的话,性能就是 0。假设一个CPU 的主频是 4GHz,IPC 是 1,那么这个 CPU 的 MIPS 就是 4000。注意的是,MIPS是理论值,实际运行环境数量一般小于这个值。 CPU 缓存。一般 CPU 都有几级缓存,分别称为 L1、L2、L3,按这个顺序越来越慢,也越来越大,当然成本也越来越低。

图片

(1)CPU 利用率高&&平均负载高

有的应用需要大量计算,他们会长时间、不间断地占用 CPU 资源,导致其他资源无法争夺到 CPU 而响应缓慢,从而带来系统性能问题。这种情况常见于 CPU 密集型的应用,大量的线程处于可运行状态,I/O 很少,常见的大量消耗 CPU 资源的应用场景有:

  • 正则操作引起的回溯

  • 数学运算

  • 序列化/反序列化

  • 反射操作

  • 死循环或者不合理的大量循环

  • 基础/第三方组件缺陷

排查高 CPU 占用的一般思路:通过 jstack 多次(> 5次)打印线程栈,一般可以定位到消耗 CPU 较多的线程堆栈。或者通过 Profiling 的方式(基于事件采样或者埋点),得到应用在一段时间内的 on-CPU 火焰图,也能较快定位问题。

还有一种可能的情况,此时应用存在频繁的 GC (包括 Young GC、Old GC、Full GC),这也会导致 CPU 利用率和负载都升高。排查思路:使用 jstat -gcutil 持续输出当前应用的 GC 统计次数和时间。频繁 GC 导致的负载升高,一般还伴随着可用内存不足,可用 free 或者 top 等命令查看下当前机器的可用内存大小。

CPU 利用率过高,是否有可能是 CPU 本身性能瓶颈导致的呢?也是有可能的。可以进一步通过 vmstat 查看详细的 CPU 利用率。用户态 CPU 利用率(us)较高,说明用户态进程占用了较多的 CPU,如果这个值长期大于50%,应该着重排查应用本身的性能问题。内核态 CPU 利用率(sy)较高,说明内核态占用了较多的 CPU,所以应该着重排查内核线程或者系统调用的性能问题。如果 us + sy 的值大于 80%,说明 CPU 可能不足。

(2)CPU 利用率低&&平均负载高

如果CPU利用率不高,说明应用并没有忙于计算,而是在干其他的事。CPU 利用率低而平均负载高,常见于 I/O 密集型进程,这很容易理解,毕竟平均负载就是 R 状态进程和 D 状态进程的和,除掉了第一种,就只剩下 D 状态进程了(产生 D 状态的原因一般是因为在等待 I/O,例如磁盘 I/O、网络 I/O 等)。

排查&&验证思路:使用 vmstat 1 定时输出系统资源使用,观察 %wa(iowait) 列的值,该列标识了磁盘 I/O 等待时间在 CPU 时间片中的百分比,如果这个值超过30%,说明磁盘 I/O 等待严重,这可能是大量的磁盘随机访问或直接的磁盘访问(没有使用系统缓存)造成的,也可能磁盘本身存在瓶颈,可以结合 iostat 或 dstat 的输出加以验证,如 %wa(iowait) 升高同时观察到磁盘的读请求很大,说明可能是磁盘读导致的问题。

此外,耗时较长的网络请求(即网络 I/O)也会导致 CPU 平均负载升高,如 MySQL 慢查询、使用 RPC 接口获取接口数据等。这种情况的排查一般需要结合应用本身的上下游依赖关系以及中间件埋点的 trace 日志,进行综合分析。

(3)CPU 上下文切换次数变高(多线程编程导致)

先用 vmstat 查看系统的上下文切换次数,然后通过 pidstat 观察进程的自愿上下文切换(cswch)和非自愿上下文切换(nvcswch)情况。自愿上下文切换,是因为应用内部线程状态发生转换所致,譬如调用 sleep()、join()、wait()等方法,或使用了 Lock 或 synchronized 锁结构;非自愿上下文切换,是因为线程由于被分配的时间片用完或由于执行优先级被调度器调度所致。

如果自愿上下文切换次数较高,意味着 CPU 存在资源获取等待,比如说,I/O、内存等系统资源不足等。如果是非自愿上下文切换次数较高,可能的原因是应用内线程数过多,导致 CPU 时间片竞争激烈,频频被系统强制调度,此时可以结合 jstack 统计的线程数和线程状态分布加以佐证。

理想情况下是各个核上的中断能够均衡。如果数量不均衡,往往会造成严重的性能问题——有的核会超载而导致系统响应缓慢,但是其他的核反而空闲。

CPU 对多线程的执行顺序是谁定的呢?是由内核的进程调度来决定的。内核进程调度负责管理和分配 CPU 资源,合理决定哪个进程该使用 CPU,哪个进程该等待。进程调度给不同的线程和任务分配了不同的优先级,优先级最高的是硬件中断,其次是内核(系统)进程,最后是用户进程。每个逻辑 CPU 都维护着一个可运行队列,用来存放可运行的线程来调度。

(4)CPU 的性能监测工具 在 Linux 上,最常用的 Top 系统进程监控命令。Top 是一个万金油的工具,可以显示出 CPU 的使用、内存的使用、交换内存和缓存大小、缓冲区大小、各个进程信息等。如果想要查看过去的 CPU 负载情况,可以用 uptime。也可以用 mpstat 和 pidstat,来分别查看每个核还有每个进程的情况。另一个常用的 vmstat 命令可以用于显示虚拟内存、内核线程、磁盘、系统进程、I/O 模块、中断等信息。 Perf 是 Linux 上的性能剖析(profiling)工具,极为有用。它是基于事件采样原理,以性能事件为基础,利用内核中的计数器来进行性能统计。它不但可以分析指定应用程序的性能问题,也可以用来分析内核的性能问题。

2.内存

Java 程序一般通过 JVM 对内存进行分配管理,主要是用 JVM 中的堆内存来存储 Java 创建的对象。系统堆内存的读写速度非常快,所以基本不存在读写性能瓶颈。但是由于内存成本要比磁盘高,相比磁盘,内存的存储空间又非常有限。所以当内存空间被占满,对象无法回收时,就会导致内存溢出、内存泄露等问题。

内存方面的性能指标,主要有缓存命中率、缓存一致性、内存带宽、内存延迟、内存的使用大小及碎片、内存的分配和回收速度等

(1)缓存和缓存命中率 

随着多核 CPU 的发展,CPU 缓存通常分成了三个级别:L1、L2、L3。一般而言,每个核上都有 L1 和 L2 缓存。L1 缓存其实分成两部分:一个用于存数据,也就是 L1d Cache,另外一个用于存指令,L1i Cache。 L1 缓存相对较小,每部分差不多只有几十 KB。L2 缓存更大一些,有几百 KB,速度就要慢一些了。L2 一般是一个统一的缓存,不把数据和指令分开。L3 缓存则是三级缓存中最大的一级,可以达到几个 MB 大小,同时也是最慢的一级了。你要注意,在同一个处理器上,所有核共享一个 L3 缓存。

(2)缓存一致性 

虽然缓存能够极大地提升运算性能,但也带来了一些其他的问题,比如“缓存一致性问题。如果不能保证缓存一致性,就可能造成结果错误。因为每个核都有自己的 L1 和 L2 缓存,当在不同核上运行同一个进程的不同线程时,如果这些线程同时操作同一个进程内存,就可能互相冲突,最终产生错误的结果。 为了达到数据访问的一致,就需要各个处理器和内核,在访问缓存和写回内存时遵循一些协议,这样的协议就叫缓存一致性协议。常见的缓存一致性协议有 MSI、MESI 等。缓存一致性协议解决了缓存内容不一致的问题,但同时也造成了缓存性能的下降。在有些情况下性能还会受到严重影响。

(3)内存带宽和延迟 

计算机性能方面的一个趋势就是,内存越来越变成主要的性能瓶颈。内存对性能的制约包括三个方面:内存大小、内存访问延迟和内存带宽。

  • 第一个方面就是内存的使用大小,这个最直观,大家都懂。这方面的优化方式也有很多,包括采用高效的,使用内存少的算法和数据结构。

  • 第二个方面是内存访问延迟,这个也比较好理解,我们刚刚讨论的各级缓存,都是为了降低内存的直接访问,从而间接地降低内存访问延迟的。如果我们尽量降低数据和程序的大小,那么各级缓存的命中率也会相应地提高,这是因为缓存可以覆盖的代码和数据比例会增大。

  • 第三个方面就是内存带宽,也就是单位时间内,可以并行读取或写入内存的数据量,通常以字节 / 秒为单位表示。一款 CPU 的最大内存带宽往往是有限而确定的。并且一般来说,这个最大内存带宽只是个理论最大值,实际中我们的程序使用只能达到最大带宽利用率的60%。如果超出这个百分比,内存的访问延迟会急剧上升。

当内存带宽较小时,内存访问延迟很小,而且基本固定,最多缓慢上升。而当内存带宽超过一定值后,访问延迟会快速上升,最终增加到不能接受的程度。那么一款处理器的内存总带宽取决于哪些因素呢? 有四个因素,内存总带宽的大小就是这些因素的乘积。这四个因素是:DRAM 时钟频率、每时钟的数据传输次数、内存总线带宽(一般是 64 字节)、内存通道数量。(4)内存的分配 程序使用的内存大小很关键,是影响一个程序性能的重要因素,所以我们应该尽量对程序的内存使用大小进行调优,从而让程序尽量少地使用内存。 应用程序向操作系统申请内存时,系统会分配内存,这中间总要花些时间,因为操作系统需要查看可用内存并分配。一个系统的空闲内存越多,应用程序向操作系统申请内存的时候,就越快地拿到所申请的内存。反之,应用程序就有可能经历很大的内存请求分配延迟。 比如说,在系统空闲内存很少的时候,程序很可能会变得超级慢。因为操作系统对内存请求进行(比如 malloc())处理时,如果空闲内存不够,系统需要采取措施回收内存,这个过程可能会阻塞。 写程序时,或许习惯直接使用 new、malloc 等 API 申请分配内存,直观又方便。但这样做有个很大的缺点,就是所申请内存块的大小不定。当这样的内存申请频繁操作时,会造成大量的内存碎片;这些内存碎片会导致系统性能下降。

一般来讲,开发应用程序时,采用内存池(Memory Pool)可以看作是一种内存分配方式的优化。所谓的内存池,就是提前申请分配一定数量的、大小仔细考虑的内存块留作备用。当线程有新的内存需求时,就从内存池中分出一部分内存块。如果已分配的内存块不够,那么可以继续申请新的内存块。同样,线程释放的内存也暂时不返还给操作系统,而是放在内存池内留着备用。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率和系统的总体内存使用效率得到提升。

(5)NUMA 的影响 

   最后看看多处理器使用内存的情景,也就是 NUMA 场景。NUMA 系统现在非常普遍,它和 CPU 和内存的性能都很相关。简单来说,NUMA 包含多个处理器(或者节点),它们之间通过高速互连网络连接而成。每个处理器都有自己的本地内存,但所有处理器可以访问全部内存。因为访问远端内存的延迟远远大于本地内存访问,操作系统的设计已经将内存分布的特点考虑进去了。比如一个线程运行在一个处理器中,那么为这个线程所分配的内存,一般是该处理器的本地内存,而不是外部内存。但是,在特殊情况下,比如本地内存已经用光,那就只能分配远端内存。 部署应用程序时,最好将访问相同数据的多个线程放在相同的处理器上。根据情况,有时候也需要强制去绑定线程到某个节点或者 CPU 核上。

(6)工具

        内存相关的工具也挺多的。比如内存监测命令或许是 free 了。这个命令会简单地报告总的内存、使用的内存、空闲内存等。vmstat(Virtual Meomory Statistics, 虚拟内存统计)也是 Linux 中监控内存的常用工具,可以对操作系统的虚拟内存、进程、CPU 等的整体情况进行监视。

3.磁盘 I/O

磁盘相比内存来说,存储空间要大很多,但磁盘 I/O 读写的速度要比内存慢,虽然目前引入的 SSD 固态硬盘已经有所优化,但仍然无法与内存的读写速度相提并论。

图片

(1)磁盘 I/O 问题排查思路:

  • 使用工具输出磁盘相关的输出的指标,常用的有 %wa(iowait)、%util,根据输判断磁盘 I/O 是否存在异常,譬如 %util 这个指标较高,说明有较重的 I/O 行为;

  • 使用 pidstat 定位到具体进程,关注下读或写的数据大小和速率;

  • 使用 lsof + 进程号,可查看该异常进程打开的文件列表(含目录、块设备、动态库、网络套接字等),结合业务代码,一般可定位到 I/O 的来源,如果需要具体分析,还可以使用 perf 等工具进行 trace 定位 I/O 源头。

  • 需要注意的是,%wa(iowait)的升高不代表一定意味着磁盘 I/O 存在瓶颈,这是数值代表 CPU 上 I/O 操作的时间占用的百分比,如果应用进程的在这段时间内的主要活动就是 I/O,那么也是正常的。

(2)网络 I/O 存在瓶颈,可能的原因如下:

  • 一次传输的对象过大,可能会导致请求响应慢,同时 GC 频繁;

  • 网络 I/O 模型选择不合理,导致应用整体 QPS 较低,响应时间长;

  • RPC 调用的线程池设置不合理。可使用 jstack 统计线程数的分布,如果处于 TIMED_WAITING 或 WAITING 状态的线程较多,则需要重点关注。举例:数据库连接池不够用,体现在线程栈上就是很多线程在竞争一把连接池的锁;

  • RPC 调用超时时间设置不合理,造成请求失败较多;

(3)存储系统的三大性能指标 

一个存储系统的性能最主要的是三个:IOPS、访问延迟、吞吐率 / 带宽。这三个指标其实是互相关联和影响的,但是一般还是分开来衡量。

  • IOPS即每秒钟能处理的读写请求数量,每个 IO 的请求都有自己的特性,比如读还是写,是顺序读写还是随机读写,IO 的大小是多少等。顺序读写是访问存储设备中相邻位置的数据;随机读写是访问存储设备中非相邻位置的数据。既然 IO 有这些特点,所以我们讨论存储系统 IOPS 性能的时候,经常需要更加具体的描述。比如顺序读 IOPS、随机写 IOPS 等。IOPS 的数值会随这样的参数不同而有很大的不同,这些参数的变化,包括读取和写入的比例、其中顺序读写及随机读写的比例、读写大小、线程数量及读写队列深度等。此外,系统配置等因素也会影响 IOPS 的结果,例如操作系统的设置、存储设备的驱动程序特点、操作系统后台运行的作业等。

  • 访问延迟和响应时间,指的是从发起 IO 请求,到存储系统把 IO 处理完成的时间间隔,常以毫秒(ms)或者微妙(us)为单位。对这一性能指标,通常会考虑它的平均值和高位百分数,比如 P99、P95。

  • 吞吐率或者带宽,衡量的是存储系统的实际数据传输速率,通常以 MB/s 或 GB/s 为单位。一般来讲,IOPS 与吞吐率是紧密相关的;它们之间的关系是,吞吐率等于 IOPS 和 IO 大小的乘积。比如对一个硬盘的读写 IO 是 1MB,硬盘的 IOPS 是 100,那么硬盘总的吞吐率就是 100MB/s。需要强调的是,这里 IO 的具体特性很重要,比如是顺序还是随机,IO 大小等。

有些存储系统会因为其 IO 队列深度增加,而获得更好的 IO 性能;比如吞吐率会升高,平均访问延迟会降低。这是为什么呢?这是因为存储系统的 IO 队列处理机制,可以对 IO 进行重新排序,从而获得好的性能。比如,它可以合并几个相邻的 IO,把随机 IO 重新排序为顺序 IO 等。

(4)不同类型硬盘

①HDD(传统硬盘)的性能 简单来说,当应用程序发出硬盘 IO 请求后,这个请求就会进入硬盘的 IO 队列。如果前面有其他 IO,那么这个请求可能需要排队等待。当轮到这个 IO 来存取数据时,磁头需要机械运动到数据存放的位置,这就需要磁头寻址到相应的磁道,并旋转到相应的扇区,然后才是数据的传输。所以,讨论硬盘 IO 的性能时,需要充分考虑这一点。我们有时候需要把硬盘响应时间和硬盘访问时间分开对待。它们之间的关系是,硬盘响应时间除了包括访问时间外,还包括 IO 排队的延迟。

图片

一块硬盘上面往往会标注后面三个参数,分别是平均寻址时间、盘片旋转速度,以及数据传输速度,这三个参数就可以提供给我们计算上述三个步骤的时间。平均寻址时间一般是几个毫秒。平均旋转时间可以从硬盘转动速度 RPM 来算出。因为每个IO 请求平均下来需要转半圈,那么如果硬盘磁头每分钟转一万圈(10K RPM),转半圈就需要 3 毫秒。要注意的是,硬盘上面标注的数据传输速度参数往往是最大值,实际的数据传输时间要取决于 IO 的大小。 对于一块普通硬盘而言,随机 IO 读写延迟就是 8 毫秒左右,IO 带宽大约每秒 100MB,而随机 IOPS 一般是 100 左右。②SSD(固态硬盘) 性能方面,SSD 的 IO 性能相对于 HDD 来说,IOPS 和访问延迟提升了上千倍,吞吐率也是提高了几十倍。但是 SSD 的缺点也很明显。主要有三个缺点:

  • 贵;

  • 容量小;

  • 易损耗。

如今,越来越多的应用程序采用 SSD 来减轻 I/O 性能瓶颈。许多测试和实践的结果都表明,与 HDD 相比,采用 SSD 带来了极大的应用程序性能提升。但是在大多数采用 SSD 的部署方案中,SSD 仅被视为一种“更快的HDD”,并没有真正发挥 SSD 的潜力。 因为尽管使用 SSD 作为存储时,应用程序可以获得更好的性能,但是这些收益,主要归因于 SSD 提供的更高的 IOPS 和带宽。但是,SSD 除了提供这些之外,它还有其它特点,比如易损耗,以及其独特的内部机制。如果应用程序的设计能充分考虑 SSD 的内部机制,设计出对 SSD 友好的应用程序,就可以更大程度地优化 SSD,从而进一步提高应用程序性能,也可以延长 SSD 的寿命,并降低运营成本。

4.网络

机房、带宽、路由器等方面优化

网络对于系统性能来说,也起着至关重要的作用。如果你购买过云服务,一定经历过, 选择网络带宽大小这一环节。带宽过低的话,对于传输数据比较大,或者是并发量比较大的系统,网络就很容易成为性能瓶颈。

假设有200m的流量同时请求进入服务器,但是带宽只有1m,这么来算光是接收这批数据量信息也要消耗大约200s的时间。带宽可以理解为在指定时间内从一端请求到另一端的流量总量。

在数据中心里面,一般的传输 RTT 不超过半毫秒。如果是同一个机柜里面的两台主机之间,那么延迟小于 0.1 毫秒。网络的传输延迟是和地理距离相关的。另外要注意的是,传输延迟也取决于传输数据的大小,因为各种网络协议都是按照数据包来传输的,包的大小会有影响。比如一个 20KB 大小的数据,用 1Gbps 的网络传输,仅仅网 卡发送延迟就是 0.2 毫秒。

图片

(1)网络的性能指标

  • 可用性。对于网络来讲,最重要的就是,网络是否可以正常联通。如何测试网络可用性呢?最简单的方法,就是使用 ping 命令。这个命令其实就是向远端的机器发送 ICMP 的请求数据包,并等待接收对方的回复。通过请求和应答返回的对比,来判断远端的机器是否连通,也就是网络是否正常工作。

  • 响应时间。端到端的数据一次往返所花费时间,就是响应时间。响应时间受很多因素的影响,比如端到端的物理距离、所经过网络以及负荷、两端主机的负荷等。

  • 网络带宽容量。它指的是在网络的两个节点之间的最大可用带宽,这一指标一般是由设备和网络协议决定的,比如网卡、局域网和 TCP/IP 的特性。如果是向网络提供商购买的带宽,那么购买的数量就是网络的带宽容量。

  • 网络吞吐量。指在某个时刻,在网络中的两个节点之间,端到端的实际传输速度。网络吞吐量取决于当前的网络负载情况,而且是随着时间不同而不断变化的。

  • 网络利用率。指网络被使用的时间占总时间的比例,一般以百分比来表示。因为数据传输的突发性,所以实际中的网络利用率一般不会太高。否则的话,那么响应时间就不能保证。

(2)单机的网络性能

虽然网络传输需要两端进行,但是我们必须从单机开始,来清楚地了解网络的协议栈。网络协议其实相当复杂,而且分很多层级。操作系统内核中,最大的一个子系统或许就是网络。网络子系统由多个协议组成,其中每个协议都在更原始的协议之上工作。大学时你应该学过 OSI 模型,或 TCP / IP 协议栈,里面的分层定义对网络协议非常适用。当用户数据通过网络协议栈传递时,数据会被封装在该协议的数据包中。在考虑多层协议的交互时,我们尽量把思路简化。其实它们之间的关系很简单,就是网络协议栈的每一层都有其职责,与其他高层和低层协议无关。

举个例子来说,我们看 IP 层的协议。IP 是第三层协议,它是通过路由器和网络发送端到端的数据报;它的主要目的,就是在网络中的每一段找到路由路径,从而最终能够到达数据报的接收地。但它不保证数据的有序性,也就是转发过程中几个数据报会重新排序;同时也不能保证整个数据的完整性和可靠性,比如丢失的数据报那就是丢失了,IP 层不会重传。那么数据的可靠性是谁保证的呢?这就需要更上层的 TCP 协议实现。TCP 层协议通过检测数据丢失并且重传,来保证数据的可靠性,也通过序列号来保证数据的有序性。但是这两层协议:TCP 和 IP 两层协议,都只能传输原始的数据;而对于数据本身是否被压缩过,这些数据表示什么,是在更高层的协议(如 HTTP 和应用层)上实现的。

我们接着具体到网络协议的程序实现,在 Unix 系统中,网络协议栈可以大体分为三层。从上到下,第一层是通过一系列系统调用,实现 BSD 套接字的套接字层,比如 sendmsg()函数;第二层是中间协议的程序,例如 TCP/IP/UDP;第三层是底部的媒体访问控制层,提供对网络接口卡(NIC)本身的访问。

(3)数据中心的网络性能 

    一台服务器和互联网的远端服务器进行数据传输的时候,需要经过好几层交换器和不同的网络。数据从一台服务器的网卡出来之后,下一步就是经过机柜上面的交换器,然后是数据中心内部的网络,再进入互联网骨干网络。接收端的情况正好相反。

  • 机柜交换器。数据中心里面的服务器不是单独放置的,一般是几十台服务器组成了一个机柜。机柜上面会有机柜交换器。这个机柜交换器的作用,一方面让机柜内部的服务器直接互通;另一方面,机柜交换器会有外联线路,连接到数据中心的骨干网络。

  • 数据中心网络。数据中心网络里面也分了好几层,从 TOR 到集群交换器(Cluter Switch),再到集合交换器(Aggregation Switch)等,最后到数据中心路由器。

图片

这里有一个值得注意的地方是,就是这个多层次结构,一般是越往上层,总的带宽越少。比如,服务器的网卡带宽是 25Gbps,即使这个机柜内有 30 台服务器,总带宽就是750Gbps,机柜交换器 TOR 的外联带宽有多少呢?可能只有 100Gbps。这个差距就叫带宽超订(over-subscription)。同样的,POD 交换器之间的带宽会继续变小。之所以允许带宽超订,是因为多数的数据交换是在内部进行的,不会全部都和外部进行交换。知道这一点是必要的,因为我们做网络方面和服务部署优化的时候,需要考虑这点,不要让高层的网络带宽成为性能瓶颈。比如假设两个服务分别用不同的服务器,它们之间的数据交换如果很多的话,就尽量让它们运行在同一个机柜的服务器里面;如果不能保证同一个机柜,就尽量是同一个 POD 内部。

(4)互联网的网络性能 

互联网上运行的是 TCP/IP 协议。一个常见的性能问题就是丢包。TCP 对丢包非常敏感,因为每次丢包,TCP 都认为是网络发生了拥塞,因此就会降低传输速度,并且采取重传来恢复,这就影响网络性能。实际情况中,造成丢包的原因有很多,不一定就是网络拥塞。因此我们需要进行各种测试观测来做根因分析。通常的丢包原因,是端到端的网络传输中的某一段发生了问题,或许是拥塞,或许是硬件问题,也或许是其他软件原因。我们需要一步步地逐段逐层地排除。 对于网络的每段,可以用工具(比如 Traceroute)来发现每一段路由,然后逐段测试。对于协议的每层,你都可以用相关工具进行分析。比如,你可以分为 TCP 层、操作系统、网卡驱动层,分别分析。具体来讲,对 TCP 层,可以用比如 netstat 来观察是不是套接字缓存不够;对操作系统,可以观察 softnet_stat,来判断 CPU 的查询队列;对网卡驱动层,可以用 ethtool 等来进行分析。

(5)内容分发网络(CDN)的性能

内容分发网络的基本原理是,利用最靠近每位用户的服务器,更快、更可靠地将(音乐、图片及其他)文件发送给终端用户,而不是每次都依赖于中心服务器。靠近用户的服务器,一般叫边缘服务器,会把请求的内容最大限度地缓存,以尽量地减少延迟。 内容分发网络的边缘服务器节点会在多个地点,多个不同的网络上摆放。这些节点之间通常会互相传输内容,对用户的下载行为最优化,并借此改善用户的下载速度,提高系统的稳定性。同时,将边缘服务器放到不同地点,也可以减少网络互连的流量,进而降低带宽成本。内容分发网络提供商往往有很大的规模,比如几十万台服务器。对服务的客户提供所需要的节点数量会随着需求而不同。

(6)工具

  • Netperf 是一个很有用的网络性能的测量工具,主要针对基于 TCP 或 UDP 的传输。Netperf 有两种操作模式:批量数据传输和请求 / 应答模式,根据应用的不同,可以进行不同模式的网络性能测试,Netperf 测试结果所反映的,是一个端到端的系统能够以多快的速度发送数据和接收数据。

  • Iperf 这个工具也以测试 TCP 和 UDP 带宽质量,比如最大 TCP 带宽,延迟抖动和数据包丢失等性能参数。

  • Netstat 这一命令可以显示与 IP、TCP、UDP 和 ICMP 协议相关的统计数据,提供 TCP连接列表,TCP 和 UDP 监听,进程内存管理的相关报告,一般用于检验本机各端口的网络连接情况。

  • Traceroute 这一命令可以帮我们知道,数据包从我们的计算机到互联网远端的主机,是走的什么网络路径。

5.其它原因

(1)异常

Java 应用中,抛出异常需要构建异常栈,对异常进行捕获和处理,这个过程非常消耗系统性能。如果在高并发的情况下引发异常,持续地进行异常处理,那么系统的性能就会明显地受到影响。

(2)数据库

数据库的操作往往是涉及到磁盘 I/O 的读写。大量的数据库读写操作,会导致磁盘 I/O 性能瓶颈,进而导致数据库操作的延迟性。对于有大量数据库读写操作的系统来说,数据库的性能优化是整个系统的核心。

(3)锁竞争

在并发编程中,我们经常会需要多个线程,共享读写操作同一个资源,这个时候为了保持数据的原子性(即保证这个共享资源在一个线程写的时候,不被另一个线程修改),我们就会用到锁。锁的使用可能会带来上下文切换,从而给系统带来性能开销。JDK1.6 之后,Java 为了降低锁竞争带来的上下文切换,对 JVM 内部锁已经做了多次优化,例如,新增了偏向锁、自旋锁、轻量级锁、锁粗化、锁消除等。而如何合理地使用锁资源,优化锁资源,就需要你了解更多的操作系统知识、Java 多线程编程基础,积累项目经验,并结合实际场景去处理相关问题。

(4)服务器的ulimit

通常使用的线上服务器都是centos系列,这里我列举centos7相关的系统配置:ulimit配置 查看服务器允许的最大打开文件数目(linux系统中设计概念为一切皆文件) 通常如果我们的java程序需要增大一些socket的链接数目,可以通过调整ulimit 里面的open参数进行配置。

[root@izwz9ic9ggky8kub9x1ptuz ~]# ulimit -a | grep openopen files                      (-n) 1000查看用户的最大进程数目[root@izwz9ic9ggky8kub9x1ptuz ~]# ulimit -a | grep usermax user processes              (-u) 7284

(5)服务器性能

Tomcat 和nginx

三、常用命令

1.查看系统当前网络连接数

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

2.查看堆内对象的分布 Top 50(定位内存泄漏)

jmap –histo:live $pid | sort-n -r -k2 | head-n 50

3.按照 CPU/内存的使用情况列出前10 的进程

#内存ps axo %mem,pid,euser,cmd | sort -nr | head -10#CPUps -aeo pcpu,user,pid,cmd | sort -nr | head -10

4.显示系统整体的 CPU利用率和闲置率

grep "cpu " /proc/stat | awk -F ' ' '{total = $2 + $3 + $4 + $5} END {print "idle \t used\n" $5*100/total "% " $2*100/total "%"}'

5.按线程状态统计线程数(加强版)

jstack $pid | grep java.lang.Thread.State:|sort|uniq -c | awk '{sum+=$1; split($0,a,":");gsub(/^[ \t]+|[ \t]+$/, "", a[2]);printf "%s: %s\n", a[2], $1}; END {printf "TOTAL: %s",sum}';

6.查看最消耗 CPU 的 Top10 线程机器堆栈信息

推荐使用 show-busy-java-threads 脚本,该脚本可用于快速排查 Java 的 CPU 性能问题(top us值过高),自动查出运行的 Java 进程中消耗 CPU 多的线程,并打印出其线程栈,从而确定导致性能问题的方法调用,该脚本已经用于阿里线上运维环境。链接地址:https://github.com/oldratlee/useful-scripts/。

7.火焰图生成(需要安装 perf、perf-map-agent、FlameGraph 这三个项目):

# 1. 收集应用运行时的堆栈和符号表信息(采样时间30秒,每秒99个事件);sudo perf record -F 99 -p $pid -g -- sleep 30; ./jmaps
#2. 使用 perf script 生成分析结果,生成的 flamegraph.svg 文件就是火焰图。sudo perf script | ./pkgsplit-perf.pl | grep java | ./flamegraph.pl > flamegraph.svg

8.按照 Swap 分区的使用情况列出前 10 的进程

for file in /proc/*/status ; do awk '/VmSwap|Name|^Pid/{printf $2 " " $3}END{ print ""}' $file; done | sort -k 3 -n -r | head -10

9.JVM 内存使用及垃圾回收状态统计

#显示最后一次或当前正在发生的垃圾收集的诱发原因jstat -gccause $pid#显示各个代的容量及使用情况jstat -gccapacity $pid
#显示新生代容量及使用情况jstat -gcnewcapacity $pid
#显示老年代容量jstat -gcoldcapacity $pid
#显示垃圾收集信息(间隔1秒持续输出)jstat -gcutil $pid 1000

10.其他的一些日常命令

# 快速杀死所有的 java 进程ps aux | grep java | awk '{ print $2 }' | xargs kill -9# 查找/目录下占用磁盘空间最大的top10文件find / -type f -print0 | xargs -0 du -h | sort -rh | head -n 10
  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值