文章目录
一、摘要
本文主要介绍如何使用jmeter对服务端接口发起http请求,以及如何设置可变参数来发起http请求。
二、背景
新的项目需要了解Phoenix在高并发情况下的查询效率,所以使用Jmeter对phoenix查询进行压力测试。原先我们已经构建了一个自定义查询服务,所以本次测试直接直接向自定义查询服务端发起查询请求,而在执行sql的开始和结束位置记录查询执行的时间即可。而在这期间我们需要通过jmeter来对服务端接口发起查询请求。
三、正文
场景1:使用固定的参数来对服务端接口发起请求
步骤1:新建线程组
打开jmeter,右击测试计划,如下图所示添加线程组。
通过设置如下部分的参数可以调整请求的并发数。
步骤2:添加Http请求
右键线程组,添加Http请求(因为我们主要是对接口发起请求,所以添加的是Http请求)
步骤3:设置接口以及接口参数来段服务端发起请求
步骤4:并添加查看结果数查看请求是否发送成功
1.添加查看结果数
2.发起请求
步骤5:查看服务端日志来记录查询时间
由于服务端的sql查询任务是异步查询的,所以接口返回的只是服务端查询任务的uuid,所以我们需要在服务端添加日志来查看查询的执行时间。所以通过分析查询日志我们即可获得执行phoenix查询的效率。
场景2:使用可变参数来对服务端接口发起请求
场景1中我们已经简单介绍了如何使用Jmeter对服务端发送查询请求,但是存在一个问题,就是每次接口请求的参数值都相同。 /api/execute.do接口存在如下五个参数:
参数名称 | 参数值 | 解释 |
---|---|---|
token | c2a161c742894180862eeabf7019ab72 | 用户标识,服务端会根据该标识判断是否有查询指定表的权限 |
up_time | 1550541354654 | 13位时间戳,查询时间 |
version | V2.3.0 | 版本号 |
query | select imei,sum(count_num) from cn_nubia_processmanager.p_test_charge_statistic_ds WHERE imei in (‘864476020902153’,‘863784024020662’,‘863784024029036’) group by imei | 发送到服务端的查询sql |
sign | dc83641fc74f0e9f1c4b2f39af5d00d1979f5d6b | 名值,根据当前的参数算出的签名值。发送到服务端,服务端会通过请求参数,进行签名校验 |
即便我们随便调整线程数,但是每一个线程发送到服务端的查询sql都是一样的,这显然和实际情况是不相符的。所以我们希望在设置并发数的时候,每一个查询请求发送的查询的sql各不相同。那么我们就需要将query的参数设置成可变的,而与此同时sign的参数也必须是可变的(因为sign的值是通过其它4个参数值计算的)。为了实现上面的需求我们需要通过写java代码来实现,具体的步骤如下所示。
步骤1:新建一个java工程,并将代码打成可执行的jar包
新建工程以及如何打成jar包可以自行百度。
1.签名生成工具类: GenApiSigin.java:
package com.test;
import org.apache.commons.lang3.StringUtils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
/**
* @Author: Vincent
* @Date 2018/4/24
**/
public class GenApiSigin {
private static final String SDK_SERVER_KEY = "customquerytest";
public static String genSign(Map<String, String> argsMap) {
StringBuffer strBuffer = new StringBuffer();
for (String key : argsMap.keySet()) {
if (StringUtils.isNotEmpty(argsMap.get(key))) {
strBuffer.append(key).append("=").append(argsMap.get(key));
}
}
strBuffer.append(SDK_SERVER_KEY);
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
messageDigest.update(strBuffer.toString().getBytes());
byte[] bytes = messageDigest.digest();
StringBuffer hex = new StringBuffer(bytes.length * 2);
for (int i = 0; i < bytes.length; i++) {
if ((bytes[i] & 0xff) < 16) {
hex.append('0');
}
hex.append(Integer.toHexString(0xff & bytes[i]));
}
return hex.toString();
}
}
2.随机sql获取类SqlList.java:
package com.test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* @author Vincent Wu
* @date 2019/2/20 20:35
*/
public class SqlList {
private List<String> list = new ArrayList<String>();
private Random random;
public SqlList() {
init();
random = new Random();
}
public String getRandomSql() {
return list.get(random.nextInt(list.size()));
}
public void init() {
list.add("select imei,sum(count_num) from cn_nubia_processmanager.p_test_charge_statistic_ds WHERE imei in " +
"('864476020902153','863784024020662','863784024029036') group by imei");
/** 此处省略N条查询sql*/
list.add("select imei,sum(count_num) from cn_nubia_processmanager.p_test_charge_statistic_ds WHERE imei in " +
"('355927032711777','864476021102163','862018021313257') group by imei");
}
}
3.封装查询参数QueryParams.java:
package com.test;
import java.util.Map;
import java.util.TreeMap;
/**
* @author Vincent Wu
* @date 2019/2/21 9:17
*/
public class QueryParams {
private final String token;
private final String upTime;
private final String version;
private final String query;
private QueryParams(Builder builder) {
this.token = builder.token;
this.upTime = builder.upTime;
this.version = builder.version;
this.query = builder.query;
}
public static class Builder {
private final String token;
private final String upTime;
private final String version;
private String query;
public Builder(String token, String upTime, String version) {
this.token = token;
this.upTime = upTime;
this.version = version;
}
public Builder setQuery(String query) {
this.query = query;
return this;
}
public QueryParams build() {
return new QueryParams(this);
}
}
public String createSign() {
Map<String, String> argsMap = new TreeMap<>();
argsMap.put("token", this.token);
argsMap.put("up_time", this.upTime);
argsMap.put("version", this.version);
argsMap.put("query", this.query);
return GenApiSigin.genSign(argsMap);
}
}
4.将打包后的jar包,放到apache-jmeter-3.0\lib\ext目录下
步骤2:新建如场景1中的步骤新建http请求
步骤3:添加BeanShell PreProcessor
1.右键新增的http请求,并添加前置处理器BeanShell PreProcessor
2.编写been shell脚本
步骤4:添加查看结果数查看接口请求结
同场景1中的步骤4。
步骤5:编写日志分析工具来分析查询执行效率
因为服务端的输出日志存在一定格式,所以我们可以编写简单的代码分析查询效率。
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author Vincent Wu
* @date 2019/2/21 18:17
*/
public class LogParseTest {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\test\\简单sql\\test.log-2000");
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
List<Long> timeList = new ArrayList<>();
String line;
while ((line = br.readLine()) != null) {
if (line.contains("Query time is --->")) {
String[] arrStr = line.split("Query time is --->");
if (arrStr[1].length() < 10) {
long time = Long.valueOf(arrStr[1].trim());
timeList.add(time);
}
}
}
long min = timeList.get(0);
long max = timeList.get(0);
long sum = 0;
for (long time : timeList) {
if (time > max) {
max = time;
}
if (min > time) {
min = time;
}
sum += time;
}
System.out.println("total--->" + timeList.size());
System.out.println("min--->" + min);
System.out.println("max--->" + max);
System.out.println("avg--->" + sum * 1.0 / timeList.size());
}
}
四、结束语
1.经过压测的结果发现,phoenix支持建立二级索引,并且对于简单的sql查询phoenix的并发效果还是挺不错的。
2.以前对Jmeter不是很了解,这次因为实际项目中有用到。所以写了本文记录了一下,其实Jmeter还有很多功能,等待我后续慢慢去发觉。