GoReplay v1.3.3 常用 CLI 命令与实战详解
六大实战走通 GoReplay 全链路:影子回放、文件录制回放与倍速、过滤改写、采样限流、多目标输出与中间件 hex 协议改写,全程实测回显并沉淀七条踩坑实录。
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端口
- 生产服务器:IP
- 示例业务应用: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
二、前置条件
- 已按 《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
- 两台服务器业务应用就位且互通:
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
- 生产服务器
/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-limit32MB 后滚动_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
排障过程(实录,排障方法论):
- 对照实验定位环节:换
/bin/cat做中间件,stdout 能看到完整请求(见配置参数详解 5.8 实测)——排除「gor 侧没喂数据」与「输出插件故障」,问题锁定在中间件脚本与协议。 - 脚本单测:手工
printf一条标准消息喂脚本,改写与注入逻辑全部正确——排除脚本逻辑错误。 - 输入侧打日志:在脚本里把收到的每一行写入
/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即为「经中间件改写后回放」的生产形态;中间件里还可以按业务逻辑丢弃请求(对某行不输出即可)或复制多份路由到不同后端。
十、生产运维技巧
- 常驻托管:生产捕获用 systemd unit(模板与 journalctl 观察实测见安装指南 8.2),参数变更走
daemon-reload + restart。 - 先 stdout 后回放:新规则(过滤/改写/中间件)上线前,先用
--output-stdout在终端确认 gor 眼中的请求形态,再切--output-http,避免把错误流量打进测试环境。 - 回放效果核验:以测试环境业务日志(或网关访问日志)核对回放请求到达情况,gor 自身的统计输出(
--output-http-stats)在短窗口下不可靠(实测见配置参数详解 FAQ Q3)。 - 录制窗口控制:临时录制任务加
--exit-after 1h之类的时长上限,配合--output-file时间模板(prod-%Y-%m-%d.gor)按天落盘,防止磁盘被撑满。 - 保护生产:捕获侧(
--input-raw)资源开销极小(实测常驻内存 10MB 量级),但回放侧--output-http-workers要按测试环境容量设定,避免影子流量反噬测试环境。
十一、踩坑实录汇总
gor --version不存在:报flag provided but not defined: -version;版本看无参数运行的首行日志或帮助尾部Current Version。且 1.3.3 包的程序自报1.3.0(上游版本常量未更新),以包管理器记录为准。- 下载 URL 版本号无
v前缀:release 标题显示v1.3.3但 tag 是1.3.3,用v1.3.3拼下载地址得到404 Not Found(实测http:404 size:9)。 - 文件回放读完不退出:
--input-file打出end of file后 gor 进程继续驻留,务必用--exit-after或pkill -x gor收尾,否则会误判回放卡死。 - 中间件是 hex 行协议:middleware 收发都是「每行一条 hex 编码消息」,不是原始文本(9.1 排障实录);而
.gor录制文件内是原文格式,两处格式不同。 - 过滤正则匹配完整 URL:
--http-allow-url对「含域名的完整 URL」匹配,跨环境复用正则时注意 Host 部分差异;先--output-stdout观察 gor 眼中的原始请求行再写正则。 - 采样限流小样本偏离标称:
--http-header-limiter的百分比是哈希长期期望,10 个会话实测 50% 档只放行 2 个(7.7 实测),小流量环境不要按百分比精确预期。 - 录制文件名带序号后缀:指定
requests.gor实际落盘requests_0.gor,脚本引用录制文件时按实际文件名(或ls确认)传参。
十二、参考链接
- 官方 Wiki Getting Started:https://github.com/buger/goreplay/wiki/Getting-Started
- 官方 Wiki( Filtering / Replay / Middleware 章节):https://github.com/buger/goreplay/wiki
- 官方 Releases(版本与变更记录):https://github.com/buger/goreplay/releases
- 本专栏系列:《GoReplay v1.3.3 介绍与选型指南》(定位、架构与同类对比)、《GoReplay v1.3.3 安装指南》(三方案安装与 systemd 托管实测)、《GoReplay v1.3.3 配置参数详解》(全量参数与默认值实测)