同步Notion :
問題引言:
loadRunner vs Jmeter
LoadRunner | Jmeter |
---|---|
非開源工具 | 開源工具 |
C 語言 | 100% java 語言 |
檢查點(web_reg_find) | 斷言 |
關聯(web_reg_save_param) | 關聯(前置/後置處理器正則提取器) |
支援IP欺騙 | 不支援IP 欺騙 |
測試結果分析圖表功能強大(資料採集) | 測試結果分析圖表功能相對較弱,需依賴擴充套件外掛 |
重量級 | 輕量級 |
安裝複雜 | 安裝簡單 |
跨平臺 | |
根據不同負載生成不同數量並行使用者 | 當前一個執行緒組只能生成一個 |
效能 | 支援web端功能測試 |
廣泛支援業界各種標準協定、多種平臺開發指令碼 | 元件 |
定義
Locust是一款易於使用的分散式負載測試工具,完全基於事件,即一個locust節點也可以在一個程序中支援數千並行使用者,不使用回撥,通過gevent使用輕量級過程(即在自己的程序內執行)。
locust: 開源 、基於python ,非多執行緒(協程)、「用例即程式碼」 ; 無錄製工具、
為什麼選擇locust
基於協程 ,低成本實現更多並行
指令碼增強(「測試即程式碼」)
使用了requests傳送http請求
支援分散式
使用Flask 提供WebUI
有第三方外掛、 易於擴充套件
約定大於設定
pip install locust
locust -v
test_xxx (一般測試框架約定)
dockerfile (docker約定)
locustfile.py (locust約定)
# locustfile.py
eg: 入門範例
from locust import HttpUser, task, between
# User ?
# function 包裝task
class MyHttpUser(HttpUser):
wait_time = between(1, 2) # 執行任務 等待時長 檢查點 思考時間
@task
def index_page(self):
self.client.get("https://baidu.com/123")
self.client.get("https://baidu.com/456")
pass
總結三步:
locust
啟動locust
存取:http://[::1]:8089/
指標詳解:
- Number of users 模擬使用者數
- Spawn rate : 生產數 (每秒)、 =>jmeter : Ramp-Up Period (in seconds)
- Host (e.g. http://www.example.com) => 取決指令碼中 絕對地址
- ![](https://s3.bmp.ovh/imgs/2022/06/14/22e82961a5609f42.png)
locust
locust -f locustfile.py --headless -u 500 -r 10 --host 123 -t 1h5m
框架是通過命令locust
執行的,常用引數有:
locust -f **.py --no-web -u 1 -t 1
locust -f **.py
父類別是個User ?
表示要生成進行負載測試的系統的 HTTP「使用者」。
為什麼方法,要包裝為task
task 表示使用者要進行的操作
TaskSet : 定義使用者將執行的一組任務的類。測試任務開始後,每個Locust使用者會從TaskSet中隨機挑選 一個任務執行
具體的內容: 方法的程式碼
class MyHttpUser(HttpUser): #使用者
# wait_time = lambda self: random.expovariate(1)*1000
wait_time = between(1, 2) # 執行任務 等待時長 檢查點 思考時間
@task
def index_page(self): # 使用者執行操作
self.client.get("https://baidu.com/123") #服務錯誤、網路錯誤
self.client.get("https://baidu.com/456")
# 斷言 、 業務錯誤
locust 並非 http 介面測試工具 , 只是內建了 「HttpUser」 範例 ,可以測試任何協定: websocket 、socket 、mqtt (webAPP、Hybrid、Html5 、桌面瀏覽器) 、rpc
WebSocket是一種在單個TCP連線上進行全雙工通訊的協定。 WebSocket使得使用者端和伺服器之間的資料交換變得更加簡單,允許伺服器端主動向使用者端推播資料。在WebSocket API中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以建立永續性的連線,並進行雙向資料傳輸。ws長連線、 和http有本質不同 ;
使用者端發起,伺服器可能接受或拒絕切換到新協定。使用者端可使用常用的協定(如HTTP / 1.1)發起請求,請求說明需要切換到HTTP / 2或甚至到WebSocket
一句話概述:協定「使用HTTP進行握手, 後使用TCP進行對談」 的全雙工協定
目前pypi , 2個ws庫
安裝 pip install websocket_client
使用
import asyncio
import websockets
async def hello():
async with websockets.connect('ws://localhost:8765') as websocket:
name = input("What's your name? ")
await websocket.send(name)
print(f" send:>>> {name}")
greeting = await websocket.recv()
print(f" recv: <<< {greeting}")
asyncio.get_event_loop().run_until_complete(hello())
建立專用使用者端連結
設計統計結果
設定成功、失敗條件
pyfile:
TimeoutError(10060, '[WinError 10060] 由於連線方在一段時間後沒有正確答覆或連線的主機沒有反應,連線嘗試失敗。') 1.請求伺服器的反爬機制導致的,請求不被接受的 ; ip限制
→ 網頁存取驗證 -2. http的連線數超過最大限制。
headers的Connection引數預設為keep-alive,之前所有請求的連結都一直存在,佔用了後續的連結請求3. 網站伺服器太差 → 修改中介軟體設定(SLB、nginx 、ApiGateway 熔斷、限流 ~
→timeout 超時時間
........... 9大策略和6大指標 、 ........... 硬體(計算機體系機構)、作業系統(OS\JVM)、檔案系統、網路通訊、資料庫系統、中介軟體(transaction、message、app)、應用程式本身
本地:
netsh winsock reset
→ 重啟
初步結果:
1. RPS xxxx 左右
2. 最大使用者數 xxx
> 表示:當前允許最大使用者數請求,但是無法全部返回結果
常見錯誤 | 可能分析原因 |
---|---|
TPS波動較大 | 網路波動、其他服務資源競爭以及垃圾回收問題 |
高並行下大量報錯 | 短連線導致的埠被完全佔用以及執行緒池最大執行緒數設定較小及超時時間較短導致。 |
叢集類系統,各服務節點負載不均衡 | SLB服務設定了對談保持 |
並行數不斷增加,TPS上不去,CPU使用率較低 | SQL沒有建立索引/SQL語句篩選條件不明確、程式碼中設有同步鎖,高並行時出現鎖等待; |
connection reset、服務重啟、timeout等 | 引數設定、服務策略、阻塞及各種鎖導致 |
User : 在locust中User類表示一個使用者。locust將為每一個被模擬的使用者生成一個User類範例,而我們可以在User類中定義一些常見的屬性來定義使用者的行為。
Task: 使用者行為
Events : locust提供的事件勾點,用於一些再執行過程中執行特定時間的操作。
wait_time
> 三種時間間隔表示式
固定時間, 由constant(wait)函數提供
區間隨機時間: `between(min_wait, max_wait)函數
自適應節奏時間: constant_pacing
用於確保任務每 X 秒(最多)執行一次
task: 任務(使用者行為)
weight
測試中,存在多個User Class,預設情況下locust將為每個User Class的範例的數量是相同的。通過設定weight屬性,來控制locust為我們的User Class生成不同數量的範例。
→ Python程式碼
不支援 , →外掛增強
locustfile05.py
使用變數方式進行傳遞
locustfile09.py
locust引數化:引入佇列的概念 queue ,實現方式是將引數推入佇列,測試時依次取出,全部取完後 locust 會自動停止。若是使用引數迴圈壓測,需要將取出的引數再推入隊尾。
locust預設情況下會使用預設的檢查點,比如當介面超時、連結失敗等原因是,會自動判斷失敗
原理:
success
和failure
。其中failure方法需要我們傳入一個引數,內容就是失敗的原因。locustfile10.py
from requests import codes
from locust import HttpUser, task, between
class DemoTest(HttpUser):
host = 'https://www.baidu.com'
wait_time = between(2, 15)
def on_start(self):
# 通過手動傳入catch_response=True 引數手動設定檢查點
with self.client.get('/', catch_response=True) as r:
if r.status_code == codes.bad:
r.success()
else:
r.failure("請求百度首頁失敗了哦哦")
@task
def search_locust(self):
with self.client.get('/s?ie=utf-8&wd=locust', catch_response=True) as r:
if r.status_code == codes.ok:
r.success()
else:
r.failure("搜尋locust失敗了哦哦")
locust預設是隨機執行taskset裡面的task的。
權重通過在@task引數中設定,如程式碼中hello:world:item是1:3:2,實際執行時的程式碼,在user中tasks會將任務生成列表[hello,world,world,world,item,item]
@tag 裝飾器 :# locust -f locustfile06.py --tags tag1
task不止一個時,可以通過@tag給task打標籤進行分類,在執行測試時,通過--tags name執行指定帶標籤的task
# locustfile06.py
import time
from locust import HttpUser, task, between, TaskSet, tag
class QuickstartUser(TaskSet):
wait_time = between(1, 5)
# wait_time = constant(3) #固定時間
@task
def hello_world(self):
self.client.get("/hello")
self.client.get("/world")
@tag("tag1", "tag2")
@task(3)
def view_items(self):
for item_id in range(10):
#self.client.request_name="/item?id=[item_id]"#分組請求
# 將統計的10條資訊分組到名為/item條目下
self.client.get(f"/item?id={item_id}", name="/item")
time.sleep(1)
def on_start(self):
self.client.post("/login", json={"username": "foo", "password": "bar"})
class MyUserGroup(HttpUser):
""" 定義執行緒組 """
tasks = [QuickstartUser] # tasks 任務列表
host = "http://www.baidu.com"
第二種:在屬性中指定
檔案中存在多個使用者類場景,
- 命令列上沒有指定使用者類,Locust 將生成相同數量的每個使用者類。
- 可以通過將它們作為命令列引數傳遞來指定要使用同一 locustfile 中的哪些使用者類:
locust -f locustfile07.py QuickstartUser2
# locustfile07.py
import time
from locust import HttpUser, task, between, TaskSet, tag, constant
class QuickstartUser1(HttpUser):
host = "http://www.baidu.com"
wait_time = constant(4)
weight = 3 #屬性中指定
@task
def hello_world(self):
self.client.get("/hello1")
self.client.get("/world1")
def on_start(self):
self.client.post("/login1", json={"username": "foo", "password": "bar"})
class QuickstartUser2(HttpUser):
host = "http://www.baidu.com"
wait_time = between(1, 5)
weight = 1
@task
def hello_world(self):
self.client.get("/hello2")
self.client.get("/world2")
def on_start(self):
self.client.post("/login2", json={"username": "foo", "password": "bar"})
什麼是集合點?
集合點用以同步虛擬使用者,以便恰好在同一時刻執行任務。在[測試計劃]中,可能會要求系統能夠承受1000 人同時提交資料,可以通過在提交資料操作前面加入集合點,這樣當虛擬使用者執行到提交資料的集合點時,就檢查同時有多少使用者執行到集合點,如果不到1000 人,已經到集合點的使用者在此等待,當在集合點等待的使用者達到1000 人時,1000 人同時去提交資料,從而達到測試計劃中的需求。
注意:框架本身沒有直接封裝集合點的概念 ,間接通過gevent並行機制,使用gevent的鎖來實現
semaphore是一個內建的計數器:
每當呼叫acquire()時,內建計數器-1
每當呼叫release()時,內建計數器+1
計數器不能小於0,當計數器為0時,acquire()將阻塞執行緒直到其他執行緒呼叫release()
兩步驟:
範例程式碼:
# locustfile08.py
import os
from locust import HttpUser, TaskSet, task,between,events
from gevent._semaphore import Semaphore
all_locusts_spawned = Semaphore()
all_locusts_spawned.acquire()# 阻塞執行緒
def on_hatch_complete(**kwargs):
"""
Select_task類的勾點方法
:param kwargs:
:return:
"""
all_locusts_spawned.release() # # 建立勾點方法
events.spawning_complete.add_listener(on_hatch_complete) #掛在到locust勾點函數(所有的Locust範例產生完成時觸發)
n = 0
class UserBehavior(TaskSet):
def login(self):
global n
n += 1
print("%s個虛擬使用者開始啟動,並登入"%n)
def logout(self):
print("退出登入")
def on_start(self):
self.login()
all_locusts_spawned.wait() # 同步鎖等待
@task(4)
def test1(self):
url = '/list'
param = {
"limit":8,
"offset":0,
}
with self.client.get(url,params=param,headers={},catch_response = True) as response:
print("使用者瀏覽登入首頁")
@task(6)
def test2(self):
url = '/detail'
param = {
'id':1
}
with self.client.get(url,params=param,headers={},catch_response = True) as response:
print("使用者同時執行查詢")
@task(1)
def test3(self):
"""
使用者檢視查詢結果
:return:
"""
url = '/order'
param = {
"limit":8,
"offset":0,
}
with self.client.get(url,params=param,headers={},catch_response = True) as response:
print("使用者檢視查詢結果")
def on_stop(self):
self.logout()
class WebsiteUser(HttpUser):
host = 'http://www.baidu.com'
tasks = [UserBehavior]
wait_time = between(1, 2)
if __name__ == '__main__':
os.system("locust -f locustfile08.py")
Locust 通過協程實現單機大量並行,但對多核 CPU 的支援並不好,可通過在一臺機器上啟動多個 Locust 範例實現對多核 CPU 的利用(單機分散式) ,同理:單臺計算機不足以模擬所需的使用者數量,Locust 也支援在多臺計算機上進行分散式負載測試。
一種是單機設定master和slave模式,另外一種是有多個機器,其中一個機器設定master,其它機器設定slave節點
注意:主節點master計算機和每個work工作節點計算機都必須具有 Locust 測試指令碼的副本。
其中 slave 的節點數要小於等於本機的處理器數
步驟: 以單臺計算機為例(既當做主控機,也當做工作機器)
Step1:→ 啟動locust master節點
locust -f locustfile07.py --master
Step2:→ 每個工作節點 locust -f locustfile07.py --worker
locust -f locustfile07.py --master
locust -f locustfile.py --worker --master-host=192.168.x.xx
更多引數介紹
將 locust 設定為 master 模式。Web 介面將在此節點上執行。
將locuster設定為worker模式。
可選擇與-- worker一起使用,以設定主節點的主機名/IP (預設值為127.0.0.1)
可選地與-- worker一起用於設定主節點的埠號(預設值為5557)。
-master-bind-host= X. X. X. X
可選擇與--master一起使用。 確定主節點將繫結到的網路介面。 預設為*(所有可用介面)。
--master-bind-port=5557
可選擇 與--master一起使用。 確定主節點將偵聽的網路埠。 預設值為5557。
--expect-workers= X
在使用--headless啟動主節點時使用。 然後主節點將等待,直到 X worker節點已經連線,然後測試才開始。
拉取映象:
docker pull locustio/locust
執行容器:
docker run -p 8089:8089 -v $PWD:/mnt/locust locustio/locust -f /mnt/locust/locustfile.py
Docker Compose:
version: '3'
services:
master:
image: locustio/locust
ports:
- "8089:8089"
volumes:
- ./:/mnt/locust
command: -f /mnt/locust/locustfile.py --master -H http://master:8089
worker:
image: locustio/locust
volumes:
- ./:/mnt/locust
command: -f /mnt/locust/locustfile.py --worker --master-host master
Locust 的預設 HTTP 使用者端使用python-requests。如果您計劃以非常高的吞吐量執行測試並且執行 Locust 的硬體有限,那麼它有時效率不夠。Locust 還附帶FastHttpUser
使用geventhttpclient代替。它提供了一個非常相似的 API,並且使用的 CPU 時間顯著減少,有時將給定硬體上每秒的最大請求數增加了 5 到 6 倍。
在相同的並行條件下使用FastHttpUser能有效減少對負載機的資源消耗從而達到更大的http請求。
比對結果如下:
locustfile11.py
HttpUser:
對比:FastHttpUser
程序:程序是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程序是系統進行資源分配和排程的一個獨立單位。每個程序都有自己的獨立記憶體空間,不同程序通過程序間通訊來通訊。由於程序比較重量,佔據獨立的記憶體,所以上下文程序間的切換開銷(棧、暫存器、虛擬記憶體、檔案控制程式碼等)比較大,但相對比較穩定安全。
執行緒: 執行緒是程序的一個實體,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位.執行緒自己基本上不擁有系統資源,只擁有一點在執行中必不可少的資源(如程式計數器,一組暫存器和棧),但是它可與同屬一個程序的其他的執行緒共用程序所擁有的全部資源。執行緒間通訊主要通過共享記憶體,上下文切換很快,資源開銷較少,但相比程序不夠穩定容易丟失資料。
協程: 協程是一種使用者態的輕量級執行緒,協程的排程完全由使用者控制。協程擁有自己的暫存器上下文和棧。協程排程切換時,將暫存器上下文和棧儲存到其他地方,在切回來的時候,恢復先前儲存的暫存器上下文和棧,直接操作棧則基本沒有核心切換的開銷,可以不加鎖的存取全域性變數,所以上下文的切換非常快。
、
程序與執行緒比較:
程序用於獨立的地址空間 ;執行緒依附於程序(先有程序後有執行緒) ,可以共用程序的地址空間
程序之間不共用全域性變數 , 執行緒之間共用全域性變數
執行緒是cpu 排程的基本單位; 程序是作業系統分配資源資源的最小單位
程序之間相互獨立 ,都可並行執行 (核數大於執行緒數)
多程序執行其中某個程序掛掉不會影響其他程序執行, 多執行緒開發中當前程序掛掉 依附於當前程序中的多執行緒進行銷燬
執行緒與協程比較
一個執行緒可包含多個協程 ,一個程序也可單獨擁有多個協程
執行緒、程序 同步機制 ,協程非同步
協程保留最近一次呼叫時狀態,每次過程重入相當於喚醒
執行緒的切換由作業系統負責排程,協程由使用者自己進行排程
資源消耗:執行緒的預設Stack大小是1M,而協程更輕量,接近1K。
執行緒: 輕量級的程序 協程: 輕量級的執行緒 (使用者態)
更多查閱:
如果Locust檔案位於與locustfile.py在不同的子目錄/或者檔名不一樣,則使用引數-f+檔名:
$ locust -f locust_files/my_locust_file.py
要在多個程序中執行Locust,我們可以通過指定--master:
$ locust -f locust_files/my_locust_file.py --master
啟動任意數量的從屬程序:
$ locust -f locust_files/my_locust_file.py --slave
如果要在多臺機器上執行Locust,則在啟動從屬伺服器時還必須指定主伺服器主機(在單臺計算機上執行Locust時不需要,因為主伺服器主機預設為127.0.0.1):
$ locust -f locust_files/my_locust_file.py --slave --master-host=192.168.0.100
還可以在組態檔(locust.conf或~/.locust.conf)或以LOCUST_字首的env vars中設定引數
例如:(這將與上一個命令執行相同的操作)
$ LOCUST_MASTER_HOST=192.168.0.100 locust
注意:要檢視所有可用選項,請鍵入:locust —help
https://docs.locust.io/en/stable/what-is-locust.html
相同:
不同:
參考資料:https://github.com/locustio/locust/wiki/Articles