测试 HTTP 服务,为了覆盖更多的场景,可以考虑录制线上流量,在测试环境进行重放。之前用 tcpcopy 比较多,最近遇到一些需求,需要在 HTTP 层做一些过滤,例如只录制指定 URL 的请求。
经过调研,发现 goreplay,其前称是 gor,很适合这个场景,有以下优点。
免 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 文件。
goreplay 是用 golang 编写的,抓包的时候调用 gopacket,后者通过 cgo 来调用 libpcap。从编译开始,从源码层面学习一下其实现。
TL;DR
由于 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:
2021/09/23 08:18:22 Required at least 1 input and 1 output
不依赖任何库。
[root@localhost goreplay]$ ldd goreplay
not a dynamic executable
一个合法的可执行文件。
[root@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
如果遇到以下编译错误。
[root@localhost goreplay]$ go build -ldflags '-extldflags "-static"'# github.com/buger/goreplay
/e/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
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() 方法。
抓包是核心功能,也算是一种 input 的类型。不过 goreplay 的实现中,实现上和 HTTP 协议绑定的很死。我参考 goreplay 的代码,实现了 goreplay-udp,用法上和 goreplay 保持一致