使用 GoReplay 進行 HTTP 流量復制
GoReplay 是一款開源網絡監控工具,可以記錄您的實時流量,并將其用于shadowing、load testing、monitoring和detailed analysis。捕獲實時HTTP流量并將其重放到測試環境中,以便使用真實數據持續測試您的系統。

1一、安裝
GoReplay 采用 Go 編寫,其只有一個單獨的可執行文件,在官方 Release[1] 頁下載后將其放到 PATH 目錄即可。
wget https://github.com/buger/goreplay/releases/download/v1.2.0/gor_v1.2.0_x64.tar.gz tar -zxvf gor_v1.2.0_x64.tar.gz mv gor /usr/local/bin
2二、基本使用
GoReplay 命令行整體使用方式為指定輸入端和輸入端,然后 GoReplay 從輸入端將流量復制到輸出端。
2.1、實時流量復制
GoReplay 輸入端可以指定一個 tcp 地址,然后 GoReplay 將該端口流量復制到輸出端;下面樣例展示從 127.0.0.0:8000 復制流量并輸出到控制臺的樣例。
首先啟動一個 HTTP Server,這里直接使用 python 的 HTTP Server

接著再讓 gor 監聽同樣的端口,--output-stdout 指定輸出端為控制臺

此時通過 curl 訪問 python 的 HTTP Server 可以看到 gor 將 HTTP 請求復制并輸出到了控制臺

同樣如果我們通過 --output-http 選項將輸出端指定為另一個 HTTP Server,那么 gor 會將請求同步復制并發送到輸出端 HTTP Server。

2.2、流量抓取與重放
2.2.1、基本使用
GoReplay 可以將輸出端指定為文件,從而將流量保存到文件中,然后 GoReplay 讀取該保存的流量文件并重放到指定的 HTTP Server 中。首先通過 --outpu-file 選項將請求保存到文件中

使用 --input-file 選項讀取流量信息,然后通過 --output-http 選項重放到目標服務器

2.2.2、擴展選項
在將流量保存到文件時,默認情況下 GoReplay 以塊形式寫入文件,并且每個塊將生成一個獨立的文件名(test_0.gor),如果想要將所有塊的流量全部寫入一個文件中,可以設置 --output-file-append 為 true。
同時 GoReplay 輸出文件名支持日期占位符,例如 --output-file %Y%m%d.gor 會生成 20210801.gor 這種文件名;所有可用的日期占位符如下:
%Y: year including the century (at least 4 digits)%m: month of the year (01..12)%d: Day of the month (01..31)%H: Hour of the day, 24-hour clock (00..23)%M: Minute of the hour (00..59)%S: Second of the minute (00..60)
請求比較多時,將流量保存到文件可能會導致文件很大,這時候可以使用 .gz 結尾作為文件名,GoReplay 讀取到 .gz 后綴后會自動進行 GZip 壓縮處理。
gor --input-raw :8000 --output-file test.gor.gz
如果需要對多個文件進行聚合重放,只需要指定多個文件即可,重放過程中 GoReplay 會自動保持請求順序:
gor --input-file *.gor --output-http http://127.0.0.1:8080
在使用文件輸入時,GoReplay 還支持壓力測試,通過 test.gor|200% 這種方式指定的文件名,GoReplay 會以兩倍的速率進行請求重放:
# Replay from file on 2x speed gor --input-file "requests.gor|200%" --output-http "staging.com"
2.3、數據丟失與緩沖區
GoReplay 采用比較底層的數據包攔截技術,當一個 TCP 數據包到達時內核 GoReplay 會進行攔截;然而數據包可以亂序到達,接下來內核需要重建 TCP 流來保證上層應用能以正確的順序讀取 TCP 數據包,這時候內核就會有一個數據包的緩沖區;默認情況下 Linux 系統的緩沖區為 2M,Windiws 為 1M,當特定的 HTTP 請求數據包超過緩沖區時,GoReplay 就無法正確的攔截(因為 GoReplay 需要一個完整的 HTTP 請求數據包用于保存到文件或者重放),同時可能會導致請求丟失、請求損壞等問題。
為了解決這種問題,GoReplay 提供了 --input-raw-buffer-size 選項用于調整緩沖區大小,例如 --input-raw-buffer-size 10485760 選項會將緩沖區調整為 10M。
2.4、速率限制
某些情況下可能為了方便調試,我們在生產環境抓取流量并鏡像到測試環境進行重放;但是可能由于生產環境流量比較大,我們并不需要如此大的請求速率,這時候可以通過速率限制讓 GoReplay 幫我們控制請求數量。
絕對數量限制: 使用 --output-http "ADDRESS|N" 形式的參數時,GoReplay 會保證鏡像的流量請求每秒不會超過 “N” 個。
# staging.server will not get more than ten requests per second gor --input-tcp :28020 --output-http "http://staging.com|10"
百分比限制限制: 使用 --output-http "ADDRESS|N%" 形式的參數時,GoReplay 會保證鏡像的流量維持在總流量的 “N%”。
2.5、請求過濾
在某些時候我們只期望把生產環境的特定流量重放到測試環境,或者禁止一些流量重放到測試環境,這時候我們可以使用 GoReplay 的過濾功能;GoReplay 提供以下選項來提供過濾功能:
--http-allow-header: 允許重放的 HTTP 頭(支持正則)--http-allow-method: 允許重放的 HTTP 方法--http-allow-url: 允許重放的 URL(支持正則)--http-disallow-header: 不允許的 HTTP 頭(支持正則)--http-disallow-url: 不允許的 HTTP URL(支持正則)
以下是官方給出的命令樣例:
# only forward requests being sent to the /api endpoint gor --input-raw :8080 --output-http staging.com --http-allow-url /api # only forward requests NOT being sent to the /api... endpoint gor --input-raw :8080 --output-http staging.com --http-disallow-url /api # only forward requests with an api version of 1.0x gor --input-raw :8080 --output-http staging.com --http-allow-header api-version:^1\.0\d # only forward requests NOT containing User-Agent header value "Replayed by Gor" gor --input-raw :8080 --output-http staging.com --http-disallow-header "User-Agent: Replayed by Gor" gor --input-raw :80 --output-http "http://staging.server" \ --http-allow-method GET \ --http-allow-method OPTIONS
2.6、請求重寫
有時候可能測試環境的 URL 路徑與生產環境完全不同,此時如果直接把生產環境的流量在測試環境重放可能會導致請求路徑錯誤等情況;為此 GoReplay 提供了 URL 重寫、參數設置、請求頭設置等功能。
通過 --http-rewrite-url 選項進行 URL 重寫
# Rewrites all `/v1/user//ping` requests to `/v2/user//ping` gor --input-raw :8080 --output-http staging.com --http-rewrite-url /v1/user/([^\\/]+)/ping:/v2/user/$1/ping
設置 URL 參數
gor --input-raw :8080 --output-http staging.com --http-set-param api_key=1
設置請求頭
gor --input-raw :80 --output-http "http://staging.server" \ --http-header "User-Agent: Replayed by Gor" \ --http-header "Enable-Feature-X: true"
Host 頭是一個特殊的請求頭,默認情況下 GoReplay 會將其自動設置為目標重放地址的域名,如果想關閉這種默認行為請使用 --http-original-host 選項。
3三、其他高級配置
3.1、中繼服務器
GoReplay 可以使用中繼服務器從而實現鏈式的流量傳遞,使用中繼服務器時只需要將輸出端設置為 TCP 模式,然后中繼服務器輸入端也設置為 TCP 模式即可:
# Run on servers where you want to catch traffic. You can run it on each `web` machine. gor --input-raw :80 --output-tcp replay.local:28020 # Replay server (replay.local). gor --input-tcp replay.local:28020 --output-http http://staging.com
如果有多個中繼服務器,可以使用 --split-output 選項讓每個抓取流量的 GoReplay 使用輪詢算法向每個中繼服務器發送流量:
gor --input-raw :80 --split-output --output-tcp replay1.local:28020 --output-tcp replay2.local:28020
3.2、輸出到 ElasticSearch
GoReplay 支持將輸出端設置為 ElasticSearch:
./gor --input-raw :8000 --output-http http://staging.com --output-http-elasticsearch localhost:9200/gor
輸出到 ES 時不需要預先創建索引,GoReplay 會自動完成,輸出到 ES 后其數據結構如下:
type ESRequestResponse struct {
ReqURL string `json:"Req_URL"`
ReqMethod string `json:"Req_Method"`
ReqUserAgent string `json:"Req_User-Agent"`
ReqAcceptLanguage string `json:"Req_Accept-Language,omitempty"`
ReqAccept string `json:"Req_Accept,omitempty"`
ReqAcceptEncoding string `json:"Req_Accept-Encoding,omitempty"`
ReqIfModifiedSince string `json:"Req_If-Modified-Since,omitempty"`
ReqConnection string `json:"Req_Connection,omitempty"`
ReqCookies string `json:"Req_Cookies,omitempty"`
RespStatus string `json:"Resp_Status"`
RespStatusCode string `json:"Resp_Status-Code"`
RespProto string `json:"Resp_Proto,omitempty"`
RespContentLength string `json:"Resp_Content-Length,omitempty"`
RespContentType string `json:"Resp_Content-Type,omitempty"`
RespTransferEncoding string `json:"Resp_Transfer-Encoding,omitempty"`
RespContentEncoding string `json:"Resp_Content-Encoding,omitempty"`
RespExpires string `json:"Resp_Expires,omitempty"`
RespCacheControl string `json:"Resp_Cache-Control,omitempty"`
RespVary string `json:"Resp_Vary,omitempty"`
RespSetCookie string `json:"Resp_Set-Cookie,omitempty"`
Rtt int64 `json:"RTT"`
Timestamp time.Time
}
3.3、Kafka 對接
除了輸出到 ES 以外,GoReplay 還支持輸出到 Kafka 以及從 Kafka 中讀取數據:
gor --input-raw :8080 --output-kafka-host '192.168.0.1:9092,192.168.0.2:9092' --output-kafka-topic 'kafka-log' gor --input-kafka-host '192.168.0.1:9092,192.168.0.2:9092' --input-kafka-topic 'kafka-log' --output-stdout