热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

注意:你LR的HTTP长连接可能是假的

1.问题发现在给上海某行做的一个HTTPPOST挡板服务程序过程中,自测时发现Loadrunner客户端显示的响应时间比预期的长很多。LR压测脚本和场景描述如下:
1. 问题发现

在给上海某行做的一个HTTP/POST挡板服务程序过程中,自测时发现Loadrunner客户端显示的响应时间比预期的长很多。

LR压测脚本和场景描述如下:

脚本:

vuser_init.c : 

char * transname = "lr_post_keepalive";
vuser_init()
{
	web_enable_keep_alive();
	return 0;
}

Action.c : 

lr_start_transaction(transname);
//web_reg_save_param("ALL","LB=","RB=",LAST);
web_custom_request("httpmocker",
	"Method=POST", 
    "URL=http://localhost:8080/httpmocker/httpmocker", 
	"EncType=application/json;charset=utf-8", 
	"RecCOntentType=application/json;charset=utf-8",
    "Body={\""
	"   "
	"   "
	"      "
	//..................(字串内容省略n行)
	"   "
	"",
    "TargetFrame=", 
LAST ); 

lr_end_transaction(transname, LR_AUTO);
//lr_output_message(lr_eval_string("{ALL}"));

vuser_end.c : 

vuser_end()
{
	return 0;
}

Action.c中的字串为固定字串,长度大约是2KB,挡板程序收到该报文后根据配置信息中加载的回挡模板内容进行回挡处理(此处配置是对两个模板进行循环迭代,挡板程序的配置参考文档<性能测试组HTTP通用报文挡板使用说明.docx>>)。LR脚本和服务器在调试通过后均关闭日志,以使客户端和服务器性能不损失。

压力环境为 :服务端和压力发起机均为一台4C/8G的笔记本电脑(我自己的哈,键盘都不完整了!)。

为保证CPU和内存资源够用,对场景做了些限制,增加pacing设置,所以:LR场景设置为 :50VU并发,pacing=20毫秒(目标TPS=2500),运行时长为10分钟。

得到交易平均响应时间如下:即平均响应时间达到39毫秒(TPS均值为623),超过了我们pacing的控制值。

  

同时,在稳定阶段,发现后端挡板程序输出的处理时间平均不到1毫秒:如下图,图中Handle表示服务端接收post报文并返回模板配置信息的总体时间(单位毫秒),即纯服务端处理的时间;Delay表示服务端挡板配置的延迟时间(测试中延迟设置为0)。

  

很显然,客户端发送和收到响应的时间远远大于服务端处理的耗时。而脚本中web_enable_keep_alive()的是长连接,貌似不会在Action迭代中进行http重连了,所以是网络传输问题么?

2. 问题定位

通过观察网络发现,根本没有流量通过,因为这里服务端和客户端都在一台机器上,这也符合预期。所以,这里根本不是网络传输问题。

再通过netstat -ano|findstr 8080观察发现,客户端有大量连接在主动关闭(连接进入TIME_WAIT状态),同时有新的连接在ESTABLISHED,所以这个web_enable_keep_alive()好像和预期的不符啊(没有长连的作用),那么后面的post请求其实是每次迭代都要建立TCP连接的,不难想象客户端看到的耗时为什么占了交易时间的绝大部分。

后来注释掉init脚本中的web_enable_keep_alive()函数再次进行测试,看到的效果一样,由此看来此处这种用法是没有任何作用的。

其实,http连接是没有长短之分的,而我们http处理代码里面的一般的请求头中的keep-alive是对上层的TCP连接来说的,即:如果你要多次请求(在LR里的话,就是一个Action里多次使用web_urlweb_custom_requestweb_submit_data等函数)或请求的页面包含多个资源,比如一般的页面都含有图片资源,css文件,js文件等静态资源,这时keep-alive可以让你的若干请求和响应在同一个连接中去完成。我们这里LR脚本的本意是要一次连接,并keep alive然后通过Action迭代进行若干次post请求,可事与愿违——这个HTTP长连接是假的

3.SK对比测试

因为我们手头上有自己的一款性能测试工具:SharpKnife(简称SK),所以经常会找机会把它与神坛上的东西对比。对同样的短连接,我做了个对比测试,结果让我对自己的工具“肃然起敬”(请允许我吹个牛哈!)。

对以上LR脚本在我们SK测试框架下需要实现的代码(脚本)如下(还需要增加apachehttpclient-4.5.2.jarhttpcore-4.4.5.jar):


import java.io.IOException;
import java.net.URI;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.impl.client.BasicCOOKIEStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
//这是SK框架的代码接口
import perftest.sharpknife.inter.JVUser;
public class HttpMockerPostTest implements JVUser{

	StringBuffer sb = new StringBuffer();
	CloseableHttpResponse respOnse= null;
	CloseableHttpClient httpclient = null;
	BasicCOOKIEStore COOKIEStore = null;
	
	public String postMessage(String mockerUrl,StringBuffer sb) {			
		byte[] cOntent= new byte[20480];		
		try{			
			HttpUriRequest postRequest = RequestBuilder.post()
	                .setUri(new URI(mockerUrl))
	                .setHeader("Content-Type","application/x-www-form-urlencoded")
	                .addParameter("", sb.toString())
	                .build();
	        respOnse= httpclient.execute(postRequest);
	        HttpEntity entity = response.getEntity();		        
	        entity.getContent().read(content);	        
	        EntityUtils.consume(entity);
		}catch(Exception e){
			e.printStackTrace();
        	} finally {
        	}
		//调试时查看结果返回
		//return (new String(content));
		return null;
	}

	@Override
	public int start(Map arg0, int arg1) {		
		COOKIEStore = new BasicCOOKIEStore();
        	httpclient = HttpClients.custom().setDefaultCOOKIEStore(COOKIEStore).build();  
        
		sb = new StringBuffer("{\""
				+"   "
				+"   "				
				//............此处省略n行字串组合
				+"   ");
		return 0;
	}

	@Override
	public String action(Map arg0, int arg1) {
		return postMessage((String) arg0.get("param1"),sb);
	}

	@Override
	public int exit(Map arg0, int arg1) {
		try {
			if(null!=response)
				response.close();
			if(null!=httpclient)
				httpclient.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		sb = null;
		return 0;
	}
}

测试代码套路如下:

1.覆盖start(Map arg0, int arg1)方法。这相当于LR里面的init方法,用来初始化一些全局变量。

2.覆盖action(Map arg0, int arg1)方法。这相当于LR里面的Action.c的作用,用来进行业务迭代。

3.覆盖exit(Map arg0, int arg1)方法。这相当于LR里面的end部分,用来释放资源。

其中,arg0是参数集合,arg1vu唯一标识号,与arg0结合使用,可以获取schedule调度文件中配置的特定参数。

把以上代码打包,放到SK工具的代理上,编辑一个调度schedule,即可运行压力测试。

本工具B/S版具体使用方法参考线上demo :http://105.115.108.120:6781/,此次测试是基于本工具的C/S版。

在同样是短连接的情况下(注意SK实现代码里post之前没有keep alive),SK工具代理端POST请求的响应时间平均是0.81毫秒(从响应时间上看,SK工具比LR提升约40倍,这是我前面提到“肃然起敬”的原因),相同的代理机压力发起能力为2566TPS,达到设定的预期值250050并发/0.02秒的迭代间隔时间=2500TPS)。


尽管SK工具的响应时间最高还是有比较高的(4毫秒算高么?),但效果对比非常明显:LRHTTP短连接请求性能比apache httpclient在我们SK工具中实现的短连接请求差了很多。

LR短连接比不了,那么它能不能做到HTTP长连接呢?LRhttp长连接肯定能提高性的,但这里如何做到呢?请继续阅读本文第4部分。


4. LR脚本的改进

根据HTTP长连接的本质可以看出,其目的是要实现一次连接,多次请求和回应,而根据上面的实验可以发现LRAction的一次迭代是一个创建和关闭TCP连接的全过程,即每次的Action迭代是在不同的TCP连接里处理的HTTP请求和响应。

那么,如果在一个Action里去实现多次http请求就可以实现长连接的目的了。所以,对Aciton里面实现POST请求的web_custom_request()方法添加for循环就可以了。修改后Action.c代码如下:

Action()
{
	int k = 0;
	//web_reg_save_param("ALL","LB=","RB=",LAST);
	for(k=0;k<10000000;k++){
		lr_think_time(0.020);
		lr_start_transaction(transname);	
		web_custom_request("httpmocker",...........,LAST);//此方法内内容同前
		lr_end_transaction(transname, LR_AUTO);		
	}
	//lr_output_message(lr_eval_string("{ALL}"));
	return 0;
}

脚本中把循环次数设定为一个很大的整数,让循环来代替Action的迭代,从而使TCP连接不从客户端主动关闭。

下面截图是最初脚本和修改后脚本TCP连接的截图:

修改前,设置的长连接无效,客户端TCP连接端口在不断变化:

修改后,连接保持不变,如图:


修改后,同样并发压力场景的平均响应时间达到SK工具的水平(平均0.001秒即1毫秒,从图中可以看到实际是在0.8毫秒以上,略高于SK平均响应时间):


TPS图像上看脚本修改后50并发的TPS均值接近2400(不算两头),低于拟设定的2500(这是LR不稳定造成的,即在全场景中还有很多响应时间超过了pacing值造成的误差,比如响应时间图中显示的最大值达到了366毫秒,而这个最大值受图像刻度的影响在响应时间图上却没有体现):


5.本文总结

下面通过几个问答来总结本文想要告诉大家的一些经验:

1. 测试中,服务器CPU等资源都还比较空闲,交易响应时间却比较长,为什么呢?

服务端资源空闲表明服务器处理能力没有饱和,不存在存储IO等问题,客户端看到的交易响应时间如果比较长的话,要考虑来回网络的延迟及网络连接的开启和关闭时间可能存在的问题,看客户端网络TCP层的缓冲是否够用。

2. LR的常见函数你深入考察了么?

LoadRunner性能测试工具对大部分协议进行了封装,方便了初级用户的使用,好多情况下你所从函数字面量理解功能可能并没有实现,要通过多种方法去验证。

3. 怎样识别客户端压力不足的问题?

压测过程中关注客户端的资源利用率和稳定性非常重要,一般压力机的CPU使用率不要超过70%,如果有文件读写的话磁盘平均响应时间应在15毫秒以下,网络延迟平均时间也不要超过10毫秒,如果达到40毫秒以上则你所处的网络环境很不理想。很多环境是使用虚拟机来发生压力的,在空闲状态下应关注虚拟机的稳定性,尽量使用Unix操作系统的虚拟机,如果是JDK那么应该选择64位的最新版。




推荐阅读
  • 深入探索HTTP协议的学习与实践
    在初次访问某个网站时,由于本地没有缓存,服务器会返回一个200状态码的响应,并在响应头中设置Etag和Last-Modified等缓存控制字段。这些字段用于后续请求时验证资源是否已更新,从而提高页面加载速度和减少带宽消耗。本文将深入探讨HTTP缓存机制及其在实际应用中的优化策略,帮助读者更好地理解和运用HTTP协议。 ... [详细]
  • 本文是Java并发编程系列的开篇之作,将详细解析Java 1.5及以上版本中提供的并发工具。文章假设读者已经具备同步和易失性关键字的基本知识,重点介绍信号量机制的内部工作原理及其在实际开发中的应用。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • Spring框架中枚举参数的正确使用方法与技巧
    本文详细阐述了在Spring Boot框架中正确使用枚举参数的方法与技巧,旨在帮助开发者更高效地掌握和应用枚举类型的数据传递,适合对Spring Boot感兴趣的读者深入学习。 ... [详细]
  • 本文介绍了如何利用Struts1框架构建一个简易的四则运算计算器。通过采用DispatchAction来处理不同类型的计算请求,并使用动态Form来优化开发流程,确保代码的简洁性和可维护性。同时,系统提供了用户友好的错误提示,以增强用户体验。 ... [详细]
  • 优化后的标题:深入探讨网关安全:将微服务升级为OAuth2资源服务器的最佳实践
    本文深入探讨了如何将微服务升级为OAuth2资源服务器,以订单服务为例,详细介绍了在POM文件中添加 `spring-cloud-starter-oauth2` 依赖,并配置Spring Security以实现对微服务的保护。通过这一过程,不仅增强了系统的安全性,还提高了资源访问的可控性和灵活性。文章还讨论了最佳实践,包括如何配置OAuth2客户端和资源服务器,以及如何处理常见的安全问题和错误。 ... [详细]
  • 利用爬虫技术抓取数据,结合Fiddler与Postman在Chrome中的应用优化提交流程
    本文探讨了如何利用爬虫技术抓取目标网站的数据,并结合Fiddler和Postman工具在Chrome浏览器中的应用,优化数据提交流程。通过详细的抓包分析和模拟提交,有效提升了数据抓取的效率和准确性。此外,文章还介绍了如何使用这些工具进行调试和优化,为开发者提供了实用的操作指南。 ... [详细]
  • 本文详细介绍了一种利用 ESP8266 01S 模块构建 Web 服务器的成功实践方案。通过具体的代码示例和详细的步骤说明,帮助读者快速掌握该模块的使用方法。在疫情期间,作者重新审视并研究了这一未被充分利用的模块,最终成功实现了 Web 服务器的功能。本文不仅提供了完整的代码实现,还涵盖了调试过程中遇到的常见问题及其解决方法,为初学者提供了宝贵的参考。 ... [详细]
  • 在Java Web服务开发中,Apache CXF 和 Axis2 是两个广泛使用的框架。CXF 由于其与 Spring 框架的无缝集成能力,以及更简便的部署方式,成为了许多开发者的首选。本文将详细介绍如何使用 CXF 框架进行 Web 服务的开发,包括环境搭建、服务发布和客户端调用等关键步骤,为开发者提供一个全面的实践指南。 ... [详细]
  • 在使用Eclipse进行调试时,如果遇到未解析的断点(unresolved breakpoint)并显示“未加载符号表,请使用‘file’命令加载目标文件以进行调试”的错误提示,这通常是因为调试器未能正确加载符号表。解决此问题的方法是通过GDB的`file`命令手动加载目标文件,以便调试器能够识别和解析断点。具体操作为在GDB命令行中输入 `(gdb) file `。这一步骤确保了调试环境能够正确访问和解析程序中的符号信息,从而实现有效的调试。 ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • 为了在Hadoop 2.7.2中实现对Snappy压缩和解压功能的原生支持,本文详细介绍了如何重新编译Hadoop源代码,并优化其Native编译过程。通过这一优化,可以显著提升数据处理的效率和性能。此外,还探讨了编译过程中可能遇到的问题及其解决方案,为用户提供了一套完整的操作指南。 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • Python 程序转换为 EXE 文件:详细解析 .py 脚本打包成独立可执行文件的方法与技巧
    在开发了几个简单的爬虫 Python 程序后,我决定将其封装成独立的可执行文件以便于分发和使用。为了实现这一目标,首先需要解决的是如何将 Python 脚本转换为 EXE 文件。在这个过程中,我选择了 Qt 作为 GUI 框架,因为之前对此并不熟悉,希望通过这个项目进一步学习和掌握 Qt 的基本用法。本文将详细介绍从 .py 脚本到 EXE 文件的整个过程,包括所需工具、具体步骤以及常见问题的解决方案。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
author-avatar
nuabolalalala4_135
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有