如何提高 Locust 的壓測效能
上一篇文章深入淺出 Locust 實現最後埋了兩個伏筆,那麼今天我們繼續探討其一——如何提高Locust的壓測效能?
Locust的效能缺陷
一、GIL
熟悉Python的人應該都知道,基於cpython編譯器的python天生就受限於它的全域性解釋鎖GIL(global interpreter lock),儘管可以使用jython、pypy擺脫GIL但是很多經典的庫在它們上面還沒有經過嚴格的測試。好在Locust使用基於gevent的協程來實現併發,實際上,使用了 libev 或者 libuv 作為eventloop的gevent可以極大地提高Python的併發能力,擁有不比JAVA多執行緒併發模型差的能力。然而,還是由於GIL,gevent也只是釋放了單核CPU的能力,導致Locust的併發能力必須通過起與CPU核數相同的slave才能發揮出來。
二、效能不佳的requests庫
Locust預設使用requests作為http請求庫,瞭解requests庫的人,無不驚訝於它設計得如此精妙好用的API。然而,在效能上卻與它的易用性相差甚遠,如果需要提高施壓能力,可以使用fasthttp,預估能提高5倍左右的效能,但是正如Locust作者所說的,fasthttp目前並不能完全替代requests。
三、rt評估不準
相信有些人吐槽過,在併發比較大的情況下,Locust的響應時間rt波動比較大,甚至變得不可信。rt是通過slave去統計的,因此併發大導致slave不穩定也是Locust被人詬病的問題之一,下面我們看一張壓測對比圖:
簡單說明上圖中顯示的是在100併發下,各個壓測工具對相同系統壓測(未到瓶頸),響應時間的中位數。可以看到介面正常rt應該在2ms以內,而Locust統計到的卻是30ms。(可以看到jmeter也好不到哪裡去,真是難兄難弟啊~)
以上便是我認為Locust目前所面臨的效能問題,只有解決了這三個問題,才能讓Locust成為真正能夠投入『生產使用』的工具。隨著整體測試人員能力的提升,與Jmeter的GUI介面點點點的方式相比,Locust的可程式設計性、靈活性和可玩性會更強一些,頗有些hack for fun的感覺。
如何提高Locust的施壓效能
一、增加slave?
原理上,支援分散式壓測的系統都可以通過不斷地增加施壓機來提高併發能力,但是這會增加機器成本和維護成本,Locust不僅支援單機、也支援分散式壓測,但是,不斷增加slave顯然不是一個很好的方案。
二、多執行緒or多程式?
Python多執行緒受GIL的影響較大,只有在IO密集型的場景下才能體現併發的優勢,如果執行緒與併發使用者是一一對應的關係,那麼就又回到GIL的問題了,無法獲得令人滿意的併發效能,如果是執行緒與併發使用者是一對多,那不如使用協程。而多程式,採用單slave多程式的方式似乎可以擺脫GIL的影響,單機可以不用起那麼多slave,但是這與單機多slave相比效能並沒有得到本質上的提升,此外單slave多程式的方式無疑會造成多程式間的IPC消耗,更不用說實現上的複雜程度了。
三、換一種語言?
有沒有可能換一種語言?重新實現一套施壓端slave端的邏輯?這種語言需要天生擁有強大的併發能力,支援與master溝通的語言Zeromq。
還真的有這樣的語言: Golang
Golang下的goroutine
- 可以理解是使用者態執行緒,goroutine的切換沒有核心開銷
- 記憶體佔用小,執行緒棧空間通常是 2M,goroutine 棧空間最小 2K
- G-M-P排程模型
那麼有沒有一個開源專案是用go實現了Locust的slave端呢?
答案是有的,它就是: boomer
目前boomer除了比較完整實現了Locust的slave端邏輯,還內建支援指定TPS,理論上支援任意協議的壓測。
然而,boomer對Locust那套Event機制支援的還不足,也無法把自定義資料上報給master,但不妨礙它成為一個優秀的壓力生成器。
Boomer example
在boomer專案中有非常多的examples,同時也提供了簡潔明瞭的說明文件,無論你是否熟悉go,相信也能很快上手,我一般在兩種場景下使用Locust + boomer,一是壓測http服務但又需要較大的併發能力,二是需要壓測一些非http協議的系統。下面,我就以壓測grpc協議的系統為例(講http已經顯得有點無趣了,,,),講解一下我是如何通過boomer提高Locust的壓測效能的。
一、grpc服務
gRPC是一個高效能、通用的開源RPC框架,其由Google主要面向移動應用開發並基於HTTP/2協議標準而設計,基於ProtoBuf(Protocol Buffers)序列化協議開發,且支援眾多開發語言。
gRPC具有以下重要特徵:
- 強大的IDL特性 RPC使用ProtoBuf來定義服務,ProtoBuf是由Google開發的一種資料序列化協議,效能出眾,得到了廣泛的應用。
- 支援多種語言 支援C++、Java、Go、Python、Ruby、C#、Node.js、Android Java、Objective-C、PHP等程式語言。
- 基於HTTP/2標準設計
官網上有非常多語言的快速入門,為了演示跨語言呼叫,且boomer是基於go語言的,所以我演示的案例是go->python。首先根據官方文件的指引,起一個helloworld的grpc服務。
根據python 的快速入門,起一個基於Python的grpc服務。
-> % python greeter_server.py
為了驗證服務是否正常啟動了,先直接使用greeter_client.py驗證一下:
-> % python greeter_client.py
Greeter client received: Hello, you!
二、序列化和反序列化
為了從boomer側發起請求,首先需要對請求和響應做序列化與反序列化,在閱讀grpc的原始碼後,定義一個結構體ProtoCodec:
// ProtoCodec ...
type ProtoCodec struct{}
// Marshal ...
func (s *ProtoCodec) Marshal(v interface{}) ([]byte, error) {
return proto.Marshal(v.(proto.Message))
}
// Unmarshal ...
func (s *ProtoCodec) Unmarshal(data []byte, v interface{}) error {
return proto.Unmarshal(data, v.(proto.Message))
}
// Name ...
func (s *ProtoCodec) Name() string {
return "ProtoCodec"
}
三、服務呼叫
我的想法是提供一套呼叫grpc服務的通用client,所以呼叫服務+方法時需要是動態的,正好grpc提供了Invoke方法可以滿足這一點,接下來定義一個Requester結構體。
// Requester ...
type Requester struct {
addr string
service string
method string
timeoutMs uint
pool pool.Pool
}
Requester中定義兩個方法,一個是獲取真實的呼叫方法getRealMethodName,一個是發起請求的方法Call,其中Call是暴露給外層呼叫的。
// getRealMethodName
func (r *Requester) getRealMethodName() string {
return fmt.Sprintf("/%s/%s", r.service, r.method)
}
Call方法核心程式碼
if err = cc.(*grpc.ClientConn).Invoke(ctx, r.getRealMethodName(), req, resp, grpc.ForceCodec(&ProtoCodec{})); err != nil {
fmt.Fprintf(os.Stderr, err.Error())
return err
}
四、連線池
如http1.1的Keep-Alive,在高併發下需要保持grpc連線以提高效能,所以需要實現一個grpc的連線池管理,這也是Requester結構體中pool的職責。
Requester例項化時初始化連線池:
// NewRequester ...
func NewRequester(addr string, service string, method string, timeoutMs uint, poolsize int) *Requester {
//factory 建立連線的方法
factory := func() (interface{}, error) { return grpc.Dial(addr, grpc.WithInsecure()) }
//close 關閉連線的方法
closef := func(v interface{}) error { return v.(*grpc.ClientConn).Close() }
//建立一個連線池: 初始化5,最大連線200,最大空閒10
poolConfig := &pool.Config{
InitialCap: 5,
MaxCap: poolsize,
MaxIdle: 10,
Factory: factory,
Close: closef,
//連線最大空閒時間,超過該時間的連線 將會關閉,可避免空閒時連線EOF,自動失效的問題
IdleTimeout: 15 * time.Second,
}
apool, _ := pool.NewChannelPool(poolConfig)
return &Requester{addr: addr, service: service, method: method, timeoutMs: timeoutMs, pool: apool}
}
這裡使用了開源庫 pool 來做grpc的連線管理。在Call方法中每次發起請求前在連線池中獲取一個連線,呼叫完成後放回連線池中。
五、指令碼編寫
接下來就是編寫boomer指令碼了,我們需要兩個檔案,一個定義pb結構的請求和響應,一個是執行邏輯main.go
a、基於.proto生成供go使用的pb.go檔案
grpc使用PB結構傳輸訊息,.proto檔案定義了PB資料,使用protoc工具可以生成直接給不同語言使用的資料結構和介面定義檔案,如下
-> % protoc helloworld.proto --go_out=./
執行成功後生成helloworld.pb.go檔案,供main.go引用。
b、編寫壓測指令碼main.go
在helloworld例子中存在兩個PB物件,分別是HelloRequest、HelloReply,python暴露的rpc服務和介面分別為helloworld.Greeter和SayHello,所以呼叫方式如下:
// 修改為要壓測的服務介面
var service = "helloworld.Greeter"
var method = "SayHello"
...
client = grequester.NewRequester(addr, service, method, timeout, poolsize)
...
startTime := time.Now()
// 構建請求物件
request := &HelloRequest{}
request.Name = req.Name
// 初始化響應物件
resp := new(HelloReply)
err := client.Call(request, resp)
elapsed := time.Since(startTime)
完整的檔案地址請看 main.go
c、除錯指令碼
使用boomer的 --run-tasks 除錯指令碼
-> % cd examples/rpc
-> % go run *.go -a localhost:50051 -r '{"name":"bugVanisher"}' --run-tasks rpcReq
2020/04/21 21:31:11 {"name":"bugVanisher"}
2020/04/21 21:31:11 Running rpcReq
2020/04/21 21:31:11 Resp Length: 29
2020/04/21 21:31:11 message:"Hello, bugVanisher!"
至此,基於boomer的grpc壓測指令碼已經完成了,剩下的就是結合Locust對被測系統進行壓測了,我這裡就不贅述了。這僅是一個演示,真實的業務一般會針對grpc框架做封裝,也許不同的語言有各自完整的一套開源框架了。需要注意的地方是,不同的框架下,我們Invoke時,真實的method可能有所不同,要根據實際情況做修改。
小結
本文簡單說明了Locust目前的一些效能缺陷,以及展示瞭如何壓測一個官方Demo的grpc服務介面,實踐發現,一臺使用boomer的4C8G壓力機,能夠很輕鬆輸出上萬的併發數,以及數萬的tps,這是Locust自帶的WorkerRunner無法企及的。
關於Locust,我還想分享一篇文章——重新定義Locust的測試報告,敬請期待~
相關文章
- locust壓測
- 效能測試——壓測工具locust——指令碼初步編寫指令碼
- Locust 壓測websocket協議Web協議
- 黑羽壓測 比 jmeter、locust、loadrunner 更簡便,效能更強JMeter
- Locust效能測試實踐
- RocketMQ這樣做,壓測後效能提高30%MQ
- 如何做“健康碼”的效能壓測
- 使用 locust 對 mysql 語句進行壓測MySql
- [效能測試] locust學習-基礎篇
- locust多程序實現分散式壓測遇到的問題分散式
- 在Rainbond上使用Locust進行壓力測試AI
- 我有個想法使用 locust 作為壓測核心, 寫一個服務端效能測試平臺,服務端
- mysqlslap效能壓測MySql
- mysqlslap 效能壓測MySql
- 如何提高python程式的效能Python
- Locust效能測試工具核心技術@task和@events
- Python技術棧效能測試工具Locust入門Python
- locust 與 jmeter 效能測試對比會更優?JMeter
- 效能壓測工具 —— wrk
- 前端搬磚工三天入門Locust壓力測試前端
- docker && k8s 分散式壓測 locust_boomer 方案DockerK8S分散式OOM
- 重新定義 Locust 的測試報告_效能監控平臺測試報告
- 伺服器的效能如何提高伺服器
- 面向Web應用的併發壓力測試工具——Locust實用攻略Web
- Locust效能測試設定持續時間(web-UI)WebUI
- Locust1.4.3版本效能測試工具案例分享
- 魔改 locust:基於 locust 和 boomer 核心,構建一個簡單 http 介面壓測共享平臺OOMHTTP
- 深入淺出開源效能測試工具 Locust (使用篇 2)
- Locust+InfluxDB+Grafana 效能測試資料視覺化展示UXGrafana視覺化
- 如何提高你的 React 應用的效能React
- 如何提高前端效能——字型篇前端
- 效能測試:主流壓測工具介紹
- Taurus.MVC 效能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET 版本MVCLinux
- Jmeter效能測試 —— 壓力模式JMeter模式
- 壓測和效能分析方法論
- python輕量級效能工具-LocustPython
- Taurus.MVC 效能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本MVCLinux
- 年度大促將至,企業如何進行效能壓測