书接上文性能测试误差分析文字版 - 上
,继续来分享一下误差来源的其他方面。
加锁资源
这个相对隐蔽,因为需要用到加锁的资源的测试场景一般都相对复杂,而且有一些比较简单的方式可以在运行前数据准备阶段将这部分工作风险化解掉。
在文章如何对消息队列做性能测试中,我用到了一个LinkedBlockingQueue<String>
集合对象将所有测试数据准备好,然后在执行性能测试的时候,各个线程每次都去拿一个正确的参数值。但是在大多数情况下,我们很难确保这个机制能够顺利进行,即使使用固定请求次数的模型进行测试,可以提前预估数据量,也无法避免测试数据导致某次请求的响应失败或者断言失败。
但是这种场景又的的确确会在日常工作中常用到,特别在处理链路压测的时候,需要一个或者多个线程安全的对象来标记链路执行的状态和支路开关状态。如链路压测中的支路问题初探和ThreadLocal 在链路性能测试中实践中提到的一些问题解决方案。又比如在固定 QPS 压测模式探索、固定 QPS 压测初试文章中用于记录请求总次数和进行异步请求补偿场景下对于线程安全对象的使用。这些都是性能测试中躲不过去的坎儿,建议避免使用synchronized
同步关键字,尽量使用JDK
自带的线程安全类对象。
参考文章:
- CountDownLatch 类在性能测试中应用
- CyclicBarrier 类在性能测试中应用
- Phaser 类在性能测试中应用
- 线程安全类在性能测试中应用
- 线程同步类 CyclicBarrier 在性能测试集合点应用
本机性能
这里基本都是教训就不详细说了,简单列几条我遇到的。
内存设置太少,导致系统处理GC
消耗CPU
,导致创建对象速度变慢,还有一种是线程数设置过大,与实际硬件资源不匹配。导致无法创建、或者线程切换时消耗CPU
过多。
线程数和其他资源设置不匹配,如端口数及端口回收机制,系统最大连接数偏低,连接池设置偏低等等。这些都将会导致在测试过程中发生不必要的等待,从而增加时间的消耗。
错误解析方式
整理分享一下我遇到两个比较大的Demo
。
校验数值型数据。比如说手机号,通常的做法都是使用正则验证手机号,比如^[1](([3][0-9])|([4][5-9])|([5][0-3,5-9])|([6][5,6])|([7][0-8])|([8][0-9])|([9][1,8,9]))[0-9]{8}$
,还有一种比较简单的^[1]([3-9])[0-9]{9}$
。这种情况下我们完全没有比较在测试中进行如此详尽的验证,对于简单的验证,我们完全可以转化成改字符串转成long
类型数据,是否在某个区间范围即可,这个方案可以将验证性能提高5-6 倍,本人亲测。对于复杂且严谨的场景,完全可以进行后处理模式,减少测试误差。
数据提取,工作中大多数接口响应的结果都是JSON
格式(或者对象),但是在绝大多数工具中和框架使用中,都会讲响应转成String
格式,然后通过正则表达式提取响应结果中的数据。这个在JMeter 吞吐量误差分析中提到的误差,主要来源就是正则表达式提取数据。除了使用工具和框架提供的正则提取功能以外,我们还能通过脚本语言自带的正则功能提取,会比工具本身减少一部分性能消耗,如文章Java 和 Groovy 正则使用中提到的Groovy
正则,使用简单,虽然没有本质上的提升,但也不失为一种替换方案。因为工具原生方案具有更强的兼容性,再加上表达式和响应结果的不可预期性,锁需要处理的情况就多,免不了会采取一些牺牲性能的方案。
在进行数据处理中,一般都要避免使用正则,不管在工具还是脚本中,比如要提取内容是固定的位置,那么直接通过index
切割字符串即可得。如果是需要逻辑判断的,那么直接使用脚本语言进行多次判断也是可以的。不到最后一刻,万不可轻言使用正则。
PS:正则表达式写的有BUG
的话,性能自然就更拉胯了。
异常处理
在性能测试中,除了工具和框架会进行一些失败的处理意外。我们还需要自己对响应结果进行断言处理,包括上面提到的正则表达式提取数据然后再对比期望值。在性能测试过程当中,免不了会有一些请求发生错误或者响应结果达不到预期,程序自然会抛出一个异常,通常情况下,工具和框架都会进行捕获也异常处理。
异常处理又会消耗较多时间,之前做过一个测试,一个Java
从捕获到空处理,单线程模式下消耗了300ms
的时间,如果是性能测试这种多线程模式中,消耗可能会更多,再加上可能会发生相当数量的错误,所以这也是测试误差的重要来源之一。
非同步结束
由于个人偏好,我一般使用固定线程请求模型时,都会选择固定请求次数的模式进行测试脚本的编写,就是每个线程执行固定的请求次数。理论上讲,由于线程之间都是公平的,所以将会在执行相同次数的请求任务之后,同时到达结束条件,从而结束整个测试任务。
但是现实总不如愿,大多数时候所有线程并不是在同一时间结束。在之前我的测试方案中,每一个线程都对应着不同的测试用户,甚至不同的测试参数。这就更加剧了线程间请求响应时间的差别了。比如在某个查询列表接口中,A 用户总计100
条数据,B 用户拥有1000
条数据,查询效率自然就不一样,叠加上N
多次的请求,差别就会更大。