天天看點

goreplay 使用經驗1. 用法2. 坑3. 深入

測試 HTTP 服務,為了覆寫更多的場景,可以考慮錄制線上流量,在測試環境進行重放。之前用 tcpcopy 比較多,最近遇到一些需求,需要在 HTTP 層做一些過濾,例如隻錄制指定 URL 的請求。

經過調研,發現

goreplay ,其前稱是 gor,很适合這個場景,有以下優點。

  1. 支援 HTTP 請求的錄制和重放,可以線上上錄制請求,在測試環境進行重放。
  2. 支援 HTTP 層面的流量過濾,可以隻挑選我們感興趣的流量。
  3. 支援請求放大,用于性能測試。

1. 用法

免 root 運作,抓包并不需要 root 權限,同樣的方法适用于 tcpdump,其實 goreplay 和 tcpdump 一樣,都用 libpcap 來抓包。

$ sudo setcap "cap_net_raw,cap_net_admin+eip" ./goreplay           

抓取 80 端口的 HTTP 請求,隻抓請求 URL 是 /api/v1 的,并輸出到終端。這個比 tcpdump 更直覺,列印到終端的是我們熟悉的 HTTP 協定。

第一行是 goreplay 自定義的 header,平常使用可以不必理會,不是實際抓到的包,從第二行開始才是實際抓到的包。

$ ./goreplay --input-raw :80 --http-allow-url '/api/v1' --output-stdout           

抓取 80 端口的所有請求,并儲存到檔案。實際會分批儲存為 request_0.gor,request_1.gor 這種檔案名。

$ ./goreplay --input-raw :80 --output-file 'request.gor'           

重放請求,例如 host2.com 是我們的新機房域名。這種重放,會根據請求的時間戳,按照抓取時的請求順序重放。

例如抓取的時候,第一秒 10 個請求,第二秒 20 個請求,那麼重放的時候,也會按照這個順序。并且讀完 request.gor 檔案,就會停止。

上面我們看到了 goreplay 自定義的 header,其第三個字段,是一個納秒級的時間戳,根據這個來保證重放的順序和速率。

$ ./goreplay --input-file 'request.gor' --output-http 'http://host2.com'           

如果是性能測試,可以不考慮請求的順序和速率,并且要求無限循環。

# --input-file 從檔案中擷取請求資料,重放的時候 100x 倍速
# --input-file-loop 無限循環,而不是讀完這個檔案就停止
# --output-http 發送請求到 http://host2.com
# --output-http-workers 并發 100 發請求
# --stats --output-http-stats 每 5 秒輸出一次 TPS 資料
$ ./goreplay --input-file 'request.gor|10000%' --input-file-loop --output-http 'http://host2.com' --output-http-workers 100 --stats --output-http-stats           

更多的指令行參數及用法,可以檢視 goreplay 源碼的

settings.go

檔案。

其輸出的 stats 含義,可以看看

這個文章

的解釋。

2. 坑

使用過程中遇到的坑。

  1. 如果 HTTP 請求不符合規範,可能會抓不到包。遇到過 HTTP 請求頭裡面的 Content-Length 不等于實際的 Body 大小,goreplay 認為其請求未結束。
  2. input-file 是單 goroutine 在跑,會有性能瓶頸。測試的時候,讀取的 RPS 在 1.7w - 1.8w 左右,如果壓測需求大于這個,需要開多個程序同時跑。

3. 深入

goreplay 是用 golang 編寫的,抓包的時候調用 gopacket,後者通過 cgo 來調用 libpcap。從編譯開始,從源碼層面學習一下其實作。

TL;DR

3.1 編譯

由于 goreplay 使用了第三方 C 代碼,不能使用 Go 的交叉編譯功能來跨平台編譯。隻能在 Linux 下編譯 Linux 使用的可執行檔案。

在 Redhat 系發型版下,可以使用 yum 來安裝依賴。

$ sudo yum install libpcap libpcap-devel           

或者從源碼編譯 libpcap。

# 安裝依賴
$ sudo yum install gcc flex byacc bison
$ wget http://www.tcpdump.org/release/libpcap-1.8.1.tar.gz && tar xzf libpcap-1.8.1.tar.gz
$ cd libpcap-1.8.1
$ ./configure
$ sudo make install           

cd 到 goreplay 的源碼目錄,執行指令。

# 純靜态編譯
$ go build -ldflags '-extldflags "-static"'           

如果編譯成功,在目前目錄會生成一個 goreplay 檔案,試運作一下。

[vagrant@localhost goreplay]$ ./goreplay
Version:
2018/06/08 08:06:22 Required at least 1 input and 1 output           

不依賴任何庫。

[vagrant@localhost goreplay]$ ldd goreplay
    not a dynamic executable           

一個合法的可執行檔案。

[vagrant@localhost goreplay]$ file goreplay
goreplay: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=e334de401c00057ca56a33c0136dc5c86debee61, not stripped           

如果遇到以下編譯錯誤。

[vagrant@localhost goreplay]$ go build -ldflags '-extldflags "-static"'
# github.com/buger/goreplay
/home/vagrant/local/go/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
/usr/bin/ld: cannot find -lpthread
/usr/bin/ld: cannot find -lc
collect2: error: ld returned 1 exit status           

需要安裝 glibc 的靜态庫。

$ sudo yum install glibc-static.x86_64           

如果不是通過 go get 來擷取 goreplay,而是通過 git clone 下來的,那麼會缺少一些第三方庫的依賴,通過 go get 指令補充就好。

$ go get github.com/Shopify/sarama           

3.2 input 和 output

input 和 output 是 goreplay 對資料流的抽象,在源碼目錄有很多 input_xxx.go 和 output_xxx.go,實作了 goreplay 的核心功能。

在啟動的時候,會解析指令行參數中指定的 input 和 output,接着啟動 emitter,從 input 中讀資料,寫到 output 中。

emitter.go 中核心代碼如下:

for _, in := range Plugins.Inputs {
    go CopyMulty(in, Plugins.Outputs...)
}           

多個 input 之間是并行的,但單個 input 到多個 output,是串行的。所有 input 都實作了 io.Reader 接口,output 都實作了 io.Writer 接口。是以閱讀代碼時,input 的入口是 Read() 方法,output 的入口是 Write() 方法。

3.3 UDP 抓包

抓包是核心功能,也算是一種 input 的類型。不過 goreplay 的實作中,實作上和 HTTP 協定綁定的很死。我參考 goreplay 的代碼,實作了

goreplay-udp

,用法上和 goreplay 保持一緻。

繼續閱讀