在一个项目短链服务性能测试时,发现一个因为测试客户端造成的吞吐量异常波动的情况,最终原因是在域名多ip的情况下,测试客户端处理不当造成的。但涉及到的知识面包括JVM、DNS缓存,所以来详细说一下。
选用的测试工具是 grinder,grinder是纯java实现的负载测试工具。
并发策略,并发1个进程,10个线程,测试10分钟。
问题表现:测试发现整体性能还算平稳,但是吞吐量毛刺比较多,且波动浮动比较大。如下图表现:
根据图示中的 Transaction per second 波动曲线发现每个20~30s左右就会出现一次抖动,抖动幅度较大,tps成倍的下降。
这个时候观察两个nginx节点,发现每隔20~30s两个nginx会切换一次,也就是说同一时间只有1个nginx是有压力的,过20~30s之后,压力切换到另一个nginx上。
显然这种情况是不合理的,两个nginx节点是均等的,压力也应该是均衡的,因此怀疑负载均衡会有问题,于是开始分析为什么Nginx会出现这个情况。
分析思路1:是否是DNS负载均衡有问题?
DNS解析多ip的策略是依次返回两个IP顺序为:AB,BA,AB...以此类推,得验证这个机制是否生效。
于是设置java启动参数:-Dsun.net.inetaddr.ttl=0 也即是不缓存ip,每次都进行DNS解析,tcpdump查看数据包实际情况。从dump数据来看,每次域名解析返回的ip地址顺序没问题,是均衡的。所以问题不在Nginx这端,那就可能是客户端使用ip的时候出现了问题。
11:48:11.333446 IP 10.122.104.7.domain > 10.122.104.134.59030: 45250 2/6/6 A 221.222.198.143, A 221.222.198.142 (269) 11:48:11.333558 IP 10.122.104.7.domain > 10.122.104.134.59030: 36926 0/1/0 (79) 11:48:11.333804 IP 10.122.104.134.41170 > 10.122.104.7.domain: 64301+ A? xxx.im. (24) 11:48:11.333816 IP 10.122.104.134.41170 > 10.122.104.7.domain: 19384+ AAAA? xxx.im. (24) 11:48:11.334223 IP 10.122.104.7.domain > 10.122.104.134.41170: 19384 0/1/0 (79) 11:48:11.334393 IP 10.122.104.7.domain > 10.122.104.134.41170: 64301 2/6/6 A 221.222.198.142, A 221.222.198.143 (269) 11:48:11.334641 IP 10.122.104.134.38868 > 10.122.104.7.domain: 784+ A? xxx.im. (24) 11:48:11.334653 IP 10.122.104.134.38868 > 10.122.104.7.domain: 33591+ AAAA? xxx.im. (24) 11:48:11.335108 IP 10.122.104.7.domain > 10.122.104.134.38868: 33591 0/1/0 (79) 11:48:11.335268 IP 10.122.104.7.domain > 10.122.104.134.38868: 784 2/6/6 A 221.222.198.143, A 221.222.198.142 (269) 11:48:11.335344 IP 10.122.104.134.38853 > 10.122.104.7.domain: 3519+ PTR? 7.104.120.10.in-addr.arpa. (43) 11:48:11.335574 IP 10.122.104.134.56540 > 10.122.104.7.domain: 12283+ A? xxx.im. (24) 11:48:11.335587 IP 10.122.104.134.56540 > 10.122.104.7.domain: 36627+ AAAA? xxx.im. (24) 11:48:11.335677 IP 10.122.104.7.domain > 10.122.104.134.38853: 3519 NXDomain 0/1/0 (102) 11:48:11.335873 IP 10.122.104.134.48745 > 10.122.104.7.domain: 26073+ PTR? 134.104.120.10.in-addr.arpa. (45) 11:48:11.335922 IP 10.122.104.7.domain > 10.122.104.134.56540: 36627 0/1/0 (79) 11:48:11.336123 IP 10.122.104.7.domain > 10.122.104.134.56540: 12283 2/6/6 A 221.222.198.142, A 221.222.198.143 (269) 11:48:11.336178 IP 10.122.104.7.domain > 10.122.104.134.48745: 26073 NXDomain 0/1/0 (104) 11:48:11.336378 IP 10.122.104.134.54979 > 10.122.104.7.domain: 37093+ A? xxx.im. (24)
分析思路2:是否是java测试客户端jvm DNS缓存问题?
在域名解析为ip地址后,资源ip地址会保存到JVM缓存中。
查看了google大致有两种说法:
1. jdk1.5和1.5之前的版本默认情况下,-Dsun.net.inetaddr.negative.ttl=-1 永久缓存。也就是应用启动之后第一次DNS解析成功后结果会一直cache。
2. jdk 1.6以后与security manager策略有关。安全策略配置文件,
JAVA_HOME/jre/lib/security/java.security
如果没有启用security manager ,默认DNS 缓存时间30秒。
# The Java-level namelookup cache policy for successful lookups: # # any negative value: caching forever # any positive value: the number of seconds to cache an address for # zero: do not cache # # default value is forever (FOREVER). For security reasons, this # caching is made forever when a security manager is set. When a security # manager is not set, the default behavior is to cache for 30 seconds. # # NOTE: setting this to anything other than the default value can have # serious security implications. Do not set it unless # you are sure you are not exposed to DNS spoofing attack. # #networkaddress.cache.ttl=-1
那么,DNS解析到的多条记录是采用轮询策略,还是其它策略?
参考了Google上一篇文章,文章里试验数据说明:
在缓存有效期内,取到的IP永远是缓存中全部A记录的第一条,并没有轮循之类的策略。
缓存失效之后重新进行DNS解析,因为每次域名解析返回的A记录顺序会发生变化,所以缓存中的数据顺序也变了,取到的IP也变化,这和本次测试的现象吻合。
找到原因后,验证多进程多线程的并发策略,结果如下:
测试之后发现tps波动趋于稳定,由此可见这个性能问题,是由于测试客户端在域名多ip的情况下,单进程并发模式造成的,所以我们测试同学在测试时候不要完全相信现有工具,比如你用jmeter做负载,那么多进程的问题jmeter就没法实现,这时候需要我们具备多成熟工具的使用经验或者是自己coding实现多进程多线程的压测请求。
思考:不进行多线程并发,仅改变客户端的java.security配置,networkaddress.cache.ttl=0 进行测试,会有什么样的结果?