流弊666,Java应用之性能测试瓶颈调优
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
引言:性能瓶颈调优
性能调优步骤
确定问题:根据性能监控的数据和性能分析的结果,确定性能存在的问题。 确定原因:确定问题之后,对问题进行分析,找出问题的原因。 确定解决方案(改服务器参数配置/增加硬件资源配置/修改代码)。 验证解决方案,分析调优结果。
性能瓶颈概率分布
数据库服务器 CPU 使用率高(慢查询、SQL 过多、连接数过多) 抛出连接数过多(连接池设置太小,导致连接排队) 数据库出现死锁
应用出现内存泄露 应用出现线程竞争/死锁 程序代码的算法复杂度 中间件、第三方应用出现异常 计算密集型任务引起 CPU 负载高 I/O 密集型任务引起 I/O 负载高
JMeter 单机负载能力有限,如果需要模拟的用户请求数超过其负载极限,也会导致 TPS 压不上去
Linux 可用内存无法回收(开销速率大于回收速率)
系统资源
CPU 监控内容:CPU 使用率、CPU 使用类型(用户进程、内核进程) 瓶颈分析:CPU已压满(接近 100%),需要再看其他指标的拐点所出现的时刻是否与 CPU 压满的时刻基本一致。 内存 监控内容:实际内存、虚拟内存 瓶颈分析:内存不足时,操作系统会使用虚拟内存,从虚拟内存读取数据,影响处理速度。 磁盘 I/O 监控内容:I/O 速度、磁盘等待队列 瓶颈分析:磁盘 I/O 成为瓶颈时,会出现磁盘I/O繁忙,导致交易执行时在 I/O 处等待。 网络 监控内容:网络流量(带宽使用率)、网络连接状态 瓶颈分析:如果接口传递的数据包过大,超过了带宽的传输能力,就会造成网络资源竞争, 导致 TPS 上不去。
CPU 瓶颈:增加 CPU 资源。 内存瓶颈:增加内存、释放缓存。 磁盘 I/O 瓶颈:更换性能更高的磁盘(如固态 SSD)。 网络带宽瓶颈;增加网络带宽。
CPU
top 参数详解
us(user):运行(未调整优先级的)用户进程所消耗的 CPU 时间的百分比。 像 shell 程序、各种语言的编译器、数据库应用、web 服务器和各种桌面应用都算是运行在用户地址空间的进程。 这些程序如果不是处于 idle 状态,那么绝大多数的 CPU 时间都是运行在用户态。 sy(system):运行内核进程所消耗的 CPU 时间的百分比。 所有进程要使用的系统资源都是由 Linux 内核处理的。当处于用户态(用户地址空间)的进程需要使用系统的资源时,比如需要分配一些内存、或是执行 I/O 操作、再或者是去创建一个子进程,此时就会进入内核态(内核地址空间)运行。事实上,决定进程在下一时刻是否会被运行的进程调度程序就运行在内核态。 对于操作系统的设计来说,消耗在内核态的时间应该是越少越好。通常 sy 比例过高意味着被测服务在用户态和系统态之间切换比较频繁,此时系统整体性能会有一定下降。 在实践中有一类典型的情况会使 sy 变大,那就是大量的 I/O 操作,因此在调查 I/O 相关的问题时需要着重关注它。 大部分后台服务使用的 CPU 时间片中 us 和 sy 的占用比例是最高的。同时这两个指标又是互相影响的,us 的比例高了,sy 的比例就低,反之亦然。 另外,在使用多核 CPU 的服务器上,CPU 0 负责 CPU 各核间的调度,CPU 0 上的使用率过高会导致其他 CPU 核心之间的调度效率变低。因此测试过程中需要重点关注 CPU 0。 ni(niced):用做 nice 加权的进程分配的用户态 CPU 时间百分比。 每个 Linux 进程都有个优先级,优先级高的进程有优先执行的权利,这个叫做 pri。进程除了优先级外,还有个优先级的修正值。这个修正值就叫做进程的 nice 值。 这里显示的 ni 表示调整过 nice 值的进程消耗掉的 CPU 时间。如果系统中没有进程被调整过 nice 值,那么 ni 就显示为 0。 一般来说,被测服务和服务器整体的 ni 值不会很高。如果测试过程中 ni 的值比较高,需要从服务器 Linux 系统配置、被测服务运行参数查找原因。 id(idle):空闲的 CPU 时间百分比。 一般情况下, us + ni + id 应该接近 100%。 线上服务运行过程中,需要保留一定的 id 冗余来应对突发的流量激增。 在性能测试过程中,如果 id 一直很低,吞吐量上不去,需要检查被测服务线程/进程配置、服务器系统配置等。 wa(I/O wait):CPU 等待 I/O 完成时间百分比。 和 CPU 的处理速度相比,磁盘 I/O 操作是非常慢的。有很多这样的操作,比如:CPU 在启动一个磁盘读写操作后,需要等待磁盘读写操作的结果。在磁盘读写操作完成前,CPU 只能处于空闲状态。 Linux 系统在计算系统平均负载时会把 CPU 等待 I/O 操作的时间也计算进去,所以在我们看到系统平均负载过高时,可以通过 wa 来判断系统的性能瓶颈是不是过多的 I/O 操作造成的。 磁盘、网络等 I/O 操作会导致 CPU 的 wa 指标提高。通常情况下,网络 I/O 占用的 wa 资源不会很高,而频繁的磁盘读写会导致 wa 激增。 如果被测服务不是 I/O 密集型的服务,那需要检查被测服务的日志量、数据载入频率等。 如果 wa 高于 10% 则系统开始出现卡顿;若高于 20% 则系统几乎动不了;若高于 50% 则很可能磁盘出现故障。 hi:硬中断消耗时间百分比。 si:软中断消耗时间百分比。 硬中断是外设对 CPU 的中断,即外围硬件发给 CPU 或者内存的异步信号就是硬中断信号;软中断由软件本身发给操作系统内核的中断信号。 通常是由硬中断处理程序或进程调度程序对操作系统内核的中断,也就是我们常说的系统调用(System Call)。 在性能测试过程中,hi 会有一定的 CPU 占用率,但不会太高。对于 I/O 密集型的服务,si 的 CPU 占用率会高一些。 st:虚拟机等待 CPU 资源的时间。 只有 Linux 在作为虚拟机运行时 st 才是有意义的。它表示虚机等待 CPU 资源的时间(虚机分到的是虚拟 CPU,当需要真实的 CPU 时,可能真实的 CPU 正在运行其它虚机的任务,所以需要等待)。
性能分析思路
wa(IO wait)的值过高,表示硬盘存在 I/O 瓶颈。 id(idle)值高,表示 CPU 较空闲。 如果 id 值高但系统响应慢时,有可能是 CPU 等待分配内存,此时应加大内存容量。 如果 id 值持续低于 10,那么系统的 CPU 处理能力相对较低,表明系统中最需要解决的资源是 CPU。
案例分析
服务对磁盘读写的业务逻辑有问题,读写频率过高,写入数据量过大,如不合理的数据载入策略、log 过多等,都有可能导致这种问题。 服务器内存不足,服务在 swap 分区不停的换入换出。
LOAD
/proc/cpuinfo 中的 processors 的最大值不一定是 CPU 的核数,有可能该 CPU 支持超线程技术,从而 processors 是物理核数的 2 倍。 这里我们需要准确的核数,具体方法为:找到 /proc/cpuinfo 文件中所有的 physical id 后的数值,取得最大的数值,加一后就是实际的 CPU 个数。然后查找任意一个 processors 下的 cpu cores,即是该颗 CPU 的核数,实际 CPU 个数乘以核数即为 CPU 的物理总核数。
[root@localhost home]# cat /proc/cpuinfo |grep "physical id"
physical id : 0
physical id : 0
[root@localhost home]# cat /proc/cpuinfo |grep "cpu cores"
cpu cores : 2
cpu cores : 2
负载测试时:系统负载应接近但不能超过阈值。 并发测试时:系统负载最高不能超过阈值的 80%。 稳定性测试时:系统负载应在阈值的 50% 左右。
如果 1 分钟 load 很高,5 分钟 load 较高,15 分钟 load 起伏不大的情况下,说明该次高 load 为突发情况,可以容忍。 如果高 load 持续,导致 5 分钟和 15 分钟 load 都已经超过报警值,这时候需要考虑进行处理。 如果 15 分钟 load 高于 1 分钟 load,说明高 load 情况已经得到缓解。
内存
top 参数详解
VIRT:进程所使用的虚拟内存的总数。它包括所有的代码,数据和共享库,加上已换出的页面,所有已申请的总内存空间。 RES:进程正在使用的没有交换的物理内存(栈、堆)。申请内存后该内存段已被重新赋值。 SHR:进程使用共享内存的总数。该数值只是反映可能与其它进程共享的内存,不代表这段内存当前正被其他进程使用。 SWAP:进程使用的虚拟内存中被换出的大小。交换的是已经申请但没有使用的空间(包括栈、堆、共享内存)。 DATA:进程除可执行代码以外的物理内存总量,即进程栈、堆申请的总空间。
free 参数详解
Mem 行:物理内存的使用情况。 Swap 行:交换空间的使用情况。 swap space 是磁盘上的一块区域,可以是一个分区,也可以是一个文件,所以具体的实现可以是 swap 分区也可以是 swap 文件。当系统物理内存吃紧时,Linux 会将内存中不常访问的数据保存到 swap 上,这样系统就有更多的物理内存为各个进程服务,而当系统需要访问 swap 上存储的内容时,再将 swap 上的数据加载到内存中,这就是常说的换出和换入。 交换空间可以在一定程度上缓解内存不足的情况,但是它需要读写磁盘数据,所以性能不是很高。因此当交换空间内存开始使用,则表明内存严重不足。 如果系统内存充足或是做性能压测的机器,可以使用 swapoff -a 关闭交换空间,或在 /etc/sysctl.conf 文件中设置 swappiness 值。如果系统内存不富余,则需要根据物理内存的大小来设置交换空间的大小,具体的策略网上有很丰富的资料。 total 列:系统总的可用物理内存和交换空间大小。 used 列:已经被使用的物理内存和交换空间大小。 free 列:还有多少物理内存和交换空间可用使用(真正尚未被使用的物理内存数量)。 在吞吐量固定的前提下,如果内存持续上涨,那么很有可能是被测服务存在明显的内存泄漏,需要使用 valgrind 等内存检查工具进行定位。 shared 列:被共享使用的物理内存大小。 buffer/cache 列:被 buffer 和 cache 使用了的物理内存大小。 Linux 内核为了提升磁盘操作的性能,会消耗一部分空闲内存去缓存磁盘数据,就是 buffer 和 cache。 如果给所有应用分配足够内存后,物理内存还有剩余,linux 会尽量再利用这些空闲内存,以提高整体 I/O 效率,其方法是把这部分剩余内存再划分为 cache 及 buffer 两部分加以利用。 所以,空闲物理内存不多,不一定表示系统运行状态很差,因为内存的 cache 及 buffer 部分可以随时被重用,在某种意义上,这两部分内存也可以看作是额外的空闲内存。 available 列:还可以被应用程序使用的物理内存大小。 从应用程序的角度来说,available = free + buffer + cache。请注意,这只是一个很理想的计算方式,实际中的数据往往有较大的误差。
snyc
echo 3 > /proc/sys/vm/drop_caches
free -m
/proc/sys/vm/drop_caches 这个值的 0 改为 1
磁盘 I/O
tps:该设备每秒的传输次数。“一次传输”意思是“一次 I/O 请求”。多个逻辑请求可能会被合并为“一次 I/O 请求”。“一次传输”请求的大小是未知的。 kB_read/s:每秒从设备(driveexpressed)读取的数据量,单位为 Kilobytes。 kB_wrtn/s:每秒向设备(driveexpressed)写入的数据量,单位为 Kilobytes。 kB_read:读取的总数据量,单位为 Kilobytes。 kB_wrtn:写入的总数量数据量,单位为 Kilobytes。
rrqm/s:每秒这个设备相关的读取请求有多少被 Merge 了。 当系统调用需要读取数据的时候,VFS 将请求发到各个 FS,如果 FS 发现不同的读取请求读取的是相同 Block 的数据,FS 会将这个请求合并 Merge。 wrqm/s:每秒这个设备相关的写入请求有多少被 Merge 了。 await:每一个 I/O 请求的处理的平均时间(单位:毫秒)。 await 的大小一般取决于服务时间(svtcm)以及 I/O 队列的长度和 I/O 请求的发出模式。假设 svtcm 比较接近 await,说明 I/O 差点没有等待时间。 假设 await 远大于 svctm(如大于 5),就要考虑 I/O 有压力瓶颈,说明 I/O 队列太长,应用得到的响应时间变慢。假设响应时间超过了用户能够容许的范围,这时可以考虑更换更快的磁盘。 svctm:I/O 平均服务时间。 %util:在统计时间内有百分之多少用于 I/O 操作。 例如,如果统计间隔 1 秒,该设备有 0.8 秒在处理 I/O,而 0.2 秒闲置,那么该设备的 %util = 0.8/1 = 80%,该参数暗示了设备的繁忙程度。 %util 接近100% 表明 I/O 请求太多,I/O 系统繁忙,磁盘可能存在瓶颈。
iostat -x 完整参数如下:
- rrqm/s: 每秒进行 merge 的读操作数目。即 delta(rerge)/s
- wrqm/s: 每秒进行 merge 的写操作数目。即 delta(wmerge)/s
- t/s: 每秒完成的读 I/O 设备次数。即 delta(rioVs
- w/s: 每秒完成的写 1/O 设备次数。即 delta(wio)/s
- rsec/s: 每秒读扇区数。即 delta(rsect)/s
- ws0c/s: 每秒写扇区数。即 deita(wsect)/s
- rkB/s: 每秒读 K 字节数。是 rsect/s 的一半,因为每扇区大小为 512 字节。(需要计算)
- wkB/s: 每秒写 K 字节数。是 wsect/s 的一半。(需要计算)
- avgrq+sz: 平均每次设备 I/O 操作的数据大小(扇区)。delta(rsect+wsect)/delta(rio+wio)
- avgqu-sz: 平均I/O队列长度,即delta(avea)/s/1000(因为 aveq 的单位为毫秒)。
- await: 平均每次设备 I/O 操作的等待时间(毫秒)。即 delta(ruse+wuse)/delta(rio+wio)
- svctm: 平均每次设备 I/O 操作的服务时间(毫秒)。即 delta(use)/delta(rio+wio)
- %util:一秒中有百分之多少的时间用于 I/O 操作,或者说一秒中有多少时间 I/O 队列是非空的。即 delta(use)/s/1000(因为 use 的单位为毫秒)
网络
网络流量监控
看视频看新闻使用带宽:客户端的下载、服务端的上行带宽。 服务端接收客户端的数据使用带宽:客户端的上传、服务端的下行带宽。
1 Mb/s 带宽速度为 128 KB/s(1024Kb / 8KB) 100 Mb/s 带宽速度为 12.5 Mb/s(考虑网络损耗通常按 10M/s 或 1280KB/s 算)
10M 带宽约 20 秒:耗时 = 流量 / 速率 = 20MB / (10Mb/8) = 20 / 1.25 = 16 秒(按 1MB/s=128KB/s 速度算即 20 秒) 100M 带宽约 2 秒:耗时 = 流量 / 速率 = 20MB / (100Mb/8) = 20 / 12.5 = 1.6 秒(按 10MB/s=128KB/s 速度算即 2 秒) 1000M 带宽约 0.2 秒:耗时 = 流量 / 速率 = 20MB / (1000Mb/8) = 20 / 125 = 0.16 秒(按 100MB/s=128KB/s 速度算即 0.2 秒)
硬件解决:增加带宽(带宽便宜)。 软件解决:分析对应业务操作的数据传送内容是否可精简;是否可以异步传送。
网络连接状态监控
对于使用 TCP 协议的服务,需要监控服务已建立连接的变化情况(即 ESTABLISHED 状态的 TCP 连接)。 对于 HTTP 协议的服务,需要监控被测服务对应进程的网络缓冲区的状态、TIME_WAIT 状态的连接数等。
数据库
慢查询
更具体的慢 SQL 分析优化,可参见《MySQL 慢 SQL & 优化方案》。
show variables like "slow%";
mysql -uroot -p123456 -h127.0.0.1 -p3307 -e "show full processlist" |grep dbname |grep -v NULL
分析 SQL 是否加载了不必要的字段/数据。 分析 SQL 是否命中索引。 如果 SQL 很复杂,优化 SQL 结构。 如果表数据量太大,考虑分表。 ……
连接数
当数据库连接池被占满时,如果有新的 SQL 语句要执行,只能排队等待,等待连接池中的连接被释放(等待之前的 SQL 语句执行完成)。 如果监控发现数据库连接池的使用率过高,甚至是经常出现排队的情况,则需要进行调优。
-- 查看最大连接数 -- 查看最大连接数
mysql> show variables like '%max_connection%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| extra_max_connections | |
| max_connections | 2512 |
+-----------------------+-------+
2 rows in set (0.00 sec)
-- 重新设置最大连接数
set global max_connections=1000;
[mysqld]
max_connections = 1000
mysql> show status like 'Threads%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_cached | 32 |
| Threads_connected | 10 |
| Threads_created | 50 |
| Threads_rejected | 0 |
| Threads_running | 1 |
+-------------------+-------+
5 rows in set (0.00 sec)
Threads_connected:表示当前连接数。跟 show processlist 结果相同。准确的来说,Threads_running 代表的是当前并发数。 Threads_running:表示激活的连接数。一般远低于 connected 数值。 Threads_created:表示创建过的线程数。 如果我们在 MySQL 服务器配置文件中设置了 thread_cache_size,那么当客户端断开之后,服务器处理此客户的线程将会缓存起来以响应下一个客户而不是销毁(前提是缓存数未达上限)。 如果发现 Threads_created 值过大的话,表明 MySQL 服务器一直在创建线程,这也是比较耗资源,因此可以适当增加配置文件中 thread_cache_size 值。
mysql> show variables like 'thread_cache_size';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| thread_cache_size | 100 |
+-------------------+-------+
1 row in set (0.00 sec)
锁
缓存命中率
通常,SQL 查询是从磁盘中的数据库文件中读取数据。 若当某一个 SQL 查询语句之前执行过,则该 SQL 语句及查询结果都会被缓存下来,下次再查询相同的 SQL 语句时,就会直接从数据库缓存中读取。(注意,MySQL 8 开始已废弃查询缓存功能。)
业务执行过程中 SQL 查询时的缓存命中率(查询语句读取缓存的次数占总查询次数的比例)。 如果缓存命中率过低,需要优化对应的代码和 SQL 查询语句,以提高缓存命中率。
案例分析
使用 top 命令观察,确定是 mysqld 导致还是其他原因。 CPU 分为用户 CPU 和内核 CPU。综合其他的各项资源指标来分析,发现内存、磁盘IO、网络等指标无任何异常,因此判断此处不是内核 CPU 占用高,主要原因是用户进程占用的 CPU 高。 确认目前 CPU 占用高的为 mysqld 进程。 分析数据库服务器 CPU 高的可能原因:慢 SQL、SQL 语句过多、连接数过多等。 使用show full processlist查看当前数据库中正在执行的 SQL 语句及连接池的状态,发现大量 SQL 在等待执行。 再结合操作过程中的系统日志进行分析,发现每进入一次商城首页,就需要在数据库中执行 19 条查询 SQL。 查看慢查询日志,看看是否有超过预期指标的 SQL 语句,并分析排查:看看执行计划是否准确、索引是否缺失、数据量是否太大等。 目前案例经过慢查询日志的分析,未存在慢查询。 确认是否存在慢 SQL: 确认是否 SQL 语句过多或连接数过多:
硬件解决:增加 CPU。 软件解决:为减少一次性加载过多 SQL,可考虑使用分批次、异步加载的方式(展示到什么位置,就查询什么位置的数据)。
JAVA 应用
JVM
垃圾回收机制
垃圾回收指将内存中已申请并使用完成的那部分内存空间回收,供新申请使用。 垃圾回收机制都是针对堆区的内存进行的。
系统在做垃圾回收时,不能够处理任何用户业务的。如果垃圾回收过于频繁,导致系统业务处理能力下降。 由于 Full GC 内存比较大,垃圾回收一次时间比较长,那么这段时间内都不能处理业务,对系统影响比较大,因此我们需要关注Full GC 频率。
新程序执行时需要先申请内存空间,会先从年轻代中申请。 在年轻代满了以后,就会进行垃圾回收Young GC。 回收时检查年轻代中的内存,是否还在使用。还在使用的部分会移存到生存区 2 中;不使用的部分则释放,此时年轻代内存空间被清空。 新程序执行申请内存空间,再从年轻代申请。 年轻代又满了,就会进行垃圾回收Young GC。还在使用的内存移存到生存区 1 中,并把生存区 2 中的内存也都存到生存区 1 中。此时就会清空年轻代和生存区 2。 循环上述 1-5 步。 如果部分内存在生存区中存活很久(内存在生存区中移动了 10 次左右),则将这部分内存放入到老年代中。 循环上述 1-7 步,直到老年代内存空间全部占满,此时就要进行垃圾回收Full GC。
作者 | Juno3550
来源 | cnblogs.com/juno3550/p/15212546.html
评论