讓我們一起編寫一個接口壓測工具
前言
前段時間有個項目即將上線,需要對其中的核心接口進(jìn)行壓測;由于我們的接口是 gRPC 協(xié)議,找了一圈發(fā)現(xiàn)壓測工具并不像 HTTP 那么多。
最終發(fā)現(xiàn)了 ghz 這個工具,功能也非常齊全。
事后我在想為啥做 gRPC 壓測的工具這么少,是有什么難點(diǎn)嘛?為了驗(yàn)證這個問題于是我準(zhǔn)備自己寫一個工具。
特性
前前后后大概花了個周末的時間完成了相關(guān)功能。
https://github.com/crossoverJie/ptg/
也是一個命令行工具,使用起來效果如上圖;完整的命令如下:
- NAME:
 - ptg - Performance testing tool (Go)
 - USAGE:
 - ptg [global options] command [command options] [arguments...]
 - COMMANDS:
 - help, h Shows a list of commands or help for one command
 - GLOBAL OPTIONS:
 - --thread value, -t value -t 10 (default: 1 thread)
 - --Request value, --proto value -proto http/grpc (default: http)
 - --protocol value, --pf value -pf /file/order.proto
 - --fully-qualified value, --fqn value -fqn package.Service.Method
 - --duration value, -d value -d 10s (default: Duration of test in seconds, Default 10s)
 - --request value, -c value -c 100 (default: 100)
 - --HTTP value, -M value -m GET (default: GET)
 - --bodyPath value, --body value -body bodyPath.json
 - --header value, -H value HTTP header to add to request, e.g. "-H Content-Type: application/json"
 - --target value, --tg value http://gobyexample.com/grpc:127.0.0.1:5000
 - --help, -h show help (default: false)
 
考慮到受眾,所以同時支持 HTTP 與 gRPC 接口的壓測。
做 gRPC 壓測時所需的參數(shù)要多一些:
- ptg -t 10 -c 100 -proto grpc -pf /xx/xx.proto -fqn hello.Hi.Say -body test.json -tg "127.0.0.1:5000"
 
比如需要提供 proto 文件的路徑、具體的請求參數(shù)還有請求接口的全路徑名稱。
目前只支持最常見的 unary call 調(diào)用,后續(xù)如果有需要的話也可以 stream。
同時也支持壓測時間、次數(shù)兩種壓測方式。
安裝
想體驗(yàn)度朋友如果本地有 go 環(huán)境那直接運(yùn)行:
- go get github.com/crossoverJie/ptg
 
沒有環(huán)境也沒關(guān)系,可以再 release 頁面下載與自己環(huán)境對應(yīng)的版本解壓使用。
https://github.com/crossoverJie/ptg/releases
設(shè)計模式
整個開發(fā)過程中還是有幾個點(diǎn)想和大家分享,首先是設(shè)計模式。
因?yàn)橐婚_始設(shè)計時就考慮到需要支持不同的壓測模式(次數(shù)、時間;后續(xù)也可以新增其他的模式)。
所以我便根據(jù)壓測的生命周期定義了一套接口:
- type (
 - Model interface {
 - Init()
 - Run()
 - Finish()
 - PrintSate()
 - Shutdown()
 - }
 - )
 
從名字也能看出來,分別對應(yīng):
- 壓測初始化
 - 運(yùn)行壓測
 - 停止壓測
 - 打印壓測信息
 - 關(guān)閉程序、釋放資源
 
然后在兩個不同的模式中進(jìn)行實(shí)現(xiàn)。
這其實(shí)就是一個典型的依賴倒置原則。
程序員要依賴于抽象接口編程、不要依賴具體的實(shí)現(xiàn)。
其實(shí)大白話就是咱們 Java 里常說的面向接口編程;這個編程技巧在開發(fā)框架、SDK或是多種實(shí)現(xiàn)的業(yè)務(wù)中常用。
好處當(dāng)然是顯而易見:當(dāng)接口定義好之后,不同的業(yè)務(wù)只需要根據(jù)接口實(shí)現(xiàn)自己的業(yè)務(wù)就好,完全不會互相影響;維護(hù)、擴(kuò)展都很方便。
支持 HTTP 和 gRPC 也是同理實(shí)現(xiàn)的:
- type (
 - Client interface {
 - Request() (*Response, error)
 - }
 - )
 
當(dāng)然前提得是前期的接口定義需要考慮周全、不能之后頻繁修改接口定義,這樣的接口就沒有意義了。
goroutine
另外一點(diǎn)則是不得不感嘆 goroutine+select+channel 這套并發(fā)編程模型真的好用,并且也非常容易理解。
很容易就能寫出一套并發(fā)代碼:
- func (c *CountModel) Init() {
 - c.wait.Add(c.count)
 - c.workCh = make(chan *Job, c.count)
 - for i := 0; i < c.count; i++ {
 - go func() {
 - c.workCh <- &Job{
 - thread: thread,
 - duration: duration,
 - count: c.count,
 - target: target,
 - }
 - }()
 - }
 - }
 
比如這里需要初始化 N 個 goroutine 執(zhí)行任務(wù),只需要使用 go 關(guān)鍵字,然后利用 channel 將任務(wù)寫入。
當(dāng)然在使用 goroutine+channel 配合使用時也得小心 goroutine 泄露的問題;簡單來說就是在程序員退出時還有 goroutine 沒有退出。
比較常見的例子就是向一個無緩沖的 channel 中寫數(shù)據(jù),當(dāng)沒有其他 goroutine 來讀取數(shù)時,寫入的 goroutine 就會被一直阻塞,最終導(dǎo)致泄露。
總結(jié)
有 gRPC 接口壓測需求的朋友歡迎試用,提出寶貴意見;當(dāng)然 HTTP 接口也可以。
源碼地址:https://github.com/crossoverJie/ptg/





















 
 
 










 
 
 
 