如何快速排程 PTS 的百萬併發能力

語言: CN / TW / HK

作者:靈苒

在實際的業務場景中,壓測是必不可少的一環,無論是對伺服器、資料庫、網路等效能瓶頸的評估,還是如瀏覽、下單、支付等重要流量節點的業務連續性保障,亦或是搬站上雲整體業務穩定性的預估,這些都需要效能壓測來幫助你建立對系統和業務的完整認知。根據 Google 的統計,如果網站開啟慢每 500 毫秒,使用者訪問量將下降 20%。根據 Amazon 統計,每慢 100 毫秒,交易額下降 1%。這些事件和統計資料為大家敲響了警鐘,也客觀說明了效能壓測對於企業應用的重要性。

壓測是通過模擬使用者行為對業務系統發起請求,測算出系統的承載能力,並對系統做一次全面的體檢,壓測後可根據壓測表現優化系統瓶頸,防止出現線上故障。

業界常見的壓測軟體 JMeter 和 PTS

目前 JMeter 是效能壓測領域應用最廣泛的開源軟體。

對於場景簡單,要求測試併發量不高的情況下,JMeter 本地測試就能滿足需求。但隨著網際網路使用者的增加,對系統承載更大併發的需求日漸提升,而單臺 JMeter 施壓機的施壓能力有一定上限,所以需要使用多臺施壓機,以提高 JMeter 的施壓能力,這就要使用到 JMeter 的分散式施壓功能。

但 JMeter 的分散式壓測前置準備較多,需要注意以下幾點:

  • 施壓機的防火牆已關閉或打開了正確的埠。為 RMI 設定了 SSL 或禁用了它。
  • 所有施壓機都在同一個子網上。如果使用 192.xxx或10.xxx IP 地址,則伺服器位於同一子網中。
  • 所有施壓機上使用相同版本的 JMeter 和 Java。
  • 所有施壓機都已經拷貝了切分好的 CSV 資料檔案、依賴 jar 包等。
  • 已配置好監控資料的收集。

由此可見 JMeter 的分散式壓測需要自己協調各資源,前置準備比較麻煩,對實施壓測的人員來說壓測效率低。

PTS 是阿里雲研發的效能測試工具,最初主要為了模擬雙十一流量洪峰,如今已走過十個年頭,在場景編排、壓測執行、壓測監控分析、報告總結等各方面能力相對完善,可提供百萬併發、千萬 TPS 流量發起能力,並且還能完全相容 JMeter,可天然彌補 JMeter 在效能壓測中的劣勢。對使用 JMerer 無法繞過叢集問題的使用者是一個很好的選擇。

PTS 的 JMeter 壓測極大的簡化了 JMeter 分散式壓測流程,同時也降低了壓測過程中對施壓機的維護成本。使用 PTS 的 JMeter 壓測,使用者只需要在控制檯配置需要使用的機器數,無須使用者提前準備多臺已安裝相同 Java 和 JMeter 版本的施壓機。同時無須使用者根據施壓機數量去切分 CSV 引數檔案;壓測結束後,PTS 會將監控資料彙總產生一個詳細的壓測報告供使用者查閱。

相比於直接在命令列執行 JMeter 指令碼來說,PTS 使用更加方便,可按需提供海量的施壓能力,並且能提供簡潔直觀的監控和報告。

如何發起 PTS 的 JMeter 壓測

和所有壓測的核心步驟一樣,使用 PTS 的 JMeter 壓測,也主要集中在建立場景、壓測場景和檢視報告三個步驟中。

1、建立場景:PTS 的 JMeter 壓測以場景為核心,壓測物件為一個場景,場景中包括JMeter(原生)指令碼、JMeter 依賴(一系列依賴 jar 包和一系列 properties 配置)、及一些壓測配置(PTS 壓測的配置,例如公網/VPC 壓測、併發量、引擎數量、壓測時長等)。

2、壓測場景:對場景的操作分為兩方面,一是對場景配置的增刪改查,二是對場景的壓測和除錯。

3、生成報告:每次對場景壓測都會生成一個壓測任務,同時生成一個報告,其中包括壓測的關鍵指標,如 TPS、RT、成功率等,可輔助使用者排查系統性能瓶頸。此外,PTS 預設將報告儲存 30 天,可以隨時檢視歷史報告,並且提供匯出 PDF 格式的報告。

在壓測領域,隨著壓測需求日益多樣化,更多使用者希望將雲上的壓測能力繼承到自己的系統,或者根據自己的業務系統,編排自定義的壓測平臺,從而實現自動化定製化壓測需求。

所以,為了方便使用者便捷排程 PTS 百萬併發的能力,PTS 開通了 JMeter 的 OpenAPI,提供瞭如下幾類壓測的核心功能:編輯場景、除錯場景、壓測場景、檢視執行時資料、檢視報告。

通過整合 OpenAPI,客戶可以更加方便的在自己的業務場景實現 PTS  百萬級併發壓測的能力,實現場景的增、刪、改、查等各種操作,一鍵啟動壓測,並在壓測過程中,隨時停止壓測。同時生成的壓測報告中,除了JMeter 原生的日誌外,還有 PTS 針對某個取樣器的成功率、TPS、RT 指標的聚合資料。另外還可以對報告進行檢視報告列表、JMeter 原生日誌以及 PTS 對 JMeter 取樣器壓測指標的聚合資料等功能。

那麼還等什麼呢?來,試著用 PTS 中 JMeter 的 OpenAPI 寫一個屬於你的百萬級併發壓測能力的壓測平臺吧!

附錄:

具體步驟如下

引入 pom 依賴

<!--建立PTS場景需要的實體類,如果只使用JMeter壓測則不需要引入-->
<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>pts-api-entity</artifactId>
  <version>1.0.1</version>
</dependency>
<!--PTS Java SDK依賴。-->
<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>pts20201020</artifactId>
  <version>1.8.10</version>
</dependency>
<!--阿里雲核心庫。-->
<dependency>
  <groupId>com.aliyun</groupId>
  <artifactId>aliyun-java-sdk-core</artifactId>
  <version>4.5.2</version>
</dependency>

\

複製下列程式碼

import com.aliyun.pts20201020.Client;
import com.aliyun.pts20201020.models.*;
import com.aliyun.teaopenapi.models.Config;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;


public class StartingDemo {

    public static void main(String[] args) throws Exception {
        Client client = getClient();
        // 建立場景
        String sceneId = createScene(client);
        // 啟動場景
        String reportId = startTesting(client, sceneId);
        // 最多等待次數
        int count = 0;
        // 查詢是否已生成報告
        while (!hasReport(client, reportId) && count++ < 20) {
            // 若報告還未生成,則等待(30s)一段時間再查詢
            // 根據壓測時間酌情等待
            Thread.sleep(30 * 1000);
        }
        // 檢視報告
        getJMeterReport(client, reportId);
    }

    private static boolean hasReport(Client client, String reportId) throws Exception {
        ListJMeterReportsRequest request = new ListJMeterReportsRequest();
        // 分頁設定
        request.setPageNumber(1);
        request.setPageSize(1);
        // 查詢條件設定
        request.setReportId(reportId);
        ListJMeterReportsResponse response = client.listJMeterReports(request);
        return response.getBody().getReports().size() > 0;
    }

    private static void getJMeterReport(Client client, String reportId) throws Exception {
        // 檢視機器日誌
        GetJMeterLogsResponse getJMeterLogsResponse = getJMeterLogs(client, reportId);
        List<Map<String, ?>> logs = getJMeterLogsResponse.getBody().getLogs();
        // 檢視取樣器聚合資料
        GetJMeterSampleMetricsResponse getJMeterSampleMetrics = getJMeterSampleMetrics(client, reportId);
        List<String> sampleMetricList = getJMeterSampleMetrics.getBody().getSampleMetricList();
        // 檢視取樣日誌
        GetJMeterSamplingLogsResponse getJMeterSamplingLogs = getJMeterSamplingLogs(client, reportId);
        List<String> sampleResults = getJMeterSamplingLogs.getBody().getSampleResults();
    }

    private static GetJMeterSamplingLogsResponse getJMeterSamplingLogs(Client client, String reportId) throws Exception {
        GetJMeterSamplingLogsRequest request = new GetJMeterSamplingLogsRequest();
        // 分頁設定
        request.setPageNumber(1);
        request.setPageSize(10);
        // 條件設定
        request.setReportId(reportId);
        GetJMeterSamplingLogsResponse response = client.getJMeterSamplingLogs(request);
        return response;
    }

    private static GetJMeterSampleMetricsResponse getJMeterSampleMetrics(Client client, String reportId) throws Exception {
        GetJMeterSampleMetricsRequest request = new GetJMeterSampleMetricsRequest();
        // 設定報告id
        request.setReportId(reportId);
        GetJMeterSampleMetricsResponse response = client.getJMeterSampleMetrics(request);
        return response;
    }

    private static GetJMeterLogsResponse getJMeterLogs(Client client, String reportId) throws Exception {
        GetJMeterLogsRequest request = new GetJMeterLogsRequest();
        // 分頁設定
        request.setPageNumber(1);
        request.setPageSize(10);
        // 查詢的壓測引擎索引
        request.setReportId(reportId);
        GetJMeterLogsResponse response = client.getJMeterLogs(request);
        return response;
    }

    private static String startTesting(Client client, String sceneId) throws Exception {
        StartTestingJMeterSceneResponse startTestingSceneResponse = startTestingScene(client, sceneId);
        String reportId = startTestingSceneResponse.getBody().getReportId();
        return reportId;
    }

    private static StartTestingJMeterSceneResponse startTestingScene(Client client, String sceneId) throws Exception {
        StartTestingJMeterSceneRequest request = new StartTestingJMeterSceneRequest();
        request.setSceneId(sceneId);
        StartTestingJMeterSceneResponse response = client.startTestingJMeterScene(request);
        return response;
    }

    private static String createScene(Client client) throws Exception {
        SaveOpenJMeterSceneRequest request = new SaveOpenJMeterSceneRequest();
        // 定義場景
        SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterScene scene = new SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterScene();
        // 設定場景名
        scene.setSceneName("test");
        // 設定檔案列表,包括JMeter指令碼、JMeter壓測依賴jar包、配置額度資料檔案等
        List<SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterSceneFileList> fileList = new ArrayList<SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterSceneFileList>();
        // 設定檔案的屬性 需要設定檔案的名稱和檔案公網可訪問的oss地址
        SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterSceneFileList testFile = new SaveOpenJMeterSceneRequest.SaveOpenJMeterSceneRequestOpenJMeterSceneFileList();
        testFile.setFileName("baidu.jmx");
        testFile.setFileOssAddress("https://pts-openapi-test.oss-cn-shanghai.aliyuncs.com/baidu.jmx");
        fileList.add(testFile);
        scene.setFileList(fileList);
        // 設定場景併發,可設定為100萬
        scene.setConcurrency(1000000);
        // 設定引擎數量 說明:一臺引擎最多能發500併發,最少1併發所以此處能設定的引擎數為[2,1000],另外引擎數量越多消耗vum越快
        scene.setAgentCount(2000);
        // 設定壓測持續時間 60s
        scene.setDuration(60);
        // 設定測試檔案的名稱,這個檔案需包括在檔案列表中
        scene.setTestFile("baidu.jmx");
        request.setOpenJMeterScene(scene);
        SaveOpenJMeterSceneResponse response = client.saveOpenJMeterScene(request);
        return response.getBody().getSceneId();
    }

    private static Client getClient() throws Exception {
        // 填寫自己的AK/SK
        String accessKeyId = "ak";
        String accessKeySecret = "sk";
        Config config = new Config();
        config.setAccessKeyId(accessKeyId);
        config.setAccessKeySecret(accessKeySecret);
        Client client = new Client(config);
        return client;
    }
}

填寫自己的 ak/sk

在上述程式碼的 getClient 中填寫正確的 ak/sk

點選啟動

點選 main 方法啟動

點選​此處​,前往 PTS 官網瞭解更多詳情!