使用阿里雲性能測試工具 JMeter 場景壓測 RocketMQ 最佳實踐

需求背景

新業務上線前,我們通常需要對系統的不同中間件進行壓測,找到當前配置下中間件承受流量的上限,從而確定上游鏈路的限流規則,保護系統不因突發流量而崩潰。阿里雲 PTS 的 JMeter 壓測可以支持用戶上傳自定義的 JMeter 腳本,按照自定義的邏輯,藉助 PTS 強大的分佈式壓測能力,對系統的不同中間件進行壓測。下面,將以 JMeter5.5 和 RocketMQ5.0 系列爲例,詳細介紹如何使用 PTS 的 JMeter 場景壓測 RocketMQ。

前置條件

1. 已在本地安裝 JMeter。

2. 已在阿里雲 ECS 上部署 RocketMQ(本文選擇的是一臺 8C32G 規格的 ECS)。

3. 已在阿里雲上開通 PTS 服務。

壓測過程

JMeter 提供了擴展性極強的 JavaSampler,我們可以通過繼承 AbstractJavaSamplerClient 類來自定義在 JavaSampler 中執行的邏輯,從而實現對 RocketMQ 進行壓測。

步驟一:創建 Maven 項目,並引入依賴

1. 新建 Maven 工程,並在 pom 文件中引入下面的依賴:

<dependency>
  <groupId>org.apache.jmeter</groupId>
  <artifactId>ApacheJMeter_java</artifactId>
  <version>5.5</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.apache.rocketmq</groupId>
  <artifactId>rocketmq-client</artifactId>
  <version>4.9.5</version>
</dependency>

ApacheJMeter_java 是 JMeter JavaSampler 的依賴,rocketmq-client 是 RocketMQ 的客戶端依賴(此處用 4.x 版本是因爲 4.x 版本的客戶端可以兼容 5.x 版本的服務端實例,但是 5.x 版本的客戶端不能兼容 4.x 版本的服務端實例,可根據自己需求調整)。其中,要注意的是 ApacheJMeter_java 依賴的 scope 定義爲 provided,JMeter 的 lib/ext 目錄下已有該 JAR 包,因此不必將該依賴一起打包。

2. 在 pom 文件中引入 maven-assembly-plugin 插件,此處使用 “jar-with-dependencies” 打包方式,將項目所需依賴和項目代碼打包到同一個 JAR 包,後續可以只上傳該 JAR 包到 PTS 的 JMeter 環境中,不用上傳多個依賴 JAR 包:

<build>
  <finalName>jmeter-rocketmq4</finalName>
  <plugins>
    <plugin>
      <artifactId>maven-assembly-plugin</artifactId>
      <version>3.4.2</version>
      <configuration>
        <!-- 打包方式 -->
        <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
      </configuration>
      <executions>
        <execution>
          <id>make-assembly</id>
          <phase>package</phase>
          <goals>
            <goal>single</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

步驟二:新建 AbstractJavaSamplerClient 的子類,並重寫相關方法

AbstractJavaSamplerClient 類繼承了 JavaSamplerClient 接口,該接口包含 setupTest、runTest、teardownTest 和 getDefaultParameters 四個方法:

  • setupTest

JMeter 將爲測試中的每個線程創建一個 JavaSamplerClient 實現實例,測試開始時,將在每個線程的 JavaSamplerClient 實例上調用 setupTest 來初始化客戶端,本例中即初始化 RocketMQ 的 producer。

  • runTest

每個線程每次迭代會調用一次 runTest 方法,本例中,需要在 runTest 方法裏面定義消息發送的方法和採樣結果的設置邏輯。

  • teardownTest

迭代完設置的次數或時間後,此方法將會被執行,本例中,需要在此方法關閉 producer。

  • getDefaultParameters

此方法定義了參數列表,這些參數通過會 JavaSamplerContext 傳遞給上述方法方法,在此方法內定義的參數,可以在 JMeter JavaRequest Sampler 的 GUI 界面設置值,本例中,需要定義 RocketMQ 的 broker 地址、topic 名稱、消息 key、消息內容等參數。

新建子類參考如下:

import java.nio.charset.StandardCharsets;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.java.sampler.AbstractJavaSamplerClient;
import org.apache.jmeter.protocol.java.sampler.JavaSamplerContext;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.exception.RemotingException;

public class JavaSamplerForRocketMQ extends AbstractJavaSamplerClient {
    private DefaultMQProducer producer;
    private static final String NAME_SRV_ADDRESS = "nameSrvAddress";
    private static final String TOPIC = "topic";
    private static final String PRODUCER_GROUP = "producer group";
    private static final String MSG_BODY = "messageBody";
    private static final String MSG_KEY = "messageKey";
    private static final String MSG_TAG = "messageTag";
    private static final String ERROR_CODE = "500";

    @Override
    public void setupTest(JavaSamplerContext javaSamplerContext) {

        try {
            // 初始化producer
            producer = new DefaultMQProducer(javaSamplerContext.getParameter(PRODUCER_GROUP));
            producer.setNamesrvAddr(javaSamplerContext.getParameter(NAME_SRV_ADDRESS));
            producer.start();
        } catch (MQClientException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public SampleResult runTest(JavaSamplerContext javaSamplerContext) {
        SampleResult sampleResult = new SampleResult();
        sampleResult.setSampleLabel("rocketmq-producer");
        // 請求開始
        sampleResult.sampleStart();
        // 普通消息發送
        Message message = new Message(
            javaSamplerContext.getParameter(TOPIC),
            javaSamplerContext.getParameter(MSG_TAG),
            javaSamplerContext.getParameter(MSG_BODY).getBytes()
        );
        try {
            // 發送消息,需要關注發送結果,並捕獲失敗等異常。
            SendResult sendResult = producer.send(message);
            // 設置發送請求的字節數
            sampleResult.setSentBytes(message.toString().getBytes(StandardCharsets.UTF_8).length);
            sampleResult.setDataType(SampleResult.TEXT);
            // 設置請求內容
            sampleResult.setSamplerData(message.toString());
            // 設置響應內容
            sampleResult.setResponseData(String.format("Msg Id:%s", sendResult.getMsgId()).getBytes());
            sampleResult.setSuccessful(true);
            sampleResult.setResponseCodeOK();
        } catch (MQBrokerException | InterruptedException | RemotingException | MQClientException e) {
            sampleResult.setSuccessful(false);
            sampleResult.setResponseCode(ERROR_CODE);
            sampleResult.setResponseData(String.format("Error Msg:%s", e).getBytes());
            return sampleResult;
        } finally {
            // 請求結束
            sampleResult.sampleEnd();
        }
        return sampleResult;
    }

    @Override
    public void teardownTest(JavaSamplerContext javaSamplerContext) {
        producer.shutdown();
    }

    @Override
    public Arguments getDefaultParameters() {
        Arguments arguments = new Arguments();
        arguments.addArgument(NAME_SRV_ADDRESS, "");
        arguments.addArgument(PRODUCER_GROUP, "");
        arguments.addArgument(TOPIC, "");
        arguments.addArgument(MSG_KEY, "");
        arguments.addArgument(MSG_TAG, "");
        arguments.addArgument(MSG_BODY, "");
        return arguments;
    }
}

步驟三:打包項目成 JAR 文件

通過 mvn clean package 將項目打包,在 target 目錄中可見 jmeter-rocketmq4.jar 和 jmeter-rocketmq4-jar-with-dependencies.jar 兩個 JAR 包,其中 jmeter-rocketmq4-jar-with-dependencies.jar 包括了所需的依賴,在後續步驟中使用此 JAR 包。

.
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── JavaSamplerForRocketMQ4.java
│   │   └── resources
│   └── test
│       └── java
└── target
    ├── jmeter-rocketmq4-jar-with-dependencies.jar
    ├── jmeter-rocketmq4.jar

步驟四:使用 JMeter GUI 進行腳本編寫和調試

1. 將打包好的 JAR 包和依賴的 JAR 包複製到 JMETER_HOME/lib/ext 目錄下,然後執行命令 JMETER_HOME/bin/jmeter 打開 JMeter GUI。

2. 新建線程組後添加 Java 請求取樣器。

3. 在下拉框中選擇步驟二中新增的類(不一定和圖片中的完全一致,按照實際的類全限定名選擇),並填寫下方相關參數。

4. 爲線程組添加“查看結果樹”和“彙總報告”監聽器,然後啓動測試計劃,在結果樹和彙總報告中驗證測試的結果是否符合預期。

5. 保存該測試計劃爲 JMX 文件。

步驟五:在 PTS 創建 JMeter 場景進行壓測

1. 在 PTS 控制檯創建 JMeter 環境,將步驟三中打包的 JAR 包上傳到該 JMeter 環境中(更多細節請參考 JMeter 環境管理的查看、修改及創建_性能測試-阿里雲幫助中心[1]):

a. 進入 PTS 控制檯,選擇“JMeter 環境”;

b. 輸入自定義的環境名;

c. 點擊上傳文件,選擇步驟三中打包的 JAR 包;

d. 點擊保存。

2. 在 PTS 控制檯創建場景中選擇“JMeter 壓測”場景:

3. 編輯“場景配置”:

a. 自定義場景名;

b. 點擊上傳文件,選擇步驟四中保存的 JMX 文件;

c. 在“使用依賴環境?”下拉框中選擇“是,使用依賴環境”;

d. 在“選擇依賴環境”下拉框選擇剛剛創建的 JMeter 環境。

4. 施壓配置:

小建議:由於我們是想通過壓測找到 RocektMQ 能承受的最大併發請求數,因此建議選擇 RPS 模式,這樣可以直接衡量 RocektMQ 的承壓能力。同時,考慮到公網帶寬限制,應該選擇阿里雲 VPC 內網壓測。

a. 選擇壓力來源爲阿里雲 VPC 內網,同時選擇部署被壓測 RocketMQ 的 ECS 所在區域;

b. 設置 ECS 的 VPC、安全組和交換機,注意 VPC 和安全組一定要和 ECS 相同,安全組中要打開響應的端口(在 ECS 控制檯設置);

c. 設置壓力模式爲 RPS 模式;

d. 設置起始 RPS、最大 RPS 和壓測時長,本文設置起始 RPS 爲 90000,最大 RPS 爲 110000,持續 2 分鐘。

e. 指定循環一般設置爲否,表示執行一次就結束,指定 IP 數會根據設置的 RPS 自動生成。

5. 其餘設置請根據需求參考 JMeter 壓測_性能測試-阿里雲幫助中心[2]。6. 保存配置並調試場景,確認和 RocketMQ 的連通,之後可以開始進行壓測。

步驟六:查看壓測報告

JMeter 的壓測報告通用解讀可以參考如何查看 JMeter 壓測數據、採樣日誌及施壓機性能_性能測試-阿里雲幫助中心[3],下一節將介紹如何使用 PTS 的壓測報告來找到 RocketMQ 的承壓能力。

報告解讀

1. 首先,查看整個壓測的概覽信息和指標趨勢。如下圖所示,報告第一欄展示了整個壓測過程的請求成功率、平均 RT、平均 TPS 等指標,這些指標可以在官方文檔中找到具體解釋。同時,根據成功率的趨勢圖所示,從 18:54:05 開始,成功率逐漸波動下降,此時的 TPS 值爲 9.55W,代表 18:54:05 計算的前 5 秒平均 TPS 約爲 9.55W。

2. 其次,使用壓測報告中的 Prometheus 監控數據對結果進一步分析。藉助阿里雲 ARMS 的 Prometheus 和 Grafana 產品,PTS 的壓測報告可以提供包括吞吐量、成功率和響應時長的時序圖,同時,支持用戶使用 PromQL 語句對數據面板進行編輯操作,靈活查詢所需的數據,在本文中,我們可以將成功率和吞吐量放在一個 panel,來進一步分析。

a. 首先點擊“成功率(時序)”,然後點擊“Edit”,可進入成功率大盤的編輯界面,複製成功率的查詢 PromQL:

sum(rate(pts_api_response_total{task_id="$task_id", code=~"200|302"}[5s]))/sum(rate(pts_api_response_total{task_id="$task_id"}[5s]))

b. 然後進入吞吐量大盤的編輯界面,使用成功率的 PromQL 替換虛擬用戶數的 PromQL,並更改 Grafana 的相關配置(下圖中紅框),便可得到展示吞吐量和成功率的面板。

該面板展示的數據統計精度爲 1 秒,可得到更精確的數據,在 18:54:05 秒時,成功率開始下降,此時 TPS 爲 96561.9。

c. 爲了更好的評估 RocketMQ 的性能,我們還可以統計出成功率保持 100% 的時間範圍內的平均 TPS,首先找到成功率爲 100% 的持續時間,下圖中爲 47 秒,然後將計算 TPS 的指標的時間範圍改成 47s,這樣每個點都代表前 47s 的平均 TPS,將鼠標移動到成功率爲 100% 的最後一個時間,當前時間的 TPS 值即爲成功率爲 100% 時間範圍內的平均 TPS,即 89357.5。

 
 

3. 最後,爲了對比不同參數的設置對 RocketMQ 性能的影響,同時驗證 PTS 在 RocketMQ 壓測上的可用性,我們做了一個簡單的對比實驗,並通過 jstat 命令來觀察不同參數對垃圾回收的影響。

實驗結果顯示,對於當前 ECS 配置部署的 RocketMQ,適當調大堆內存可以有效提高 RocketMQ 的性能,當堆內存提高到 24g 時(此事 ECS 內存使用率達到 85.39%),性能沒有顯著提高;適當提高 sendMessageThreadPoolNums 的值可以提高 RocketMQ 的性能,當 sendMessageThreadPoolNums 超過 16 後,性能沒有顯著提高,甚至略有下降。用戶可以根據實際情況,進行更詳細的對比實驗,來充分評估所部署的 RocketMQ 承壓能力。

結束語

本文介紹了使用阿里雲 PTS 的 JMeter 場景壓測 RocketMQ 的詳細步驟,對各環節逐一進行了說明,最後,通過對壓測報告的自定義分析,展現了 PTS 強大的壓測結果分析能力,藉助 JMeter 和 PTS,用戶可以對各類中間件進行靈活多維的分析,助力其構建起穩定健壯的系統。

相關鏈接:

[1] JMeter 環境管理的查看、修改及創建_性能測試-阿里雲幫助中心

https://help.aliyun.com/document_detail/170857.html?spm=a2c4g.103173.0.0.292c20f8wnWyCV

[2] JMeter 壓測_性能測試-阿里雲幫助中心

https://help.aliyun.com/document_detail/97876.html?spm=a2c4g.91788.0.0.2fde6f338aHIDI

[3] 如何查看 JMeter 壓測數據、採樣日誌及施壓機性能_性能測試-阿里雲幫助中心

https://help.aliyun.com/document_detail/127454.html?spm=a2c4g.94066.0.0.4a5164bepHmzWD

作者:森元

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章