一、JMeter 如何通过自定义Sample来压测RPC服务

RPC(Remote Procedure Call)俗称远程过程调用,是常用的一种高效的服务调用方式,也是性能压测时经常遇到的一种服务调用形式。常见的RPC有GRPC、Thrift、Dubbo等。这里以GRPC为例介绍在JMeter中如何添加自定义的Sample来压测GRPC服务,JMeter中提供的Sample如下图所示,从中可以看到并没有我们需要压测GRPC的Sampler。

本文作者:张永清, 转载请注明: https://www.cnblogs.com/laoqing/p/16339979.html  来源于博客园 ,本文摘选自《软件性能测试分析与调优实践之路》

但是从图中可以看到,JMeter中提供了Java 请求Sample,因此我们可以编写一个自定义的Java请求的Sample来实现GRPC调用,由于需要自定义,自然就需要新建一个Java语言的Maven项目,在项目中引入如下jar包依赖,jar包的版本需要跟压测时的JMeter工具版本保持一致。由于笔者用的JMeter工具的版本是3.0,所以如下依赖包选择的也是3.0版本。由于本节需要一些Java语言和Maven项目管理的基础,所以对于这块不熟悉的读者可以预先阅读一些关于这块的基础书籍。

<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_java</artifactId>
<version>3.0</version>
</dependency>

项目中除了需要增加JMeter的依赖外,还需要增加GRPC的依赖,Maven项目完整的pom内容如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jmeter.tools</groupId>
<artifactId>jmeter-grpc</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<properties>
<grpc.version>1.27.0</grpc.version>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.jmeter/ApacheJMeter_java -->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_java</artifactId>
<version>3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.jmeter/ApacheJMeter_core -->
<dependency>
<groupId>org.apache.jmeter</groupId>
<artifactId>ApacheJMeter_core</artifactId>
<version>3.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<skip>true</skip>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}</outputDirectory>
<overWriteReleases>true</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<useSubDirectoryPerType>true</useSubDirectoryPerType>
<includeArtifactIds>
guava
</includeArtifactIds>
<silent>true</silent>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
<defaultGoal>compile</defaultGoal>
</build>
</project>

编写一个自定义的Java请求Sample,只需要实现JMeter提供的JavaSamplerClient接口即可,如下所示。

本文作者:张永清, 转载请注明: https://www.cnblogs.com/laoqing/p/16339979.html  来源于博客园 ,本文摘选自《软件性能测试分析与调优实践之路》

import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult; public class ExampleSample implements JavaSamplerClient {
@Override
public void setupTest(JavaSamplerContext javaSamplerContext) {
//初始化方法,对数据进行初始化,该方法只会执行一次
} @Override
public SampleResult runTest(JavaSamplerContext javaSamplerContext) {
//Sample的请求的具体实现
return null;
} @Override
public void teardownTest(JavaSamplerContext javaSamplerContext) {
//数据或者资源销毁接口,一般用于压测停止时,需要做的动作。
} @Override
public Arguments getDefaultParameters() {
//参数设置方法,一般用于设置传递参数
return null;
}
}

JMeter提供的JavaSamplerClient接口需要实现的四个方法,如下表所示。

表: JavaSamplerClient接口需要实现的四个方法说明

方法

描述

setupTest(JavaSamplerContext javaSamplerContext)

初始化方法。一般用于对数据进行初始化。性能压测时该方法只会被执行一次,方法体里面的内容可以为空

runTest(JavaSamplerContext javaSamplerContext)

Sample请求的具体实现。比如调用GRPC服务就需要在该方法中编写调用GRPC服务的代码

teardownTest(JavaSamplerContext javaSamplerContext)

用于数据或者资源销毁的方法。一般用于压测停止时,需要执行的数据或者资源的释放动作。性能压测时该方法也只会被执行一次,方法体里面的内容同样可以为空

getDefaultParameters()

参数设置方法。一般用于设置传递的参数

GRPC示例:以传入用户名和密码进行用户注册的GRPC服务作为示例,该GRPC接口请求输入和响应输出都是JSON的文本形式,GRPC服务的proto文件内容如下(proto是GRPC提供的接口协议定义标准文档):

syntax = "proto3";
package com.zyq.example.cas.management.grpc;
message RequestData {
string text = 1;
}
message ResponseData {
string text = 1;
}
service StreamService {
//rpc服务的方法
rpc SimpleFun(RequestData) returns (ResponseData){}
}

服务接口详细说明如下表示。

表:  服务接口详细说明

参数

说明

RequestData

定义了文本类型的参数用于GRPC服务的请求入参使用,比如传入JSON: {"userAccount":"zyq","password":"mima"}

ResponseData

定义了文本类型的参数用于请求响应使用,用于存储GRPC服务调用后响应的文本内容

StreamService

定义了一个GRPC服务,并且服务里面包含了SimpleFun这个方法,方法中请求传入RequestData,调用完成后返回ResponseData

本文作者:张永清, 转载请注明: https://www.cnblogs.com/laoqing/p/16339979.html  来源于博客园 ,本文摘选自《软件性能测试分析与调优实践之路》

请求调用过程如下图所示。

服务器的配置信息如下表所示。

表:  服务器的配置说明

服务器类型

配置说明

应用服务器(GRPC)

内存:2G

CPU:4核

部署软件:GRPC Java应用服务、JDK1.8

操作系统:CentOS7

数据库服务器

内存:2G

CPU:2核

部署软件:MySQL

操作系统:CentOS7

本文作者:张永清, 转载请注明: https://www.cnblogs.com/laoqing/p/16339979.html  来源于博客园 ,本文摘选自《软件性能测试分析与调优实践之路》

 

笔者这里自己实现的GRPC服务的Sample具体示例代码如下:

import com.cf.cas.management.grpc.Example;
import com.cf.cas.management.grpc.StreamServiceGrpc;
import com.google.gson.Gson;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult; import java.util.HashMap;
import java.util.Map; /**
* Created by zyq on 2020/3/4.
*/
public class GrpcJmeter implements JavaSamplerClient {
private String userAccount;
private String password;
private String address;
private Integer port;
@Override
public void setupTest(JavaSamplerContext javaSamplerContext) { }
@Override
public SampleResult runTest(JavaSamplerContext javaSamplerContext) {
SampleResult results = new SampleResult();
userAccount = javaSamplerContext.getParameter("userAccount"); // 获取在JMeter中设置的参数值
password = javaSamplerContext.getParameter("password"); // 获取在JMeter中设置的参数值
address = javaSamplerContext.getParameter("address"); // 获取在JMeter中设置的参数值
port =Integer.valueOf(javaSamplerContext.getParameter("port")) ; // 获取在JMeter中设置的参数值
results.sampleStart();// JMeter 开始统计响应时间标记
ManagedChannel channel=null;
try {
//grpc调用的具体实现
channel = ManagedChannelBuilder.forAddress(address, port).usePlaintext().build();
StreamServiceGrpc.StreamServiceBlockingStub stub = StreamServiceGrpc.newBlockingStub(channel);
Map<String,Object> map = new HashMap<>();
map.put("userAccount",userAccount);
map.put("password",password);
Gson gson = new Gson();
Example.RequestData requestData = Example.RequestData.newBuilder().setText(gson.toJson(map)).build();
Example.ResponseData responseData = stub.simpleFun(requestData);
//设置请求的数据,这里设置后,在JMeter的察看结果树中才可显示
results.setRequestHeaders(gson.toJson(map));
if(null!=responseData && null!=responseData.getText() && responseData.getText().contains("success")){
results.setSuccessful(true);
}
else {
results.setSuccessful(false);
}
//设置响应的数据,这里设置后,在JMeter的察看结果树中才可显示
results.setResponseMessage(responseData.getText());
results.setResponseData(responseData.getText(),"UTF-8");
} catch (Exception e) {
results.setSuccessful(false);
e.printStackTrace();
}
finally {
if(null!=channel){
channel.shutdown();
}
results.sampleEnd();// JMeter 结束统计响应时间标记
}
return results;
}
@Override
public void teardownTest(JavaSamplerContext javaSamplerContext) {
}
@Override
public Arguments getDefaultParameters() {
Arguments params = new Arguments();
params.addArgument("userAccount", "zyq");//设置参数,并赋予默认值
params.addArgument("password", "111");//设置参数,并赋予默认值
params.addArgument("address", "127.0.0.1");//设置参数,并赋予默认值
params.addArgument("port", "8883");//设置参数,并赋予默认值
return params;
}
}

  本文作者:张永清, 转载请注明: https://www.cnblogs.com/laoqing/p/16339979.html  来源于博客园 ,本文摘选自《软件性能测试分析与调优实践之路》

示例编写完成后,执行Maven项目打包命令mvn assembly:assembly,即可生成性能压测时需要放入JMeter中的jar包,如下图所示。

将生成的jmeter-grpc-1.0-SNAPSHOT.jar放入JMeter工具的apache-jmeter-3.0\apache-jmeter-3.0\lib\ext目录下,如下图所示,JMeter的ext目录专门用于存放扩展的JMeter自定义jar包。

放入后打开JMeter工具,在添加Java请求Sample后,即可看到我们自己编写的自定义GRPC服务Sample了,如下图所示。

在JMeter工具中执行请求调用后,即可在察看结果树这个JMeter元件中看到请求调用的结果,如下所示。

由此可见,JMeter支持的功能其实非常强大,理论上只要Java语言可以调用的服务都可以使用JMeter来做性能压测。

二、JMeter对GRPC服务的性能压测分析与调优

在添加完GRPC服务的Sample后,我们在上图的基础上,增加Summary Report、聚合报告、图形结果、响应断言、计数器这几个JMeter元件,以辅助我们做性能压测。其中计数器是本次用来辅助做参数化的,如下图所示,在图中userAccount和password这两个参数都用到了计数器产生的counter变量来构造数据,由于计数器是递增的,所以保证了构造出来的数据不会重复。

JMeter的性能压测脚本准备完成后,采用10个并发用户开始进行压测,如下图所示。

 未完待续........(中间省略的部分请查看原书)

使用jvisualvm工具,查看jvm进程的线程运行情况如下图所示。可以看到由于是10个并发用户,所以GRPC服务端的默认执行线程也是10个,但是从图中可以看到这些线程大部分时间都不是处于真正的运行状态,而是处于监视状态,由此怀疑服务端应用程序多线程并发处理时可能遇到了同步锁争抢。

未完待续........(中间省略的部分请查看原书)

从代码中可以看到,这段代码使用同步锁来保证插入到数据中的用户账号不会重复,每次插入前都需要先查询数据库中是否存在该账号,如果不存在才插入,同步锁是用来保证并发调用时线程安全的,确保数据库中不会出现重复的脏数据。

针对上述情况,分析总结如下:

  •         代码中虽然使用了同步锁保证了线程安全,使数据库中不出现重复的脏数据,但是却影响了多线程并发时的性能。而且此种线程安全只能适用单个应用服务器节点的部署情况,如果是分布式的多个节点部署方案,则此种同步锁无法奏效,此时一般需要借助分布式同步锁,比如借助Redis、Zookeeper来实现分布式同步锁。但是使用这种分布式同步锁,其并发性能一般也很低效。
  •         除了使用同步锁来保证数据不重复插入这种方式外,还可以使用数据库的唯一索引来保证数据库的数据唯一。比如针对本示例中的情况,可以对数据库表中的用户账号字段建立唯一索引,确保不重复插入,虽然使用唯一索引后,数据库肯定会有性能消耗,但是在数据量不是非常大的时候,这种方式性能效果应该更佳,而且由于需要根据用户账号查询,所以在查询时,也是需要索引来提高查询效率。
  •         针对数据库中用户表中的数据量非常大的情况,还可以采用分表的方案。比如可以针对用户账号基于某种算法做分表处理,确保同一个用户账号采用算法计算时每次都是进入同一个表中,这样还是可以对每张分表中的用户账号字段建立唯一索引来提高性能。

本文作者:张永清, 转载请注明: https://www.cnblogs.com/laoqing/p/16339979.html  来源于博客园 ,本文摘选自《软件性能测试分析与调优实践之路》

未完待续,更多内容  

备注:作者的原创文章,转载须注明出处。原创文章归作者所有,欢迎转载,但是保留版权。对于转载了博主的原创文章,不标注出处的,作者将依法追究版权,请尊重作者的成果。

关于软件性能分析调优,可以加微信号yq597365581或者微信号hqh345932,进入专业的性能分析调优群进行交流沟通。

软件性能测试分析与调优实践之路-JMeter对RPC服务的性能压测分析与调优-手稿节选的更多相关文章

  1. RocketMQ性能压测分析(转载)

    一   机器部署 1.1  机器组成 1台nameserver 1台broker  异步刷盘 2台producer 2台consumer   1.2  硬件配置 CPU  两颗x86_64cpu,每颗 ...

  2. RocketMQ性能压测分析(转)

    原创文章,转载请注明出处:http://jameswxx.iteye.com/blog/2093785 一   机器部署 1.1  机器组成 1台nameserver 1台broker  异步刷盘 2 ...

  3. 软件性能测试分析与调优实践之路-Web中间件的性能分析与调优总结

    本文主要阐述软件性能测试中的一些调优思想和技术,节选自作者新书<软件性能测试分析与调优实践之路>部分章节归纳. 在国内互联网公司中,Web中间件用的最多的就是Apache和Nginx这两款 ...

  4. 软件性能测试分析与调优实践之路-Java应用程序的性能分析与调优-手稿节选

    Java编程语言自从诞生起,就成为了一门非常流行的编程语言,覆盖了互联网.安卓应用.后端应用.大数据等很多技术领域,因此Java应用程序的性能分析和调优也是一门非常重要的课题.Java应用程序的性能直 ...

  5. MySQL数据库的性能分析 ---图书《软件性能测试分析与调优实践之路》-手稿节选

    1  .MySQL数据库的性能监控 1.1.如何查看MySQL数据库的连接数 连接数是指用户已经创建多少个连接,也就是MySQL中通过执行 SHOW  PROCESSLIST命令输出结果中运行着的线程 ...

  6. 【原】Nginx添加Content-MD5头部压测分析

    如需转载,必须注明原文地址,请尊重作者劳动成果. http://www.cnblogs.com/lyongerr/p/5048464.html 本文介绍了webbenck安装,但是最后使用的是ab工具 ...

  7. 高并发场景下JVM调优实践之路

    一.背景 2021年2月,收到反馈,视频APP某核心接口高峰期响应慢,影响用户体验. 通过监控发现,接口响应慢主要是P99耗时高引起的,怀疑与该服务的GC有关,该服务典型的一个实例GC表现如下图: 可 ...

  8. HAProxy压测及参数调优

    背景 小米容器云平台,在构建云厂商集群时,需要通过HAProxy将云厂商LB流量从宿主机转到容器中,但对于HAProxy的性能没有把握.参考网上的一篇HAProxy压测文章,文章中提到HAProxy ...

  9. 全链路压测平台(Quake)在美团中的实践

    背景 在美团的价值观中,以“客户为中心”被放在一个非常重要的位置,所以我们对服务出现故障越来越不能容忍.特别是目前公司业务正在高速增长阶段,每一次故障对公司来说都是一笔非常不小的损失.而整个IT基础设 ...

随机推荐

  1. 大数据学习之路之ambari配置(二)

    按照网上的教程配置,发现配置到hadoop虚拟机内存就开始不够了,心累

  2. jboss7学习3-jboss安装 访问(外网)添加用户

    一.下载安装 1.下载地址: http://www.jboss.org/jbossas/downloads ,下载Certified Java EE 6 Full Profile版本. 2.解压 jb ...

  3. MySQL 中 SQL语句大全(详细)

    sql语句总结 总结内容 1. 基本概念 2. SQL列的常用类型 3. DDL简单操作 3.1 数据库操作 3.2 表操作 4. DML操作 4.1 修改操作(UPDATE SET) 4.2 插入操 ...

  4. Android 接入腾讯IM即时通信(详细图文)

    原文地址:Android 接入腾讯IM即时通信(详细图文) | Stars-One的杂货小窝 腾讯云IM官网文档上提供了带UI模块和不带UI模块的,本文是基于带UI模块进行了Module封装,可以方便 ...

  5. 拼写检查-c++

    [问题描述] 作为一个新的拼写检查程序开发团队的成员,您将编写一个模块,用已知的所有形式正确的词典来检查给定单词的正确性.        如果字典中没有这个词,那么可以用下列操作中的一个来替换正确的单 ...

  6. ELK日志保留7天-索引生命周期策略

    一.简介 ELK日志我们一般都是按天存储,例如索引名为"kafkalog-2022-04-05",因为日志量所占的存储是非常大的,我们不能一直保存,而是要定期清理旧的,这里就以保留 ...

  7. python---复杂度、斐波那切数列、汉诺塔

    时间复杂度 ​ 用来估计算法运行时间的一个式子. ​ 一般来说, 时间复杂度高的算法比复杂度低的算法慢. 常见的时间复杂度: ​ O(1) < O(logn) < O(n) < O( ...

  8. ubuntu下安装typora、pycharm、搜狗拼音、MySQL、docker

    安装typora # or run: # sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BA300B7755AFCFAE ...

  9. SSM整合_年轻人的第一个增删改查_基础环境搭建

    写在前面 SSM整合_年轻人的第一个增删改查_基础环境搭建 SSM整合_年轻人的第一个增删改查_查找 SSM整合_年轻人的第一个增删改查_新增 SSM整合_年轻人的第一个增删改查_修改 SSM整合_年 ...

  10. JavaScript基础第02天笔记

    JavaScript基础第02天 1 - 运算符(操作符) 1.1 运算符的分类 运算符(operator)也被称为操作符,是用于实现赋值.比较和执行算数运算等功能的符号. JavaScript中常用 ...