10 KiB
10 KiB
RUNBOOK — 凌晨压测执行手册
目标读者:负责 prod 凌晨压测的 on-call 工程师 执行窗口:02:00 - 06:00 (业务低峰) 预计总耗时:1.5 - 4 小时 (按场景数) 风险等级:🟡 中 (会写 23k+ 测试数据,但物理隔离 star_id=999900)
0. 前置检查 (T-1 天)
0.1 确认 prod 状态
# SSH 到 prod
ssh root@101.132.250.62
# 确认 prod 网关正常
curl -sS http://localhost:8080/health
# 期望: {"service":"top-fans-gateway","status":"ok"}
# 确认磁盘空间 > 10GB (R5 红线需要)
df -h /opt
# 期望: Avail > 10G
0.2 确认阿里云快照 < 24h
- 登录 ECS 控制台 → 实例 → 磁盘与镜像 → 快照
- 必须有 < 24h 的快照,否则不要开压
- 没有的话先手动触发:实例 → 更多 → 磁盘和镜像 → 创建快照
0.3 备份数据库
ssh root@101.132.250.62
mkdir -p /opt/topfans/backups
pg_dump -h localhost -U postgres topfans > /opt/topfans/backups/pre-loadtest-$(date +%Y%m%d-%H%M).sql
ls -lh /opt/topfans/backups/pre-loadtest-*.sql
# 期望: 文件 > 50MB
1. 上传/确认工具 (T-30min)
1.1 确认工具已上传到 prod
ssh root@101.132.250.62
ls -la /opt/topfans/loadtest/
# 必须看到:
# seed (二进制)
# loadgen (二进制)
# loadtest_bcrypt.txt
# scripts/prod_seed.sh
# README.md
# reports/ (空目录)
如果文件缺失,本地重新上传:
# 本地 (从 backend 目录)
cd /Users/liulujian/Documents/code/TopFansByGithub/backend
# 重新编译
make loadgen-build
# 上传
scp bin/seed bin/loadgen root@101.132.250.62:/opt/topfans/loadtest/
scp scripts/loadgen/seed/loadtest_bcrypt.txt root@101.132.250.62:/opt/topfans/loadtest/
scp scripts/loadgen/scripts/prod_seed.sh root@101.132.250.62:/opt/topfans/loadtest/scripts/
ssh root@101.132.250.62 "chmod +x /opt/topfans/loadtest/{seed,loadgen} /opt/topfans/loadtest/scripts/prod_seed.sh"
1.2 重新生成 bcrypt 哈希 (如果你改了密码策略)
# 本地
cd backend/scripts/loadgen/seed
# 生成与 tokens.go 硬编码密码 (默认 "Test@123") 匹配的哈希
python3 -c "import bcrypt; print(bcrypt.hashpw(b'Test@123', bcrypt.gensalt(rounds=10)).decode())" \
> loadtest_bcrypt.txt
# 上传覆盖
scp loadtest_bcrypt.txt root@101.132.250.62:/opt/topfans/loadtest/
2. 数据准备 (T0 = 02:00)
2.1 SSH 到 prod
ssh root@101.132.250.62
2.2 一键跑 seed (生产数据灌入)
cd /opt/topfans/loadtest
bash scripts/prod_seed.sh
这一步骤会做什么:
- 读
/opt/topfans/docker/.env.prod拿 DB_PASSWORD + JWT_SECRET - 插入 star_id=999900 测试明星 (1 行)
- 插入 1000 个测试用户 (mobile 19900000001 - 19900001000)
- 插入 1000 个 fan_profile + crystal
- 插入 5000 个 assets
- 插入 3000 个 booth_slots + 2000 个 exhibitions
- 插入 10000 个 friendships
- 重置所有相关表的 PG 序列 (CLAUDE.md 规范,避免后续 GORM 插入报 duplicate key)
- 签 1000 个 JWT,写到
users.csv
预计耗时:30 - 60 秒
预期输出:
✓ stars seeded
✓ 1000 users seeded
✓ 1000 fan_profiles + crystal seeded
✓ 5000 assets seeded
✓ 3000 booth_slots + 2000 exhibitions seeded
✓ 10000 friendships seeded
✓ sequences reset
✅ users.csv written: 1000 rows
✅ seed + tokens completed
3. 开压前 7 项检查 (T0+1min)
cd /opt/topfans/loadtest
./loadgen --cmd=preflight --target=http://localhost:8080
预期全部 PASS:
✓ ① Gateway /health HTTP 200
✓ ② SSH to prod (省略,如不需要 server metrics)
✓ ③ pg_dump backup > 50MB (你的备份)
✓ ④ 阿里云快照 < 24h (人工确认)
✓ ⑤ prod 磁盘空闲 > 10GB free > 10G
✓ ⑥ users.csv 1000 rows rows=1000
✓ ⑦ JWT_SECRET set set
ALL CHECKS PASSED — 可以开压
如果有 FAIL:见 "附录 A: 故障排查"
4. 烟雾测试 (T0+2min) — 强烈推荐
这一步只花 30 秒,但能提前发现 90% 的集成问题,省后面 1 小时排错
cd /opt/topfans/loadtest
JWT_SECRET=$(grep '^JWT_SECRET=' /opt/topfans/docker/.env.prod | cut -d= -f2) \
./loadgen --cmd=run --scenarios=S1 --stage=baseline --rps=1 --duration=30s \
--target=http://localhost:8080 --monitor=off 2>&1 | tee reports/smoke-s1.log
预期:
📊 S1: total=30 err=0 5xx=0 p99=200ms stages=1
✅ loadgen done. total=30 err=0 fiveXX=0
判定:
- ✅ total=30, err=0 → 进入正式压测
- ❌ total < 30 → 跑挂了,查
reports/smoke-s1.log - ❌ err > 0 → auth/JWT 问题,检查
users.csv和 JWT_SECRET
5. 正式压测 (T0+3min)
5.1 选择策略
Plan B 推荐 (S1 + S2 + S4,~1.5 小时):
cd /opt/topfans/loadtest
export JWT_SECRET=$(grep '^JWT_SECRET=' /opt/topfans/docker/.env.prod | cut -d= -f2)
export PROD_SSH=root@101.132.250.62
# === 场景 1: Login (02:05-02:30, 25min) ===
./loadgen --cmd=run --scenarios=S1 \
--stage=step --step-schedule='2,5,10,15,20' \
--duration=5m --target=http://localhost:8080 \
--monitor=full --prod-ssh=$PROD_SSH \
--inter-scenario-pause=0s 2>&1 | tee reports/s1.log
# 预期: 5 个 stage,每 stage 5min,p99 应随 RPS 阶梯上升
# === 场景 2: Read (02:35-03:00, 25min) ===
./loadgen --cmd=run --scenarios=S2 \
--stage=step --step-schedule='10,30,60,100,150' \
--duration=5m --target=http://localhost:8080 \
--monitor=full --prod-ssh=$PROD_SSH \
--inter-scenario-pause=0s 2>&1 | tee reports/s2.log
# === 场景 4: Mint (03:05-03:30, 25min, 写重,保守) ===
./loadgen --cmd=run --scenarios=S4 \
--stage=step --step-schedule='1,2,3,5' \
--duration=5m --target=http://localhost:8080 \
--monitor=full --prod-ssh=$PROD_SSH \
--inter-scenario-pause=0s 2>&1 | tee reports/s4.log
Plan A 全量 (S1-S7,~3.5 小时):
# S1-S7 全部跑,S4/S7 写重场景保守
SCENARIOS="S1,S2,S3,S4,S5,S6,S7"
SCHEDULES_BY_SCENARIO='{"S1":"2,5,10,15,20","S2":"10,30,60,100,150","S3":"5,15,30,50","S4":"1,2,3,5","S5":"5,10,20,40","S6":"20,50,100,150","S7":"1,2,3,5"}'
# (目前 loadgen 一次只支持一个 schedule,需要跑 7 次)
5.2 每个场景跑完后做什么
- 检查
reports/{scenario}.log末尾的📊行 - 记录 total / err / 5xx / p99 / stages
- 如果
🚨 circuit breaker tripped触发,立即停,见附录 B
6. 生成报告 (T+1min)
cd /opt/topfans/loadtest
./loadgen --cmd=report --input=./reports --output=./reports/final-report.md
产出:
reports/
├── S1.json
├── S2.json
├── S4.json
├── baseline.csv # Excel 可直接打开
├── s1.png # RPS/P99/Error 曲线图
├── s2.png
├── s4.png
└── final-report.md # 人看的报告
7. 收尾 (T+2min)
7.1 拉报告到本地
# 本地
mkdir -p ~/Desktop/loadtest-report-$(date +%Y%m%d)
scp -r root@101.132.250.62:/opt/topfans/loadtest/reports/* ~/Desktop/loadtest-report-$(date +%Y%m%d)/
7.2 决定是否清理测试数据
| 情况 | 动作 |
|---|---|
| 数据分析完,后续不需要 | ./seed --cleanup --full |
| 数据还要保留做下一轮 | ./seed --cleanup (保留 1000 用户,清理关联数据) |
| 只是 JWT 过期 | ./seed --reset-tokens --jwt-secret=$JWT_SECRET |
| 生产事故 | ./seed --cleanup --full + 立即回滚,见附录 C |
7.3 (可选) 关闭监控后台采样
# 如果你启动了 monitor/sample.sh,杀掉
ssh root@101.132.250.62 "pkill -f 'monitor/sample.sh'"
8. 报告分析 (T+30min,白天)
见 REPORT_GUIDE.md — 教你怎么读 final-report.md,定位瓶颈,写行动项。
附录 A: 故障排查
A.1 preflight FAIL: users.csv 不存在
原因: 上次 seed 没跑成功
修复: cd /opt/topfans/loadtest && bash scripts/prod_seed.sh
A.2 preflight FAIL: 阿里云快照 < 24h
原因: 没备份 修复: 在 ECS 控制台手动建快照,等就绪后重跑 preflight
A.3 烟雾测试 FAIL: 大量 4xx
原因: JWT_SECRET 不匹配 / users.csv 过期 修复:
# 1. 确认 JWT_SECRET
grep '^JWT_SECRET=' /opt/topfans/docker/.env.prod
# 2. 重签 token (数据保留)
./seed --reset-tokens --jwt-secret=$JWT_SECRET
# 3. 重跑
./loadgen --cmd=run --scenarios=S1 --stage=baseline --rps=1 --duration=30s \
--target=http://localhost:8080 --monitor=off
A.4 烟雾测试 FAIL: 大量 5xx
原因: 网关/服务挂了
修复: 先看 docker ps 确认服务在,curl /health 确认网关活
附录 B: Circuit Breaker 触发 (🚨)
如果出现 🚨 circuit breaker tripped!,立即:
- Ctrl+C 停止当前 loadgen (会 graceful shutdown,等待当前请求完成)
- 立即判断:
- 5xx > 10% 持续 10s → 服务有问题,见附录 C
- 仅客户端错率高 → 测试问题,可能是 step 跳太猛
- 降低 RPS 重跑 或 改天再试
附录 C: 紧急灭火 (production 被打挂了)
判定: 服务真实报错(不是测试客户端问题),prod 用户受影响。
立即执行 (按顺序,每步 30s 内):
ssh root@101.132.250.62
# 1. 停 loadgen + 监控
pkill -f 'bin/loadgen'
pkill -f 'monitor/sample.sh'
# 2. 清测试数据 (1 秒)
cd /opt/topfans/loadtest
./seed --cleanup --full
# 3. 重启服务 (让 prod 回到 baseline)
cd /opt/topfans/docker
docker-compose -f docker-compose.prod.yml --profile prod restart
# 4. (最严重情况) 从备份还原
bash /opt/topfans/loadtest/recover/restore-from-backup.sh
# 输入 backup 文件路径,预计 5-8 分钟
事后:
- 写事故复盘
- 修压测发现的 bug
- 调整 step schedule (下一次更保守)
附录 D: 常用 cheat sheet
# 查看 loadtest 进程
ssh root@101.132.250.62 "ps aux | grep -E '(loadgen|sample)' | grep -v grep"
# 看实时日志
ssh root@101.132.250.62 "tail -f /opt/topfans/loadtest/reports/*.log"
# 看 metrics feed
ssh root@101.132.250.62 "tail -f /opt/topfans/loadtest/metrics-feed.jsonl"
# 测一下网关还活着
ssh root@101.132.250.62 "curl -sS http://localhost:8080/health"