從零開始用golang編寫一個分散式測試工具

雨落雲風發表於2017-09-27

起源

當開發http 介面的時候,往往我們會關心開發的server能承受多少壓力,這時候一個比較常用的工具是 apache bench。一部分情況下ab工具確實能滿足需求,但是很多時候並不能,需要分散式式測試工具。

壓力測試比較關心的是產生壓力獲取測試物件的如TPS、響應時延等效能資料主機資源如cpu,memory消耗資料以定位效能瓶頸,簡單的單機測試工具並不能很好的滿足這些需求。

我們可以選擇雲平臺的分散式測試工具,比如騰訊的wetest,阿里雲也有類似產品。但是這種產品往往收費不菲。也可以選擇類似的開源產品,比如locust。但是調研發現,這種開源產品往往比較簡單,或者過於陳舊。google官網有一個壓力測試例子,用的就是locust。大家可以看一下這個工具,基於python,功能非常簡陋,master,slave模式,不支援線上編輯指令碼,修改測試要重啟。

如何設計這樣的壓力測試工具

k8s是目前比較流行的容器編排系統,是否可以在k8s上自己做一個分散式測試工具呢。當然可以用google推薦的做法,在k8s上執行master slave 模式的locust,又或者自己動手做一個。
既然執行在k8s上,那麼這個測試工具實際上關心的事就比較簡單了:k8s已經實現了排程,資源監控,我們的工具只需要定義指令碼,執行指令碼,統計測試結果,收集測試過程的資源消耗。
需要說明的是,測試工具執行在k8s上只是為了利用k8s的基礎設施,簡化工具設計,事實上測試的物件可以是執行在k8s上或者在k8s外的任何服務。

使用golang做測試指令碼

選擇golang作為測試指令碼的原因一是語言成熟,語法簡單,二是goroutine很方便,很容易把壓力打上去,三是即有編譯型語言的高效能,同時又像指令碼一樣能夠快速執行(編譯很快)。

golang本身是編譯型語言,不是指令碼語言,執行要先編譯,但是因為編譯很快,實際上很容易當成一個指令碼來執行,比如這個例子。或者顯式的執行go build,go run,像docker/distribution專案的dockerfile這樣。那麼要實現一個"動態"執行golang的工具要做的就是:定義一種型別的任務,使用者的測試指令碼只要實現這種任務的interface,客戶端就可以裝載這種指令碼編譯執行。具體舉例如下。

// 1. 定義一個TTask作為interface
type TTask interface {
    Name() string
    Run() int
}

// 2. 使用者的測試指令碼實現這個interface
// New ....
func newhello() task.TTask {
    return &hellotask{}
}

type hellotask struct {
}

func (h *hellotask) Name() string {
    return "task"
}

func (h *hellotask) Run() int {
    time.Sleep(time.Microsecond * 10)
    return 0
}

// 3. 實現一種註冊式的外掛機制,讓使用者的任務註冊進來
var tasksets = make(map[string]NewFunc, 0)
func Register(newfunc NewFunc) {
    t := newfunc()
    _, registered := tasksets[t.Name()]
    if registered {
        panic(fmt.Sprintf("TTask named %s already registered", t.Name()))
    }
    tasksets[t.Name()] = newfunc
}

// 使用者指令碼中要註冊他的指令碼
Register(task.NewFunc(newhello))

// 4. 測試客戶端不關心使用者指令碼的實現細節,執行tasksets裡面TTask就可以了
func main() {
    for _, f := range tasksets {
        f().Run()
        ...
    }
}複製程式碼

一個更復雜一點的例子在這裡

使用k8s的基礎設施

首先實現測試任務,使用job是很合適不過的,設定parallelism併發執行。

其次要實現任務的動態新增和掛載,可以使用k8s的configmap來實現。使用configmap來儲存使用者的指令碼,執行agent的時候將指令碼自動掛載到agent的容器對應路徑,容器啟動指令碼中加入build流程,這樣就能很方便的實現一種"動態"的執行golang指令碼的效果了。

當壓力測試使用多個節點的時候,我們往往需要同時觀測測試客戶端和服務端的cpu等資源監控,因為客戶端已經天然的執行在k8s上了,可以直接使用k8s的監控設施。
另外測試工具的設計並沒有侷限在測試執行在k8s上的server,但是如果剛好,被測試的物件也執行在k8s上,那麼也可以很方便的或者server的宿主機metrics,如果不是,那麼server端就需要裝一個收集metric的deamon實現同樣的效果了。

效果

dashboard支援檢視測試任務,每個任務有一個最近執行的記錄和建立時間。

編輯一個測試專案,測試指令碼是用golang編輯的,需要實現一個TTaskSet 的interface。支援設定任務的goroutine和執行時間,權重,設定權重之後goroutine數量會在多個taskset之間分配,一個taskset又可以新增多個task。taskset併發執行,一個taskset中的task序列執行,這樣設計的好處是可以滿足使用者併發,序列,帶context的序列多種需求,非常靈活。任務可以選擇執行的節點,多個節點併發測試。

執行完測試的效果,目前還沒有加入測試中的client,server資源監控,但是簡單的測試統計已經有了。如圖是測試的一個執行在1G虛擬機器的nginx容器的測試結果。測試結果同時有各個節點的執行結果和彙總結果,同時繪製latency的百分點陣圖。


完整專案地址在 github.com/arlert/ymir 歡迎拍磚。

相關文章