性能测试问题排查常规思路
1、性能问题服务器硬件指标常见表现:
当出现性能问题时,服务器常见的表现会出现以下几种:
l 应用服务器CPU居高不下:如果非计算类服务,一般是由于系统存在错误逻辑导致线程锁或有低级错误(例如日志写入用了system.out)导致,如果是计算类服务
l 应用服务器内存使用持续走高,一般是由于服务器产生大量实例积压导致,常见于大量session/线程大量堆积(一个session一般占用10-20M左右的jvm内存),但是为什么会导致session/线程堆积的问题要具体分析。
l 应用服务器磁盘交换率居高不下,顾名思义,大量的文件写操作在进行,如果业务逻辑没有频繁读写文件,则需排查,首先排查日志写入
l 应用服务器网络流量居高不下,常见报文一般是2K-20K左右,可根据并发数计算大致流量,若实际流量和计算明显不符,则需要排查到底是哪些连接在消耗流量,常见是一些图片、js等资源文件没有设置本地缓存。
l 数据库服务器cpu居高不下,一般常见于大量全表扫描,大量物理读,sql执行效率低下等情况,要配合top10 sql进一步查询
l 数据库服务器缓冲区命中率持续偏低,偏低一般是小于95%,常见于数据库配置有问题,联系DBA进行进一步解决。
l 数据库服务器网络流量居高不下,要结合考虑业务及应用逻辑分析为什么会有大量数据持续的在数据库和应用服务器之间流动,并根据实际情况看是否能简化部分互交逻辑,减少带宽消耗。
2、性能问题排查一般过程:
按照问题发生的概率和投入产出比来说,一般情况下,会按照下图逻辑进行性能问题排摸和调优。在解决SQL和应用配置问题后,基本已经解决了90%左右的性能问题了。
3、数据库与sql排查注意点
排查数据库和sql语句的问题。常通过获取top 10 sql来定位最耗资源的sql语句,并分析sql语句来确认性能问题。数据库常见的性能问题表现为数据库服务器CPU过高。以oracle为例,列举一些常见问题:
1) 数据表/字段没有索引或sql查询时,某条件不在索引上:这个问题在新系统的测试环境上最为常见,常常是开发新建了表或字段,忘了加索引。加上索引即可修复。
2) 表锁、行锁,这个是应用和数据库的共同问题,常见于几种情况(包含但不限于):
a) 最常见:Select for update 之后没有及时 commit掉。
b) 对某一张大表进行大量或全量update的时候没有分批进行,而是一次性update并commit,并且对该表的读取方式未使用脏读模式。
c) 自增订单号没有使用sequence,而是通过数据库内某字段做控制。在大并发的情况下,对一条记录进行频繁的update
3) 数据表有索引,sql查询的时候,索引未生效,一般索引未生效有以下原因(包含但不限于),需要逐一排查:
a) 使用不等于!= 或<>运算符
b) 使用like的时候百分号/下划线等通配符在前,例如 "%_"
c) 数据发生很大变动之后,表没进行分析,可通过表分析或强制索引解决
d) 单独引用复合索引里的部分索引列,会不走索引或导致索引效果下降
e) 字符型字段为数字时在where条件里不添加引号,隐式数据转换导致索引失效
f) 在where条件中对索引列进行函数运算,如trim(),如需对字段进行函数处理,则要建立函数索引.
g) 对索引列使用 not in ,not exist 运算符
h) 日期时间类字段做索引,在查询的时候等号两边数据类型不一致,导致隐式数据转换导致索引失效,例如datatime 查timestamp 或者字符串查timestamp。很多大公司的编码规范中都完全抵制使用日期时间类字段作为索引。
i) 使用了is null导致索引(尤其是复合索引)失效,建议字段不要为NULL, 用0、特殊值或空字符串来代替
j) 跨表空间查询导致索引失效,建议对单个表空间进行查询后,用union联合查询
4) sql未经优化,例如使用多层嵌套查询,group by中使用了having等问题
5) 缓冲区命中率低于90%,一般来说,缓冲区命中率应该要高于90%,最好在95%以上
6) 表数据过大,未进行分表或分表空间。尤其是对于一些交易记录表,历史交易记录表、日志表等数据表
4、应用服务器参数排查注意点
出现任何性能问题之后,除数据库外,最应该排查的内容应当是对应用服务器的配置参数排查,一般排查以下问题:
l java内存/堆栈:未设置最大内存会导致服务器当大量并发发生时,无休止的使用内存和虚拟内存导致服务器资源耗尽。缺省值一般无限制或128m-256m
l 进程池/连接数:未设置最大连接数会导致当大量并发发生时,无休止的产生新连接/线程去处理请求,最终导致服务器资源耗尽。最大连接数设置过小,也会导致大量外链无法接入系统,返回连接超时。另外连接超时时间也不能配置的过大。
l java内存GC:过于频繁的GC会对服务器的cpu产生持续性压力,并且会影响内存数据的可靠性,gc间隔时间过长也会导致GC时cpu使用率产生井喷时增长。因此如果需要调整gc策略,需要找到平衡点
l 日志配置:建议将日志按照天输出进行配置,并且重定向到指定磁盘或目录,并定期清理,避免由于日志输出导致磁盘满的情况。
l 日志级别:系统运行时,建议日志级别调低,尽量只打印error级别的日志。
l 系统核心参数:一般常见于ulimit -a相关参数和sysctl.conf相关参数,用于控制系统级别的资源使用极限,经常容易忘记配置的是ulimit中的 open files(-n)参数,linux的系统默认是1024,实际作为服务器使用时远远不够的。另外ulimit参数分系统级别和用户级别,都需要设置正确
l 数据库连接池:这里单独拿出来是因为经常会遇到一种情况,即应用连接池很大,而数据库连接池却只有5-10甚至更小,当产生大量耗时的数据库查询时,请求的处理链就会阻塞在数据库查询上导致服务器资源耗尽,应用崩溃,而数据库层面却没有任何异常产生。
5、应用服务器代码排查注意点
如果数据库和应用配置都没有问题,那就需要排查应用本身的代码问题了。一般来说,如果能在项目开发代码编写的时候就考虑性能问题,会让代码的总体性能和质量都得到比较大的提升。从性能测试角度,一般建议注意以下问题:
l 如果是自己开发(含二次开发)的线程池,要考虑内存溢出,异常或者代码导致连接未释放多线程的问题。
l 系统构架设计时,要小心并行转串行的逻辑模块会成为最主要的瓶颈。
l 对于一些数据库读写极其频繁的应用,可以考虑使用数据库长连接的方式。
l 尽可能的减少递归的使用,好的递归能提高效率,但写的不好的递归会极大消耗服务器资源。
l 字符串拼接时,避免使用+号而是使用StingBuilder
l 尽量减少正则表达式的使用
l 对于连接,流等对象,有open就要有close,例如磁盘IO,网络IO,数据库连接等。
l 在处理异常时,如果能用选择判断,尽量不要使用try catch
l 除非不得已,否则不要在循环中使用try catch,应该把其放在最外层(个人深刻体会)
l 如果可以,循环内尽量不要创建对象引用
l 除非必要,程序运行中尽量避免使用反射,反射机制真的效率很差
l 避免一次性载入过大的数据/文件到内存,以免撑爆jvm,建议使用缓冲区读取的方式
l 如果要使用嵌套循环,建议在使用前多做业务逻辑分析和确认,避免由于逻辑错误产生冗余循环
l 前端尽量对非敏感数据、不经常变化的数据、页面资源、脚本文件等采用客户端缓存,减少服务器和客户端的互交数据量和互交过程。
l java写日志的时候,禁止使用system.out,必须使用框架提供的日志写入类
6、常见性能测试监控工具枚举分析