一、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 = https://www.cnblogs.com/laoqing/p/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,進入專業的性能分析調優群進行交流溝通,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/489813.html
標籤:其他
