GoReplay v1.3.3 常用 CLI 命令与实战详解

六大实战走通 GoReplay 全链路:影子回放、文件录制回放与倍速、过滤改写、采样限流、多目标输出与中间件 hex 协议改写,全程实测回显并沉淀七条踩坑实录。

GoReplay流量回放压力测试2026/10/25

GoReplay v1.3.3 常用 CLI 命令与实战详解

目标:以实战为主线走通 GoReplay v1.3.3(命令 gor)的全部核心操作——生产流量影子回放、文件录制与回放(含倍速/循环)、过滤与改写、采样限流、多目标输出与中间件改写,每个实战都给出可直接复制的命令与实测回显,并在文末沉淀踩坑实录。参数含义与默认值见 《GoReplay v1.3.3 配置参数详解》;安装与 systemd 托管见 《GoReplay v1.3.3 安装指南》。

一、环境信息

  • 操作系统:Ubuntu 22.04.4 LTS(x86_64),两台:
    • 生产服务器:IP 10.89.56.2,示例业务应用监听 8000 端口,GoReplay 1.3.3 装于 /usr/local/bin/gor
    • 测试服务器:IP 10.89.56.3,主机名 test-server,同款业务应用监听 8000 端口
  • 示例业务应用:Python 3 内置 HTTP 服务 python3 -m http.server 8000(访问日志在 /var/log/app.log,用于核对回放到达)
  • 录制文件目录:/var/lib/gor/;中间件脚本:/opt/gor-mw.py
  • GoReplay 版本:1.3.3(程序运行时自报 Version:1.3.0,以包版本为准,原因见安装指南 FAQ Q3)
  • 实测日期:2026-10-25

二、前置条件

  1. 已按 《GoReplay v1.3.3 安装指南》 完成安装,root 身份可执行:
which gor && gor 2>&1 | head -2

实测回显:

/usr/local/bin/gor
2026/10/25 00:13:07 [PPID 2667 and PID 2673] Version:1.3.0
2026/10/25 00:13:07 Required at least 1 input and 1 output
  1. 两台服务器业务应用就位且互通:
curl -s -o /dev/null -w "local app:%{http_code}\n" http://127.0.0.1:8000/ && \
curl -s -o /dev/null -w "cross to target:%{http_code}\n" http://test-server:8000/

实测回显:

local app:200
cross to target:200
  1. 生产服务器 /etc/hosts 已配置 10.89.56.3 test-server(回放目标用主机名访问)

三、命令语法与启动形态

GoReplay 没有子命令,一切操作通过「输入插件 + 输出插件 + 修饰参数」组合表达,语法骨架:

gor [输入插件...] [输出插件...] [过滤/改写/限流参数...]

三种启动形态与适用场景:

形态命令样例适用
前台运行gor --input-raw :8000 --output-stdout调试观察,Ctrl-C 停止
nohup 后台nohup gor --input-raw :8000 --output-http http://test-server:8000 > /var/log/gor.log 2>&1 &临时录制/回放任务
systemd 常驻unit 文件 ExecStart=/usr/local/bin/gor --input-raw :8000 --output-...生产常驻捕获

本文各实战均先以前台/nohup 形态验证,systemd 托管(unit 模板、开机自启、journalctl 观察)已在 《GoReplay v1.3.3 安装指南》 第八章完整实测,此处不再重复。

四、实战一:生产流量影子回放

目标:生产服务器上启动捕获进程,把 8000 端口的实时流量原样复制到测试服务器,全程不影响生产请求。

第 1 步(生产服务器):启动捕获回放进程:

nohup gor --input-raw :8000 --output-http http://test-server:8000 > /var/log/gor.log 2>&1 &
sleep 2
tail -3 /var/log/gor.log

实测回显:

Interface: eth0 . BPF Filter: ((tcp dst port 8000) and (dst host 10.89.56.2))
Interface: lo . BPF Filter: ((tcp dst port 8000) and (dst host 127.0.0.1 or dst host ::1))
2026/10/25 00:17:15 [PPID 2893 and PID 2899] Version:1.3.0

第 2 步(生产服务器):产生两笔生产流量:

curl -s -o /dev/null -w "req1:%{http_code}\n" http://127.0.0.1:8000/index.html
curl -s -o /dev/null -w "req2:%{http_code}\n" http://127.0.0.1:8000/tmp/

实测回显:

req1:404
req2:200

第 3 步(测试服务器):核对回放请求到达:

tail -6 /var/log/app.log

实测回显:

127.0.0.1 - - [25/Oct/2026 00:17:08] "GET / HTTP/1.1" 200 -
10.89.56.2 - - [25/Oct/2026 00:17:08] "GET / HTTP/1.1" 200 -
10.89.56.2 - - [25/Oct/2026 00:17:17] "GET /tmp/ HTTP/1.1" 200 -
10.89.56.2 - - [25/Oct/2026 00:17:17] code 404, message File not found
10.89.56.2 - - [25/Oct/2026 00:17:17] "GET /index.html HTTP/1.1" 404 -

💡 解读:测试服务器日志中来源为 10.89.56.2 的记录即回放流量——GET /tmp/(200)与 GET /index.html(404)连响应语义都与生产一致(测试环境同样没有 index.html);Host 头默认被替换为回放目标(test-server:8000)。整个过程中生产请求正常返回(req1/req2 都拿到响应),验证了「影子」语义。收尾停进程:pkill -x gor。

五、实战二:文件录制与回放

目标:把生产流量录制成文件,再在测试环境按需回放(支持加速/循环),适用于生产与测试网络隔离的场景。

5.1 录制(生产服务器)

录制 GET 与 POST 两类请求(--input-raw-track-response 同时保留响应,便于后续比对):

mkdir -p /var/lib/gor
nohup gor --input-raw :8000 --output-file /var/lib/gor/requests.gor --input-raw-track-response \
  > /var/log/gor-rec.log 2>&1 &
sleep 2
curl -s -o /dev/null -w "rec1:%{http_code}\n" http://127.0.0.1:8000/tmp/
curl -s -o /dev/null -w "rec2:%{http_code}\n" -X POST -d "k=1" http://127.0.0.1:8000/upload
sleep 3 && pkill -x gor
ls -la /var/lib/gor/

实测回显:

rec1:200
rec2:501
ls: cannot access '/var/lib/gor/requests.gor': No such file or directory
-rw-r--r-- 1 root root 1346 Oct 25 00:21 /var/lib/gor/requests_0.gor

💡 解读:① 录制文件自动带序号后缀——指定的 requests.gor 落盘为 requests_0.gor(超过 --output-file-size-limit 32MB 后滚动 _1、_2);② rec2:501 是示例服务(SimpleHTTP)不支持 POST 的业务响应,不影响录制——POST 请求体照录。

5.2 认识录制格式(.gor 文件)

head -c 900 /var/lib/gor/requests_0.gor | cat -v

实测回显(节选,cat -v 展示控制字符):

1 9c6e1f407f000001c3ac7f8e 1792887718616870919 0
GET /tmp/ HTTP/1.1^M
Host: 127.0.0.1:8000^M
User-Agent: curl/7.81.0^M
Accept: */*^M
^M

M-pM-^_M-^PM-5M-pM-^_M-^YM-^HM-pM-^_M-^YM-^I
2 9c6e1f407f000001c3ac7f8e 1792887718617308021 30433
HTTP/1.0 200 OK^M
Server: SimpleHTTP/0.6 Python/3.10.12^M
...

💡 解读:每条记录 = 首行事件头(1=请求 / 2=响应 + 请求ID + 纳秒时间戳 + 耗时微秒)+ HTTP 原文 + 🐵🙈🙉 分隔行(cat -v 下显示为 M-pM-^_... 的 UTF-8 字节序列)。请求与响应共享同一请求 ID(如 9c6e1f407f000001c3ac7f8e)实现配对。注意:文件里存的是原文格式,而中间件收到的才是 hex 编码(见实战六,这是个容易混淆的坑)。

5.3 回放前预演(dry-run)

gor --input-file /var/lib/gor/requests_0.gor --input-file-dry-run --output-null

实测回显:

2026/10/25 00:22:21 [PPID 3035 and PID 3041] Version:1.3.0
[DEBUG][elapsed 386.799µs]: [INPUT-FILE] FileInput: end of file '/var/lib/gor/requests_0.gor'

Records found: 4
Malformed records: <nil>
Files processed: 1
Bytes processed: 1640
Max wait: 3.684744ms

💡 解读:Records found: 4 = 2 请求 + 2 响应(双录文件按记录数计);Max wait: 3.684744ms 是请求间最大间隔,即整个文件回放的理论时长下限;Malformed records: <nil> 表示无损坏记录。

5.4 按原始时间轴回放

gor --input-file /var/lib/gor/requests_0.gor --output-http http://test-server:8000 2>&1 | head -3

实测回显:

2026/10/25 00:26:47 [PPID 3146 and PID 3152] Version:1.3.0
[DEBUG][elapsed 4.921666ms]: [INPUT-FILE] FileInput: end of file '/var/lib/gor/requests_0.gor'

测试服务器核对(实测回显):

10.89.56.2 - - [25/Oct/2026 00:26:47] "GET /tmp/ HTTP/1.1" 200 -
10.89.56.2 - - [25/Oct/2026 00:26:47] code 501, message Unsupported method ('POST')
10.89.56.2 - - [25/Oct/2026 00:26:47] "POST /upload HTTP/1.1" 501 -

💡 解读:录制的 GET 与 POST 都按原始顺序回放到达(POST 501 为测试环境同样不支持 POST 的业务语义,请求体已完整送达)。注意:文件读完(出现 end of file 调试行)后 gor 进程不会自动退出——1.x 的行为是继续驻留等待,实操用 --exit-after 30s 控制运行窗口或 pkill -x gor 收尾,否则误以为回放卡住。

5.5 倍速回放(|200% 语法)

文件名后接 |N% 即可加速(200% = 2 倍速,时间轴压缩一半):

timeout 20 gor --input-file "/var/lib/gor/requests_0.gor|200%" \
  --output-http http://test-server:8000 --exit-after 15s 2>&1 | head -4

实测回显:

2026/10/25 00:28:00 [PPID 3176 and PID 3178] Version:1.3.0
2026/10/25 00:28:00 Running gor for a duration of 15s
[DEBUG][elapsed 2.924936ms]: [INPUT-FILE] FileInput: end of file '/var/lib/gor/requests_0.gor'

测试服务器核对(实测回显,两笔请求同一秒到达,间隔被压缩):

10.89.56.2 - - [25/Oct/2026 00:28:00] code 501, message Unsupported method ('POST')
10.89.56.2 - - [25/Oct/2026 00:28:00] "POST /upload HTTP/1.1" 501 -
10.89.56.2 - - [25/Oct/2026 00:28:00] "GET /tmp/ HTTP/1.1" 200 -

💡 解读:|200% 语法被接受且生效——本例原始录制时间轴仅数毫秒,加速效果有限;录制文件时间跨度越长(小时级流量),倍速压缩越明显(如 |1000% 把 1 小时压到 6 分钟)。--input-file-loop 可循环播放(批量压测时与倍速配合:--input-file "file.gor|500%" --input-file-loop)。

六、实战三:过滤与改写

目标:只回放感兴趣的流量(URL 白名单 + 方法白名单),并对回放流量做环境适配改写(注入标记头、重写路径)。

6.1 URL 白名单 + 头注入

只放行 /tmp/ 开头的请求,并给放行的请求注入标记头:

nohup gor --input-raw :8000 --output-stdout --http-allow-url "^/tmp/" \
  --http-set-header "X-Gor-Filter: url-whitelist" > /var/log/gor-filter.log 2>&1 &
sleep 2
curl -s -o /dev/null -w "req-tmp:%{http_code}\n" http://127.0.0.1:8000/tmp/
curl -s -o /dev/null -w "req-other:%{http_code}\n" http://127.0.0.1:8000/etc/
sleep 2
grep -E "^(GET|POST|PUT|DELETE|HEAD)" /var/log/gor-filter.log
grep -c "X-Gor-Filter" /var/log/gor-filter.log
pkill -x gor

实测回显:

req-tmp:200
req-other:200
GET /tmp/ HTTP/1.1
1

💡 解读:两笔生产请求都正常返回(过滤不影响生产本身),但 gor 输出侧只剩 GET /tmp/——/etc/ 被白名单丢弃;注入的 X-Gor-Filter 头出现 1 次。多个 --http-allow-url 叠加为 OR 关系;--http-disallow-url 语义相反(命中即丢)。

6.2 URL 正则重写(捕获组)

把 /v1/user/<id>/ping 重写为 /v2/user/<id>/ping:

nohup gor --input-raw :8000 --output-stdout \
  --http-rewrite-url "/v1/user/([^\/]+)/ping:/v2/user/$1/ping" > /var/log/gor-rw.log 2>&1 &
sleep 2
curl -s -o /dev/null -w "rw:%{http_code}\n" http://127.0.0.1:8000/v1/user/42/ping
sleep 2
grep -E "^(GET|Host)" /var/log/gor-rw.log
pkill -x gor

实测回显:

rw:404
GET /v2/user/42/ping HTTP/1.1
Host: 127.0.0.1:8000

💡 解读:生产请求 GET /v1/user/42/ping(404 为示例服务无此路由,不影响验证),gor 输出侧变为 GET /v2/user/42/ping——正则捕获组 $1 把 42 原样带入。改写族还有 --http-rewrite-header(头部正则重写)、--http-set-param(补查询参数)、--http-set-header(上一实战已演示注入)。

6.3 方法白名单

--http-allow-method 可叠加,只回放指定方法(帮助原文示例):

gor --input-raw :8000 --output-http http://test-server:8000 \
  --http-allow-method GET --http-allow-method OPTIONS

⚠️ 注意:--http-allow-url 的正则匹配的是「完整 URL(含域名)」(帮助原文:"Filter get matched against full url with domain")。本机回环下 ^/tmp/ 可命中,但若正则涉及域名部分(如 ^www\.),务必按 gor 眼中的完整 URL 书写——排查技巧:先去掉过滤用 --output-stdout 看原始请求行再定正则。

七、实战四:采样限流(会话级一致性)

目标:录制 10 个不同会话(X-Session-Id 头)的请求,分别以 50%、20% 档回放,观察 --http-header-limiter 的哈希采样行为。

第 1 步:录制 10 个会话的请求:

rm -f /var/lib/gor/limiter_0.gor
nohup gor --input-raw :8000 --output-file /var/lib/gor/limiter.gor > /var/log/gor-lm.log 2>&1 &
sleep 2
for i in 1 2 3 4 5 6 7 8 9 10; do
  curl -s -o /dev/null -H "X-Session-Id: sess-$i" http://127.0.0.1:8000/tmp/?s=$i
done
sleep 3 && pkill -x gor
grep -c "GET /tmp/" /var/lib/gor/limiter_0.gor

实测回显:

10

第 2 步:50% 档采样回放(输出到 stdout 统计放行数):

timeout 15 gor --input-file /var/lib/gor/limiter_0.gor \
  --http-header-limiter "X-Session-Id:50%" --output-stdout --exit-after 5s 2>&1 | grep -c "GET /tmp/"

实测回显:

2

第 3 步:20% 档:

timeout 15 gor --input-file /var/lib/gor/limiter_0.gor \
  --http-header-limiter "X-Session-Id:20%" --output-stdout --exit-after 5s 2>&1 | grep -c "GET /tmp/"

实测回显:

1

💡 解读:50% 档放行 2/10、20% 档放行 1/10,明显低于标称比例——--http-header-limiter 用 FNV-1A 哈希 + 阈值判定(fnv32a(会话值) 落入阈值区间才放行),标称百分比是长期大样本的期望值,10 个会话的小样本完全取决于具体值的哈希落点,出现大幅偏离是正常现象。其核心价值在一致性:同一 X-Session-Id 的所有请求永远同放行/同丢弃(多次运行结果可复现),保证回放会话完整。--http-param-limiter(按 GET 参数哈希)行为同理。需要精确控量时配合 --output-http-workers 限并发(1.3.3 无全局 RPS 限速参数,见配置参数详解 FAQ Q5)。

八、实战五:多目标输出

目标:一次捕获/回放同时投递到多个目标(如一个测试环境 + 一个终端观察),验证输出插件的复制语义。

timeout 15 gor --input-file /var/lib/gor/requests_0.gor \
  --output-stdout --output-http http://test-server:8000 --exit-after 5s 2>&1 | grep -cE "^GET /tmp/|^POST /upload"

实测回显:

2

💡 解读:stdout 侧看到全部 2 条请求,同时它们也被回放到 test-server(GoReplay 默认每个输出收到全量流量,即复制语义)。--split-output true 可切换为多输出间均分流量——但注意帮助文本的默认值标注与语义描述易混淆(详见配置参数详解 5.9),生产多输出场景建议显式传参明确行为,避免流量分配预期偏差。

九、实战六:中间件改写(含踩坑实录)

目标:用 Python 脚本作为中间件,把回放流量的 /tmp/ 路径改写为 /etc/ 并注入自定义头,演示 GoReplay 最灵活的扩展机制。

9.1 第一次尝试(失败,协议误判)

按「中间件收到原始 payload 文本」的认知写脚本——逐行读、遇空行输出缓冲:

cat > /opt/gor-mw.py <<'EOF'
#!/usr/bin/env python3
import sys
buf = []
for line in sys.stdin:
    if line.startswith("GET /tmp/"):
        line = line.replace("GET /tmp/", "GET /etc/", 1)
    elif line.startswith("Host:"):
        buf.append(line); buf.append("X-Gor-MW: path-rewritten\n"); continue
    buf.append(line)
    if line.strip() == "":
        sys.stdout.write("".join(buf)); sys.stdout.flush(); buf = []
EOF
chmod +x /opt/gor-mw.py
timeout 20 gor --input-file /var/lib/gor/limiter_0.gor --middleware "/opt/gor-mw.py" \
  --output-stdout --exit-after 5s 2>&1 | head -6

实测回显(无任何请求输出,gor 空转到超时):

2026/10/25 00:30:00 [PPID 3351 and PID 3353] Version:1.3.0
2026/10/25 00:30:00 Running gor for a duration of 5s
[DEBUG][elapsed 42.207977ms]: [INPUT-FILE] FileInput: end of file '/var/lib/gor/limiter_0.gor'

2026/10/25 00:30:05 gor run timeout 5s

排障过程(实录,排障方法论):

  1. 对照实验定位环节:换 /bin/cat 做中间件,stdout 能看到完整请求(见配置参数详解 5.8 实测)——排除「gor 侧没喂数据」与「输出插件故障」,问题锁定在中间件脚本与协议。
  2. 脚本单测:手工 printf 一条标准消息喂脚本,改写与注入逻辑全部正确——排除脚本逻辑错误。
  3. 输入侧打日志:在脚本里把收到的每一行写入 /tmp/mw.log,发现收到的不是 HTTP 原文,而是 hex 编码的行(节选,解码后即 1 a258... 0\nGET /tmp/?s=1 HTTP/1.1\r\nHost: ...):
3120613235383166343037663030303030313061663930626138203137393238383831313836343131343536343420300a474554202f746d702f3f733d3120485454502f312e310d0a486f73743a203132372e302e302e313a383030300d0a557365722d4167656e743a206375726c2f372e38312e300d0a4163636570743a202a2f2a0d0a582d53657373696f6e2d49643a20736573732d310d0a0d0a

💡 结论(协议真相):GoReplay 1.3.3 的 middleware 协议是「每行一条 hex 编码的消息」——gor 把每条 payload hex 编码后按行喂给中间件 stdin,中间件输出的每一行也会被 gor 做 hex 解码还原。第一次尝试的脚本从未 flush(hex 行不满足空行判断),10 条消息全部滞留缓冲区后随进程退出丢失。另外注意:.gor 录制文件里是原文格式,middleware 管线里才是 hex 行格式,两者不要混淆。

9.2 修正后实战(成功)

按 hex 行协议重写 /opt/gor-mw.py:

cat > /opt/gor-mw.py <<'EOF'
#!/usr/bin/env python3
"""GoReplay 中间件:hex 行协议,路径改写 /tmp/ -> /etc/ 并注入 X-Gor-MW 头"""
import sys

for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    # 1. hex 解码还原 payload 原文
    raw = bytes.fromhex(line).decode("utf-8", "replace")
    # 2. 路径改写
    raw = raw.replace("GET /tmp/", "GET /etc/", 1)
    # 3. 头注入(插到 Host 行前)
    raw = raw.replace("Host: ", "X-Gor-MW: path-rewritten\r\nHost: ", 1)
    # 4. hex 编码写回(每行一条消息)
    sys.stdout.write(raw.encode("utf-8").hex() + "\n")
    sys.stdout.flush()
EOF
timeout 20 gor --input-file /var/lib/gor/limiter_0.gor --middleware "/opt/gor-mw.py" \
  --output-stdout --exit-after 5s 2>&1 | grep -E "^(GET|X-Gor-MW)" | head -6

实测回显:

GET /etc/?s=1 HTTP/1.1
X-Gor-MW: path-rewritten
GET /etc/?s=2 HTTP/1.1
X-Gor-MW: path-rewritten
GET /etc/?s=3 HTTP/1.1
X-Gor-MW: path-rewritten

💡 解读:全部请求被成功改写——GET /tmp/?s=N 变为 GET /etc/?s=N,X-Gor-MW: path-rewritten 注入到 Host 头之前。把 --output-stdout 换成 --output-http http://test-server:8000 即为「经中间件改写后回放」的生产形态;中间件里还可以按业务逻辑丢弃请求(对某行不输出即可)或复制多份路由到不同后端。

十、生产运维技巧

  1. 常驻托管:生产捕获用 systemd unit(模板与 journalctl 观察实测见安装指南 8.2),参数变更走 daemon-reload + restart。
  2. 先 stdout 后回放:新规则(过滤/改写/中间件)上线前,先用 --output-stdout 在终端确认 gor 眼中的请求形态,再切 --output-http,避免把错误流量打进测试环境。
  3. 回放效果核验:以测试环境业务日志(或网关访问日志)核对回放请求到达情况,gor 自身的统计输出(--output-http-stats)在短窗口下不可靠(实测见配置参数详解 FAQ Q3)。
  4. 录制窗口控制:临时录制任务加 --exit-after 1h 之类的时长上限,配合 --output-file 时间模板(prod-%Y-%m-%d.gor)按天落盘,防止磁盘被撑满。
  5. 保护生产:捕获侧(--input-raw)资源开销极小(实测常驻内存 10MB 量级),但回放侧 --output-http-workers 要按测试环境容量设定,避免影子流量反噬测试环境。

十一、踩坑实录汇总

  1. gor --version 不存在:报 flag provided but not defined: -version;版本看无参数运行的首行日志或帮助尾部 Current Version。且 1.3.3 包的程序自报 1.3.0(上游版本常量未更新),以包管理器记录为准。
  2. 下载 URL 版本号无 v 前缀:release 标题显示 v1.3.3 但 tag 是 1.3.3,用 v1.3.3 拼下载地址得到 404 Not Found(实测 http:404 size:9)。
  3. 文件回放读完不退出:--input-file 打出 end of file 后 gor 进程继续驻留,务必用 --exit-after 或 pkill -x gor 收尾,否则会误判回放卡死。
  4. 中间件是 hex 行协议:middleware 收发都是「每行一条 hex 编码消息」,不是原始文本(9.1 排障实录);而 .gor 录制文件内是原文格式,两处格式不同。
  5. 过滤正则匹配完整 URL:--http-allow-url 对「含域名的完整 URL」匹配,跨环境复用正则时注意 Host 部分差异;先 --output-stdout 观察 gor 眼中的原始请求行再写正则。
  6. 采样限流小样本偏离标称:--http-header-limiter 的百分比是哈希长期期望,10 个会话实测 50% 档只放行 2 个(7.7 实测),小流量环境不要按百分比精确预期。
  7. 录制文件名带序号后缀:指定 requests.gor 实际落盘 requests_0.gor,脚本引用录制文件时按实际文件名(或 ls 确认)传参。

十二、参考链接