性能测试组简报

未来已来2018-09-06 13:58

作者:左晴


一、    一次JVM参数调整过程  左琴

JVM参数:
某无状态的服务产品,其内存对象大多为生命周期较短的新对象,JDK版本1.6,JVM内存为4G,垃圾收集器默认(Young区为Parallel Scavenge,Old区为Serial Old)。

调整过程:
按当前JVM配置,在高并发下5min内Full GC频繁达到43次,平均Full GC时间为1s,应用服务TPS为630,波动很大。为减少Full GC次数,应尽量使对象在Young区被回收掉,减少其晋升到老生代引发Full GC的概率。因此将JVM内存开到8G,使得Young区大小增至之前的一倍,在同样的并发下,5min内Full GC 次数6次,平均Full GC时间1s,TPS达到670,性能有所好转。但通过VisulVM查看Young区对象的年龄age基本都为1。

JVM内存分配原则中有一个动态年龄判定,即虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor区所有相同年龄对象的大小总和大于Survivor空间的一半,大于或等于该年龄的对象就可以直接进入老年代。根据如下的GC日志输出可以看出,Survivor区age=1的对象大小约270M,已经远大于Survivor区(约270M)的一半,由此得出绝大部分年轻对象(age=1)直接晋升到了Old区。(PS:age的分布可通过增加-XX:+PrintTenuringDistribution参数输出到GC日志,在使用ParNew收集器时才会生效)

2014-05-08T11:04:57.296+0800:426.748: [GC 426.749: [ParNew Desired survivor size 143163392 bytes,new threshold 1(max 15)
- age 1: 276372392 bytes, 276372392 total: 2516415K->279616K(2516544K), 0.0679290 secs] 7209855K->5192656K(8108992K), 0.0687610 secs] [Times: user=1.16 sys-0.04,real=0.07 secs]

为了尽可能使得年轻对象在Yong区被回收掉,避免其直接晋升到Old区,我们通过修改-XX:SurvivorRatio参数将survivor区大小增大到约为1G左右,再通过VisualVM查看age=1的对象所占比例已远远减小,如此虽然造成了一定的空间浪费,但避免了age很小的对象过早进入Old区,同时分析GC日志得到Full GC次数为0,Young GC次数为102次,平均YoungGC时间为0.1s,TPS增加到800,曲线平稳,性能有较大提升。

小结:
JVM参数调节很难总结出通用的方法,不同服务的业务对象有不同的特点。对于线上服务的JVM参数调节,可以通过分析GC日志找准症结所在,GC日志信息应尽可能详细,比如增加-XX:+PrintTenuringDistribution参数输出对象age的大小分布。其次根据垃圾回收的原理去分析如何解决问题,最后通过调节JVM的相关参数达到一个相对最优的状态。


二、    测试代码性能问题导致瓶颈 肖武

测试过程中会遇到响应时间很长,被测服务器的资源利用率很低的情况。最后定位到是测试客户端的瓶颈,先看两个例子:

1. 测试后台的一个产品,通过SDK方式调用服务,并发100时发现响应时间400ms左右,超过目标指标,服务器总体的负载都很低,CPU利用率5%都不到。对服务器进程jstack后未发现明显异常。降低并发到2,响应时间降低到10ms左右,TPS变化不是很大。观察了下测试机器,资源使用不高,通过jstack发现测试进程的100个线程中有1个线程拥有一个锁,其他的99个线程都在等待这个锁,这样测试机器的并发压力就受到了限制。同时因为锁等待,导致响应时间比较长。接下来就是解决SDK代码中的锁等待问题了。

2. 上传接口的测试程序,多线程使用同一个HttpClient实例,因为HttpClient实例内部的连接池是40.并发增加到100后,TPS基本不变,响应时间变化很大,但是tomcat的访问日志看到的响应时间远小于测试客户端统计到响应时间,对测试进程jstack后发现线程都在等待获取连接,后通过修改测试代码,每个测试线程使用一个HttpClient实例解决。

哪些情况下客户端可能是瓶颈?

1 测试过程中,服务器的资源利用比较低,增加并发后,TPS变化很小,但是响应时间很长,排除了应用服务器和数据库的问题

2.测试客户端观察到请求的响应时间比web服务器上或者应用服务器上的访问日志中记录的响应时间长很多

3.测试客户端的资源使用异常,例如CPU很高,ioutil高


通过上面的例子,总结下通用的定位客户端性能问题的思路

1.观察测试机的资源使用情况,CPU,磁盘,网络。测试进程CPU高的话可以借助VisualVM来定位CPU热点,磁盘繁忙的话可以使用iotop来定位

2.如果测试机的资源利用率也不高,对测试进程jstack下查看线程的状态是不是有大量的waiting、blocked以及是不是有监视器锁等待

3.查看测试进程生成的日志,观察是不是有异常和报错,有的话先解决异常和报错


三、    Tomcat NIO connector高并发参数配置实践 张伟杰

项目需要单台Tomcat支撑2w以上的高并发。为了完成目标,我们使用了Tomcat7,并配置了非阻塞的NIO connector,相比传统的BIO,NIO使用少量线程处理大量连接,可以有效减少内存开销和上下文切换带来的cpu消耗。

为了实现高并发,有几个参数需要注意调整:

1、maxConnections:

Tomcat7新增了这一参数,用来限制客户端和tomcat之间允许建立的最大连接数。当已有连接数超过该值后,新的连接请求将堆积在队列中等待。maxConnections默认值=1w,当需要支撑更高的连接数时,必须调大maxConnections,以避免客户端出现大量连接超时。 建议调整为预期的最大连接数。

2、acceptCount:

如前面所述,新的连接请求可能会堆积在队列中等待接收,队列长度由acceptCount控制(该参数就是ServerSocket的backlog参数的马甲)。acceptCount 默认值=100,高并发场景可以适当增大该值,提高tomcat的缓冲能力。

3、maxThreads:

tomcat工作线程池大小,在阻塞式的BIO connector中,由于一个连接对应一个线程,当并发数上升时,该值常需要设置比较大;使用非阻塞的NIO后,maxThread可设置的比较小。某项目设置maxThread=24,成功支撑了3w+连接。实际操作时,可用profile工具观察线程的工作状态,根据线程的繁忙程度适当调整maxThread大小。

4、processorCache:

该值代表processor对象缓存池大小。对于每个请求,tomcat会优先从缓存池中获取一个processor对象,以执行http request的解析和分派。processorCache默认值=200,对于高并发场景,需要调大processorCache,以避免工作线程频繁新建、销毁processor对象,带来额外的性能开销。更严重的是,由于创建processor过程存在锁,工作线程容易在创建processor时大量阻塞,导致性能严重下降。建议将processorCache调整为预期的最大连接数。


四、    nginx做反向代理时,服务器端口资源耗尽的解决方法  张伟杰

如果开启keepalive后仍存在问题,可以适当增大keepalive取值大小,nginx总的keepalive连接池大小 = keepalive取值 * nginx worker数。 如果以上调整仍不奏效,可以尝试开启tcp_tw_reuse 系统参数(一般不建议开启tcp_tw_reuse,存在一定安全隐患)。


五、    grinder调用shell脚本统计异步类接口的响应时间  史彦诚

对于异步类接口,测试工具grinder无法直接得到异步操作实际完成的延迟时间。需要额外的手段记录下操作实际完成的时间。例如调用创建虚拟机的异步接口后,以能ping通这台虚拟机作为该异步操作完成的时刻,这里采用grinder脚本调用测试机本地shell脚本的方式来实现。

具体步骤:

1. shell脚本实现延迟时间统计。仍以创建虚拟机接口为例,不断ping新创建出的虚拟机,记录下ping通的时间。如下:
nohup ping \-c $timeOut $ip >> ping_record.txt' &
2. 将shell脚本与grinder结合使用:
#导入执行shell语句所需要的类
from java.lang import Runtime
...
request.Post(url)
#发送Post请求后调用shell脚本
Runtime.getRuntime().exec("bash pingServer.sh " + index + " " + ip);

这里需要注意:
1)shell脚本要后台执行,避免影响grinder的并发性;
2)多并发下,shell脚本要使各请求结果能区分开,保证可读性。
3)统计好各异步操作的完成时间后,需另行准备脚本来解析和计算整个测试过程的平均响应时间。


六、    nginx缓存盘IO飙升的原因分析 张翱

某产品线上的前端nginx出现缓存盘IO飙升的问题,可以确定其问题业务为下载文件。在测试环境中通过加大并发来重现,无果。
经过分析,nginx开启了缓存机制,会在内存中为每个连接开辟一个缓冲区,当缓冲区不够时会写入缓存盘中的临时文件。
nginx作为代理,是客户端和后端服务之间的桥梁,一般情况下客户端接收响应和后端服务发送响应的速率是一个收发平衡的过程,由于网络环境的差异,往往nginx和后端服务之间的网络环境很好,响应能迅速发送到nginx,但客户端的类型多种多样,可能是应用服务器,可能是浏览器甚至移动端,它们和nginx间的网络环境就千差万别了,如此就造成了收发速率上巨大的落差,Tomcat中的文件能迅速传到nginx,但是nginx传到客户端就比较慢,导致文件数据在缓冲区中积累,进而大量写缓存盘的临时文件,使其IO飙升。
为了能在测试环境中重现该问题,通过nginx的limit_rate指令限制其返回给客户端的响应速率,确实出现了IO飙升的现象,验证上面一段的原因分析。



网易云大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者左晴授权发布