手把手教你編寫最簡單的效能指令碼

絲瓜呆呆發表於2021-06-08

在指令碼實現中,我們最常用的協議就是 HTTP 和 TCP 了吧,所以在今天的內容裡,我簡單地說一下如何編寫 HTTP 和 TCP 指令碼,以應測試主題。

先上圖

 

 

我們知道 HTTP 是應用層的協議之一,現在很多場景都在用它,並且是用的 HTTP1.1 的版本,對應的是 RFC2616,當然還有補充協議 RFC7231、6265。

還有一點也需要注意,HTTP 是通過 Socket 來使用 TCP 的,Socket 做為套接層 API,它本身不是協議,只規定了 API。

而我們通常在 JMeter 中寫 TCP 指令碼,就是直接呼叫 Socket 層的 API。TCP 指令碼和 HTTP 指令碼最大的區別就是,TCP 指令碼中傳送和接收的內容完全取決於 Socket server 是怎麼處理的,並沒有通用的規則。所以指令碼中也就只有根據具體的專案來發揮了。

這個介面的訪問邏輯:JMeter——SprintBoot 的應用——MySQL。

 

 

 編寫 JMeter 指令碼

1、建立執行緒組

 Ramp-up Period(in seconds):遞增時間,以秒為單位。指的就是上面配置的執行緒數將在多長時間內會全部遞增完。如果我們配置了 100 執行緒,這裡配置為 10 秒,那麼就是 100/(10s*1000ms)=1 執行緒 /100ms;如果我們配置了 10 執行緒,這裡配置為 1 秒,則是 10/1000=1 執行緒 /100ms。這時我們要注意了哦,在 10 執行緒啟動的這個階段中,對伺服器的壓力是一樣的。示意圖如下:

 Loop Count 這個值指的是一個執行緒中指令碼迭代的次數。這裡你需要注意,這個值和後面的 Scheduler 有一個判斷關係,下面我們會提到。

Delay Thread creation until needed:這個含義從字面看不是特別清楚。這裡有一個預設的知識點,那就是 JMeter 所有的執行緒是一開始就建立完成的,只是遞增的時候會按照上面的規則遞增。如果選擇了這個選項,則不會在一開始建立所有執行緒,只有在需要時才會建立。這一點和 LoadRunner 中的初始化選項類似。只是不知道你有沒有注意過,基本上,我們做效能測試的工程師,很少有選擇這個選項的。選與不選之間,區別到底是什麼呢?

如果不選擇,在啟動場景時,JMeter 會用更多的 CPU 來建立執行緒,它會影響前面的一些請求的響應時間,因為壓力機的 CPU 在做其他事情嘛。

如果選擇了的話,就會在使用時再建立,CPU 消耗會平均一些,但是這時會有另一個隱患,就是會稍微影響正在跑的執行緒。這個選項,選擇與否,取決於壓力機在執行過程中,它能產生多大的影響。如果你的執行緒數很多,一旦啟動,壓力機的 CPU 都被消耗在建立執行緒上了,那就可以考慮選擇它,否則,可以不選擇。

即便設定了 Scheduler 的 Duration 為 100 秒,執行緒仍然會以 10 秒為結束點。

有些人不太理解這一點,經常會設定迭代次數,同時又設定 Scheduler 中的 Duration。而對 TPS 來說,就會產生這樣的圖:

 

場景沒執行完,結果 TPS 全掉下去了,於是開始查後端系統,其實和後端沒有任何關係。

 

 2、建立 HTTP Sampler

這個圖片可以表示成功嗎?

不是的,業務的成功,只能靠業務來判斷。這裡只是查詢成功了,沒返回資料也是查詢成功了。

POST 介面

下面我將 Method 改為 POST,POST 介面與 GET 介面的區別有這麼幾處:要把 Path 改為 /pa/add;輸入 JSON 格式的 Body Data。

 執行起來,檢視下結果。

 

 

 報錯了,先看懂問題,再處理問題,別瞎蒙!

上面這個問題其實提示得很清楚:“不支援的媒體型別”。這裡就兩個資訊,一個是 Content type,一個是 charset。它們是 JMeter 中 HTTP Header 裡預設自帶的。我們要傳送的是 JSON 資料,而 JMeter 預設是把它當成 text 發出去的,這就出現了問題。所以我們要加一個 Header,將 Content type 指定為 JSON。

加一個 HTTP Header,如下所示:

 在這裡,我需要跟你強調的是,手工編寫 HTTP 指令碼時,要注意以下幾點:

要知道請求的型別,我們選擇的型別和後端介面的實現型別要是一致的。

業務的成功要有明確的業務判斷(在下面的 TCP 中,我們再加斷言來判斷)。

判斷問題時,請求的邏輯路徑要清晰。

編寫完 HTTP 指令碼時,我們再來看一下如何編寫 TCP 指令碼。

手工編寫 TCP 指令碼

首先建立 TCP Sampler。右鍵點選 Thread Group - Add - Sampler - TCP Sampler 即可建立。

 

 

 輸入配置和要傳送的資訊。

IP 地址和埠是必須要輸入的。對於建立一個 TCP 協議的 JMeter 指令碼來說,簡單地說,過程就是這樣的:建立連線 - 發資料 - 關閉連線。

但是,通常我們在建立 TCP 協議的指令碼時,都是根據業務介面規範來說的,複雜點其實不在指令碼本身上,而是在介面的規則上。

新增斷言

我回放了一下指令碼,發現如下情況:

 都執行對了呀,為什麼下面的沒有返回資訊呢?這種情況下只有第一個請求有返回資訊,但是下面也沒有報錯。這裡就需要注意了。

測試工具的成功,並不等於業務的成功。

所以我們必須要做的就是響應斷言,也就是返回值的判斷。在 JMeter 中,斷言有以下這些:

 

 

 什麼是斷言呢?

 

 

 斷言指的就是伺服器端有一個業務成功的標識,會傳遞給客戶端,客戶端判斷是否正常接收到了這個標識的過程。

 

 

 在這裡我新增了一個斷言,用以判斷伺服器是否返回了 OK。 你要注意這個“OK”是從哪來的哦,它是從服務端的這一行程式碼中來的。 String response = message + " is OK";

請注意,這個斷言的資訊,一是可以判斷出業務的正確性。我在工作中發現有些人用頁面中一些並不必要的文字來判斷,這樣就不對了,我們應該用有業務含義的判斷標識。

如果我們再次回放指令碼,你會發現除了第一個請求,後面 9 個請求都錯了。

所以,在做指令碼時,請你一定要注意,斷言是必須要加的。

長短連線的問題

我們檢視一下 JMeter 的控制檯錯誤資訊:

 ERROR o.a.j.p.t.s.TCPSampler: 
java.net.SocketException: Broken pipe (Write failed)
  at java.net.SocketOutputStream.socketWrite0(Native Method) ~[?:1.8.0_111]
  at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109) ~[?:1.8.0_111]
  at java.net.SocketOutputStream.write(SocketOutputStream.java:141) ~[?:1.8.0_111]
  at org.apache.jmeter.protocol.tcp.sampler.TCPClientImpl.write(TCPClientImpl.java:78) ~[ApacheJMeter_tcp.jar:5.1.1 r1855137]
  at org.apache.jmeter.protocol.tcp.sampler.TCPSampler.sample(TCPSampler.java:401) [ApacheJMeter_tcp.jar:5.1.1 r1855137]
  at org.apache.jmeter.threads.JMeterThread.doSampling(JMeterThread.java:622) [ApacheJMeter_core.jar:5.1.1 r1855137]
  at org.apache.jmeter.threads.JMeterThread.executeSamplePackage(JMeterThread.java:546) [ApacheJMeter_core.jar:5.1.1 r1855137]
  at org.apache.jmeter.threads.JMeterThread.processSampler(JMeterThread.java:486) [ApacheJMeter_core.jar:5.1.1 r1855137]
  at org.apache.jmeter.threads.JMeterThread.run(JMeterThread.java:253) [ApacheJMeter_core.jar:5.1.1 r1855137]
  at java.lang.Thread.run(Thread.java:745) [?:1.8.0_111]

Broken pipe。這個提示表明客戶端上沒有這個連線了,而 JMeter 還以為有這個連結,於是接著用這個連結來發,顯然是找不到這個通道,於是就報錯了。

為什麼會報這個錯呢?因為我們程式碼是短連結的,服務端處理完之後,就把這個連結給斷掉了。

這是為什麼呢?因為在 JMeter 中,預設是複用 TCP 連線的,但是在我們這個示例中,服務端並沒有儲存這個連線。所以,我們應該在指令碼中,把下圖中的 Re-use connection 給去掉。

 

 這時再回放指令碼,你就會發現 10 次迭代全都對了。如下圖所示:

 

 但是,這裡還有一個知識點,希望你注意。短連線的時候,必然會產生更多的 TCP 連線的建立和銷燬,對效能來說,這會讓系統變得緩慢。

所以你可以看到上面 10 條迭代全都對了的同時,響應時間也增加了。

長短連線的選擇取決於業務的需要,如果必須用短連結,那可能就需要更多的 CPU 來支撐;要是長連線,就需要更多的記憶體來支撐(用以儲存 TCP 連線)。

TCP 連線超時

下面這個錯誤,屬於典型的主機連不上。

java.net.ConnectException: Operation timed out (Connection timed out)
  at java.net.PlainSocketImpl.socketConnect(Native Method) ~[?:1.8.0_111]
  at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[?:1.8.0_111]
  at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) ~[?:1.8.0_111]
  at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) ~[?:1.8.0_111]
  at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[?:1.8.0_111]
  at java.net.Socket.connect(Socket.java:589) ~[?:1.8.0_111]
  at org.apache.jmeter.protocol.tcp.sampler.TCPSampler.getSocket(TCPSampler.java:168) [ApacheJMeter_tcp.jar:5.1.1 r1855137]
  at org.apache.jmeter.protocol.tcp.sampler.TCPSampler.sample(TCPSampler.java:384) [ApacheJMeter_tcp.jar:5.1.1 r1855137]
  at org.apache.jmeter.threads.JMeterThread.doSampling(JMeterThread.java:622) [ApacheJMeter_core.jar:5.1.1 r1855137]
  at org.apache.jmeter.threads.JMeterThread.executeSamplePackage(JMeterThread.java:546) [ApacheJMeter_core.jar:5.1.1 r1855137]
  at org.apache.jmeter.threads.JMeterThread.processSampler(JMeterThread.java:486) [ApacheJMeter_core.jar:5.1.1 r1855137]
  at org.apache.jmeter.threads.JMeterThread.run(JMeterThread.java:253) [ApacheJMeter_core.jar:5.1.1 r1855137]
  at java.lang.Thread.run(Thread.java:745) [?:1.8.0_111]

要想解決這個問題,就要先確定服務端是可以正常連通的。

如果不能正常連通,那麼通常都是 IP 不正確、埠不正確、防火牆阻止之類的問題。解決了網路連通性的問題,就可以解決 connection timed out 的問題。

總結

其實這篇文章只想告訴你一件事情,手工編寫指令碼,從基礎上說,是非常簡單的,只是有三點需要特別強調:

1、涉及到業務規則和邏輯判斷之後,編寫指令碼就複雜了起來。但是瞭解業務規則是做指令碼的前提條件,也是效能測試工程師的第一步。

2、編寫指令碼的時候,要知道後端的邏輯。這裡的意思不是說,你一開始寫指令碼的時候,就要去讀後端的程式碼,而是說你在遇到問題的時候,要分析整個鏈路上每個環節使用到了什麼技術,以便快速地分析判斷。

3、寫指令碼是以最簡為最佳,用不著故意複雜。

 

相關文章