編寫一個接口壓測工具

前言
前段時間有個項(xiàng)目即將上線,需要對其中的核心接口進(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è)計(jì)模式
整個開發(fā)過程中還是有幾個點(diǎn)想和大家分享,首先是設(shè)計(jì)模式。
因?yàn)橐婚_始設(shè)計(jì)時就考慮到需要支持不同的壓測模式(次數(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???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/
最后如果有同樣在學(xué)習(xí) go 的朋友,特別是有 Java 開發(fā)經(jīng)驗(yàn)的(這里大部分應(yīng)該都寫 Java)朋友,感興趣的可以在公眾號后臺回復(fù) "go群" 加入我創(chuàng)建的一個與 go 開發(fā)相關(guān)的技術(shù)群。
