07. 性能分析和调优
07. 性能分析和调优
1. 性能调优的步骤
- 确定问题 - 测试⼈员职责
- 根据性能监控的数据和性能分析的结果,确定性能是否存在问题(要求)
- 确定原因 - 开发⼈员职责
- 确定了问题之后,对问题进行分析,找出问题产生的原因
- 给出解决方案 - 开发⼈员职责
- 确定调整目标和解决方案(改服务器参数配置/增加硬件资源配置/修改代码)
- 验证问题 - 测试⼈员回归测试
- 按照给出的解决方案,重新进行测试,既要保证有问题的指标得到解决,⼜要保证其他指标没有出现新问题
- 分析调优结果
- 分析出问题的性能指标是否有提升,关注其他指标未下降
注意:性能测试调优并不是一次完成的过程,针对同一个性能问题,上面的五步可能要经过多次循环才 能最终完成性能调优的目标(即:测试发现问题-找原因-调整-验证-分析-再测试……)
2.性能问题可以产⽣的原因 (性能瓶颈分析)
在实际的性能测试中,会遇到各种各样的问题,比如TPS压不上去,导致这种现象的原因很多,作为测试人员应配合开发人员进行分析尽快找出瓶颈的所在
常见的性能瓶颈分析
服务器资源分析
CPU瓶颈:CPU已压满(接近100%),通常其他指标的拐点出现的时刻是否与CPU压满的时刻基本一致
内存瓶颈:内存不足时,操作系统会使用虚拟内存,从虚拟内存读取数据,影响处理速度
磁盘I/O瓶颈:磁盘I/O成为瓶颈时,会出现磁盘I/O繁忙,导致交易执行时在I/O处等待
网络带宽:如果传递的数据包过大,超过了带宽的传输能力,就会造成网络资源竞争,导致TPS上不去
运⾏速度从快到慢:CPU >> 内存 >> 磁盘 存储空间从⼤到⼩:磁盘 >> 内存 >> CPU
JVM瓶颈分析
- JVM内存:内存申请没有及时释放,造成内存泄漏
数据库瓶颈分析
- 慢查询
- 数据库的连接池:设置太小,导致数据库连接出现排队
- 数据库出现死锁
程序内部实现机制
压测机
- JMeter单机负载能力有限,如果需要模拟的用户请求数超过其负载极限,也会导致TPS压不上去
1. 服务器资源分析 — CPU瓶颈
- 每个程序运行都需要占用CPU,那么单CPU的机器是如何同时运行多个程序的?
时间片即CPU分配给各个程序的时间,每个程序被分配一个时间段,称作它的时间片,即该程序允许运行的时间
串行:是大家排队一个一个来,并行是大家一起上 并发(Concurrency):体现在单个处理器;逻辑上同步运行。 并发有两种描述,一种是形容多个任务的执行状态;另一种是对“并发性”的简称。 并行(Parallelism),体现在多处理器,多核心;物理上同步运行。 并行:是真正的同时运行--在同一个时刻多个任务同时执行。例如多核处理器上,有多个线程同时执行同一段代码。单核处理器无法在同一时刻执行多个任务,因此无法并行。
CPU使用率
表示一段时间内,正在使用的CPU时间段 / 总的CPU时间段 * 100%
CPU使用率分为用户态、系统态和空闲态
用户态:表示 CPU 处于应用程序执行的时间
系统态:表示系统内核执行的时间
空闲态:表示空闲系统进程执行的时间
CPU使⽤率 = 已使⽤的时间⽚ / 总时间⽚ * 100% 已使⽤的时间⽚ = ⽤户CPU + 系统CPU 总时间⽚ = ⽤户CPU + 系统CPU + 空闲CPU ⽤户CPU:所有应⽤程序运⾏时消耗的CPU 系统CPU:操作系统运⾏消耗的CPU
查看CPU使用率的命令
top
测试关注点:
- 当CPU使用率高时,确定是用户CPU高,还是系统CPU高
- 如果是用户CPU高,说明某个软件程序的CPU资源占用率高,需要定位代码程序运行的效率
- 如果是系统CPU高,同步观察是否是其他资源(磁盘IO、内存、网络等)不足
2. 服务器资源分析 — 内存和虚拟内存
内存
- 又称主存储器/物理内存,计算机中所有程序的运行都在内存中进行
虚拟内存
- 是计算机系统内存管理的一种技术。当计算器内存不足时,可以使用虚拟内存进行补偿
- 在程序运行时,可以将程序的一部分装入内存,而将其余部分留在外存(磁盘),就可以启动程序执行。
- 在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。
- 同时,操作系统将内存中暂时不使用的内容换出到外存上,从而腾出空间存放将要调入内存的信息。
- 这样,系统好像为用户提供了一个比实际内存大得多的存储器,称为虚拟存储器。
内存瓶颈
- 内存不足时,操作系统会使用虚拟内存,从虚拟内存读取数据;而内存的速度要远快于磁盘速度,因此使用虚拟内存时性能大大降低。
查看内存使用的命令
查看总量
top
查看虚拟内存使用量
- vmstat
Swap
- si: 每秒从交换区写到内存的大小
- so: 每秒写入交换区的内存大小
测试关注点
- 如果si,so 长期不等于0,表示内存不足。(需要大量的从内存和虚拟内存之间读来读去,说明内存盛不开那么多进程, 说明内存不足)
3. 服务器资源分析 — 磁盘IO瓶颈
磁盘IO瓶颈
- 影响性能的是磁盘的读写速度(Input和Output速率),不是磁盘大小
查看磁盘IO使用的命令
安装:yum install -y sysstat
iostat -x 1 10 (每隔 1秒刷新显示,显示10次)
%util: 表示一秒中有百分之多少的时间用于 I/O
%iowait: CPU等待输入输出完成时间的百分比。
测试关注点
- 如果%util接近100%,说明产生的I/O请求太多,I/O系统已经满负荷(说明磁盘⻓时间占⽤CPU在发送数据,说明磁盘传输速度不⾜,存在瓶颈)
- 如果%iowait的值过高,表示硬盘存在I/O瓶颈(说明磁盘IO传输数据的任务很多,在等待,说明磁盘传输速度不⾜,存在瓶颈)
4. 服务器资源分析 — 网络瓶颈
网络瓶颈
影响性能的是网络的传输速度,与网络的总带宽进行对比,接近总带宽,说明网络存在瓶颈。
查看网络使用的命令
sar -n DEV 1 2 (每隔 1秒刷新显示,显示2次)
rxkB/s: 每秒接收的数据量(千字节数)
txkB/s: 每秒发送的数据量(千字节数)
测试关注点
- 将每秒接收的数据量rxkB/s,与网络最大带宽进行对比,如果实际传输速率接近网络最大带宽,说明网络IO有瓶颈
补充
宽带:⽤户(业务)维度来描述⽹络速率的⽅式。 例如:20M宽带、100M宽带、1000M 宽带速率单位:b(bit)/s 带宽:数据在⽹络中传输的速率,在技术中都是通过带宽来描述速率 速率单位:B(Byte)/s 1B = 8bit 实际情况:1000M 宽带 —— 对应着的带宽速率为 1000/8 = 125M
5. 数据库瓶颈分析 — 慢查询
慢查询定义
- 指执行速度低于设置的阀值的SQL语句
作用
- 帮助定位查询速度较慢的SQL语句,方便更好的优化数据库系统的性能
MySQL慢查询参数介绍
show variables like 'slow_query%';
slow_query_log: 慢查询日志开启状态 [ON:开启,OFF:关闭]
slow_query_log_file: 慢查询日志存放位置
long_query_time: 慢查询时长设置(超过该时长才会被记录,单位:秒)
show variables like 'long_query_time%';
慢查询开启并配置
- mysql> set global slow_query_log='ON'; # 开启慢查询日志
- mysql> set global slow_query_log_file='/data/slow_query.log'; # 设置慢查询日志存放位置
- mysql> set global long_query_time=1; # 设置慢查询时间标准,设置之后会在下次会话才生效
查看慢查询日志
打开慢查询开关后,就可以记录系统运行时查询时间长的SQL语句,并同步在日志文件中查看慢查询的SQL
6. 数据库瓶颈分析 — 数据库连接池
为什么要使用数据库连接池?
数据库连接池定义
- 数据库连接池是负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
作用
- 可以提高对数据库操作的性能
当客户请求数据库连接时
首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;
如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;
如果达到就按设定的最大连接数,进行等待
如果超出最大等待时间,则抛出异常给客户
测试关注点
- MYSQL官网给出了一个设置最大连接数的建议比例:max_connections / max_connections * 100% ≈ 85%,需要关注 MYSQL的最大连接数和 系统运行时数据库已建立连接数的比例
- 如果已使用连接数与最大连接数比例超过85%,需要增加最大连接数设置,否则会造成连接失败
- 如果已使用连接数与最大连接数比例小于10%,那显然设置的过大,会造成系统资源的浪费
- 查看 MYSQL 最大连接数:show variables like '%max_connections%';
查询当前数据库已建立连接数:show status like 'Threads_connected';
7. 数据库瓶颈分析 — 数据库死锁
锁的介绍
- 一个用户修改数据时,对该数据进行加锁操作,其他用户不能进行修改
- 只有当第一个用户修改完成后,释放了锁,其他用户拿到锁后,才能修改数据
MySQL主要有两种锁:表级、行级
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度最大,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,并发度也最高
死锁
- 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象。
- 若无外力作用,它们都将无法推进下去.此时称系统处于死 锁状态或系统产生了死锁。
- 表级锁不会产生死锁
死锁案例:投资功能,A和B两人分别给用户1给用户2进行投资
行锁
- 分析:在第一步骤进行操作时加锁,第二步无法操作导致死锁
转账功能 第一步 第二步 A给B转500 A记录:余额减少500 B记录:余额增加500 B给A转600 B记录:余额减少600 A记录:金额增加600
测试关注点(初步确定死锁):
查询当前是否锁表
show open tables where in_use>=1;
如果表锁了,这个sql可以查询出来
但是这个sql查出来的表,不一定是被锁住的。因为慢查询,如果耗费时间很长,也会查询出来
查看执行时间最长的线程,找到对应sql,找到表
show processlist;
如果需要先紧急解决问题,可以先手动杀死死锁的连接
- kill process_id
https://blog.csdn.net/qq_24691007/article/details/122932768
一、MYSQL数据库锁的种类
在数据库系统中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。
1、行级锁
行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。
特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
2、表级锁
表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
3、页级锁
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁.表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB支持页级锁
特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
二、MySQL常用存储引擎的锁机制
1、MyISAM和MEMORY:采用表级锁(table-level locking)
2、BDB:采用页面锁(page-level locking)或表级锁,默认为页面锁。
3、InnoDB:支持行级锁(row-level locking)和表级锁,默认为行级锁。
三、Innodb中的行锁与表锁
Innodb引擎中既支持行锁也支持表锁,那么什么时候会锁住整张表,什么时候或只锁住一行呢?
InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁。行级锁的缺点是:由于需要请求大量的锁资源,所以速度慢,内存消耗大。
四、行级锁与死锁
MyISAM中是不会产生死锁的,因为MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待。而在InnoDB中,锁是逐步获得的,就造成了死锁的可能。
在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。 在UPDATE、DELETE操作时,MySQL不仅锁定WHERE条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的next-key locking。当两个事务同时执行,一个锁住了逐渐索引在等待其他相关索引,一个锁定了非主键索引,在等待主键索引。这样就会发生死锁。发生死锁后,InnoDB一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。
五、如何避免死锁
有多种方法可以避免死锁,这里只介绍常见的三种,具体如下:
1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表,可以大大降低死锁机会。
2、在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率;
8. JAVA应用瓶颈分析 — JVM内存
JVM内存
- Java虚拟机在执行Java程序的过程中所管理的不同的内存数据区域。可简单分为:堆内存和非堆内存
- 堆内存
- 主要存放用new关键字创建的对象,所有对象实例以及数组都在堆上分配。
- 给开发人员使用的(关注)
- 非堆内存
- 保存虚拟机自己的静态数据,存放加载的Class类级别静态对象如类、方法等。
- 给JVM自己使用的
JVM堆内存的管理机制(JAVA垃圾回收机制)
- 年轻代存储“新生对象”,我们新创建的对象存储在年轻代中。
- 当年轻内存占满后,会触发Minor GC,清理年轻代内存空间。
- 老年代存储长期存活的对象和大对象。年轻代中存储的对象,经过多次GC后仍然存活的对象会移动到老年代中进行存储。
- 老年代空间占满后,会触发Full GC。Full GC是清理整个堆空间,包括年轻代和老年代
常见的内存问题
- 内存泄漏
- 内存泄露 memory leak,是指程序在申请内存后,无法完全释放已申请的内存空间。
- 一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
- 内存溢出
- 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory
- memory leak会最终会导致out of memory!
- 内存泄漏
测试关注点
堆内存使用量持续增长 —— 可能是内存泄漏
Full GC比较慢,执行时会停止程序一些事务的处理。 因此Full GC频率不能过高(低于10分钟)
如果Full GC之后,堆中仍然无法存储对象,就会出现 内存溢出 —— 程序出现crash(崩溃)
9. JAVA应用瓶颈分析 — JVM内存监控
JVM监控 —— 使用本地jvisualvm远程监控服务器:
- 添加应用程序启动参数,并启动服务
#脚本: nohup java -server \ -Dfile.encoding=UTF-8 \ -Dcom.sun.management.jmxremote \ -Djava.rmi.server.hostname=192.168.10.31 \ -Dcom.sun.management.jmxremote.port=12400 \ -Dcom.sun.management.jmxremote.ssl=false \ -Dcom.sun.management.jmxremote.authenticate=false \ -jar /home/litermail/litemall-all-0.1.0-exec.jar & #lsof -i:12400 #kill -9
进入本地jdk安装目录bin目录,找到 jvisualvm.exe 并启动
右键“远程”选择“添加远程主机”,并输入主机IP
- 右键主机选择“添加JMX连接”,并输入JMX端口
- 连接成功后在主机下会有对应的连接显示,双击查看监控信息
10. 压测机瓶颈分析 — 压测机
- 压测机影响性能测试结果的原因
- 主要是JMeter单机负载能力有限,如果需要模拟的用户请求数超过其负载极限,也会导致TPS压不上去
- 压测机资源的监控方法
- Windows测试机:自带“任务管理器”
- Linux测试机:PerfMon组件
- 解决方案
- 采用分布式执行的方法来提高负载量(增加资源、增加多台压测机),达到系统性能测试要求的TPS
11. 小节
1. 常见的性能问题主要有哪些方面?
服务器资源分析
CPU瓶颈:用户CPU和系统CPU
内存瓶颈:实际内存和虚拟内存
磁盘IO瓶颈:磁盘input和output
网络瓶颈:网络上下行带宽
数据库瓶颈分析
慢查询
数据库连接池
死锁
JVM瓶颈分析
- JVM内存:堆内存和内存回收
程序内部实现机制
压测机
单机负载能力有限,采用分布式执行提高负载量
3. 性能调优的案例
案例1 — 获取首页数据-CPU高
场景描述
- 进入首页后,加载首页的相关数据,包括:轮播图、频道、优惠券、团购专区、品牌商直供、新品首发、热卖商品、专题精选等数据
测试结果数据
- 问题分析
- CPU已接近100%
- 通过查询日志,首页一次请求中需要查询很多数据 27个查询
- 解决方案
- 提升服务器配置
- 分批次、异步加载首页数据,首页底部的数据(如:新品首发、热卖商品、专题精选等数据)等用户向下滑动页面时再加载
案例2 — 查看商品详情-网络高
场景描述
- 进入商品详情页面时,加载商品的详细信息
测试结果数据
问题分析
- 网络带宽已跑满
- 一次请求中返回了全部数据
解决方案
- 提升服务器网络带宽
- 分批次、异步加载商品数据
案例3 — 搜索商品-慢查询
场景描述
模拟慢SQL
#设置慢SQL的查询时长(测试后记得修改回来) set global long_query_time=0.1;
进入首页,在搜索框中输入关键字搜索商品
测试结果数据
搜索关键字**“床”**时,出现慢查询SQL语句
可以换个商品多个关键字外加分页数据调大
http://litemall.com/wx/goods/list?keyword=母亲节&page=1&limit=100000
查看慢查询语句cat /var/lib/mysql/localhost-slow.log,包含如下SQL语句
问题分析
找出搜索商品接口对应的SQL语句(通过查看代码实现 或者从日志中获取查询SQL)。如右图日志信息:
分析具体的SQL语句
定位问题
- 当搜索关键字匹配到大量的商品时,第3条SQL语句会返回大量重复数据
- 第4条SQL语句中的in查询条件中同样包含大量重复的商品分类id
- 因此:第4条SQL会出现查询时间较长,是由于第3条SQL返回的ID有大量重复
解决方案
优化第3条SQL语句,因该SQL语句的查询结果大部分都是重复的,可以进行去重处理
案例4 —JVM内存溢出
场景描述
- 请求测试接口(/wx/index/oom),模拟内存溢出
测试结果数据
/** * 模拟代码 */ public class TestHeapOOM { public static void main(String[] args) { List list = new ArrayList(); while (true) { list.add(new TestHeapOOM()); } } }
正常使用的JVM内存
模拟JVM内存溢出
问题分析
- JVM内存占用随着时间的推移占用越来越多,直至内存溢出,系统退出
解决方案
- 排查代码存在的问题,及时释放无用的对象