一、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個,但是從圖中可以看到這些執行緒大部分時間都不是處於真正的執行狀態,而是處於監視狀態,由此懷疑伺服器端應用程式多執行緒並行處理時可能遇到了同步鎖爭搶。
未完待續........(中間省略的部分請檢視原書)
從程式碼中可以看到,這段程式碼使用同步鎖來保證插入到資料中的使用者賬號不會重複,每次插入前都需要先查詢資料庫中是否存在該賬號,如果不存在才插入,同步鎖是用來保證並行呼叫時執行緒安全的,確保資料庫中不會出現重複的髒資料。
針對上述情況,分析總結如下:
本文作者:張永清, 轉載請註明: https://www.cnblogs.com/laoqing/p/16339979.html 來源於部落格園 ,本文摘選自《軟體效能測試分析與調優實踐之路》