topfans/backend/scripts/loadgen/README.md
2026-06-16 23:00:07 +08:00

259 lines
9.3 KiB
Markdown

# 后端服务压测工具 (loadgen)
> 给阿里云单机 (4G/2C) TopFans 后端微服务用的压测 + 数据准备工具集。
> 凌晨 02:00-06:00 业务低峰执行,数据物理隔离 `star_id=999900`。
---
## 📚 文档地图
| 文档 | 用途 | 谁要看 |
|------|------|--------|
| **README.md** (本文) | 工具集概览 + 5 分钟入门 | 所有人 |
| [RUNBOOK.md](RUNBOOK.md) | 凌晨压测**一步一步**操作手册 | on-call 工程师 |
| [REPORT_GUIDE.md](REPORT_GUIDE.md) | 压测报告**怎么读** + 瓶颈定位 + 行动项模板 | 看报告的工程师 / TL |
| [seed/README.md](seed/README.md) | seed 工具细节 (数据准备) | 第一次跑压测的人 |
---
## 🧰 工具集概览
```
loadgen/
├── seed/ # 数据准备 CLI (生成 1000 个测试用户 + 资产 + JWT)
├── loadgen/ # 压测主程序 (7 个场景,6 维熔断,带 reporter)
├── monitor/ # 监控栈 (Prometheus + Grafana,可选)
├── recover/ # 紧急灭火 (一键停 + 数据库恢复)
├── scripts/ # 部署到 prod 的辅助脚本
└── reports/ # 跑测产出 (gitignore,scp 拉回本地)
```
### 核心 CLI: `bin/seed` + `bin/loadgen`
| 命令 | 作用 |
|------|------|
| `./bin/seed` | 灌测试数据 → `users.csv` + 数据库 |
| `./bin/seed --cleanup` | 清理测试数据 (保留 1000 用户) |
| `./bin/seed --cleanup --full` | 全部删掉 (账号本身) |
| `./bin/seed --reset-tokens` | 只重签 JWT (跨周压测用) |
| `./bin/loadgen --cmd=preflight` | 7 项开压前检查 |
| `./bin/loadgen --cmd=run --scenarios=S1` | 跑场景 |
| `./bin/loadgen --cmd=report` | 生成 markdown 报告 + PNG 图表 |
### 7 个场景
| ID | 场景 | 默认 RPS | 写/读 | 关键 API |
|----|------|---------|------|---------|
| S1 | Login | 15 | 写(轻) | `POST /api/v1/auth/login` |
| S2 | Read | 250 | 读 | `GET /api/v1/assets/{id}` |
| S3 | Like | 50 | 写(轻) | `POST/DELETE /api/v1/social/assets/{id}/like` |
| S4 | Mint | 1-5 | **写(重)** | `POST /api/v1/assets/mints/precreate` |
| S5 | Dashboard | — | 读聚合 | (dashboard 聚合) |
| S6 | Ranking | 300 | 读 | `GET /api/v1/rankings/hot` |
| S7 | Place | 1-5 | **写(重)** | (摆展事务) |
---
## ⭐ 推荐入口:prod_loadtest.sh 一键脚本
> **如果你的目标是打生产压测(无论 prod 还是本地 docker),优先用 [`scripts/prod_loadtest.sh`](scripts/prod_loadtest.sh)**。
> 子命令模式覆盖了全部流程,凭据从 `docker/.env.prod` 读,避免明文泄露。
```bash
cd /Users/liulujian/Documents/code/TopFansByGithub
# 一键:up → backup → seed → preflight → loadgen → report → cleanup-all → down
./backend/scripts/loadgen/scripts/prod_loadtest.sh pipeline \
--scenarios=S1,S2,S4 --stage=step --step-schedule='5,10,20' --duration=60s
```
完整子命令、状态查询、故障排查见 [scripts/README.md](scripts/README.md)。
---
## 🚀 5 分钟入门 (本地 docker)
```bash
# 1. 编译 (Linux prod 部署用,本地 darwin 直接 go build)
cd backend
make loadgen-build
# 2. 准备数据 (需要本地 docker postgres)
cd scripts/loadgen/seed
# 生成 bcrypt 哈希 (与 tokens.go 硬编码的 "Test@123" 匹配)
python3 -c "import bcrypt; print(bcrypt.hashpw(b'Test@123', bcrypt.gensalt(rounds=10)).decode())" \
> loadtest_bcrypt.txt
# 跑 seed (用本地 docker 的 env)
DB_PASSWORD=123456 \
JWT_SECRET=topfans-secret-key-local-dev-only \
/Users/liulujian/Documents/code/TopFansByGithub/backend/bin/seed \
--db-name=top-fans --db-host=localhost --db-port=15432 --db-user=postgres
# 3. 复制 users.csv 到 backend 目录
cp users.csv ../../../users.csv
# 4. 开压前检查
cd ../../../ # = backend
JWT_SECRET=topfans-secret-key-local-dev-only \
./bin/loadgen --cmd=preflight --target=http://localhost:8080
# 5. 烟雾测试 (30 秒,1 RPS)
JWT_SECRET=topfans-secret-key-local-dev-only \
./bin/loadgen --cmd=run --scenarios=S1 --stage=baseline --rps=1 --duration=30s \
--target=http://localhost:8080 --monitor=off
# 6. 生成报告
JWT_SECRET=topfans-secret-key-local-dev-only \
./bin/loadgen --cmd=report --input=./reports --output=./reports/final-report.md
open reports/final-report.md # macOS
```
---
## 🔨 编译
```bash
cd backend
make loadgen-build # 编译 seed + loadgen 到 bin/
make loadgen-test # 单元测试 (23 个)
make loadgen-vet # go vet
make loadgen-ci # vet + test + build (CI 单步)
```
手动编译 (Linux prod):
```bash
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/seed ./scripts/loadgen/seed/
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o bin/loadgen ./scripts/loadgen/loadgen/
```
---
## 🛡️ 安全设计
### 数据隔离
所有测试数据用 `star_id = 999900` 物理隔离,**不影响**真实业务 star_id (87, 88, 91, 93, 94, 95)。
### CLAUDE.md 序列重置
seed 工具末尾自动同步所有相关表的 PG 序列(避免后续 GORM 插入报 duplicate key)。
### 凌晨窗口
执行窗口:**02:00 - 06:00** 业务低峰。
紧急灭火: `recover/emergency-stop.sh` 一键停 + `restore-from-backup.sh` 5-8min 还原。
### 6 维红线熔断 (自动停)
| # | 红线 | 阈值 | 数据源 |
|---|------|------|--------|
| R1 | 客户端错误率 | > 5% 持续 30s | loadgen HDR |
| R2 | 客户端 P99 | > 3000ms 持续 30s | loadgen HDR |
| R3 | 5xx 比例 | > 10% 持续 10s | loadgen status |
| R4 | PG 连接数 | > 42 持续 30s | metrics-feed |
| R5 | 磁盘空闲 | < 5GB 持续 30s | metrics-feed |
| R6 | OOM 事件 | 瞬时触发 | metrics-feed |
---
## 📊 报告产出
跑完 + `--cmd=report` ,`reports/` 下:
```
reports/
├── S1.json # 原始数据 (含 stages)
├── S2.json
├── S4.json
├── baseline.csv # Excel 友好的汇总
├── s1.png # RPS / P99 / Error 曲线
├── s2.png
├── s4.png
└── final-report.md # ← 主要看这个
```
`final-report.md` 包含:
1. **总览表** (所有场景一行一个,7 )
2. **每个场景的 ⚠️ 拐点 RPS** (自动算:第一个 p99 >50% 的 stage)
3. **阶梯结果表** (每 stage 的 RPS / p50 / p95 / p99 / err / 5xx)
4. **PNG 曲线图** (RPS / P99 / Error 三条线)
详细读法见 [REPORT_GUIDE.md](REPORT_GUIDE.md)。
---
## 🧪 测试状态
```
seed: 5/5 PASS
loadgen/lib: 16/16 PASS
scenarios: 2/2 PASS
TOTAL: 23/23 PASS
```
---
## 📁 完整目录
```
backend/scripts/loadgen/
├── README.md # ← 你在这里
├── RUNBOOK.md # ← 凌晨压测操作手册
├── REPORT_GUIDE.md # ← 报告怎么读
├── seed/ # 数据准备工具
│ ├── main.go # CLI 入口
│ ├── stars.go users.go profiles.go assets.go
│ ├── slots_and_exhibits.go friendships.go
│ ├── tokens.go sequences.go cleanup.go
│ ├── seed_test.go # 单元测试
│ ├── loadtest_bcrypt.txt # Test@123 哈希 (与 tokens.go 匹配)
│ └── README.md
├── loadgen/ # 压测主程序
│ ├── main.go # CLI 入口
│ ├── preflight.go verify.go # 7 项开压前检查 + 压后验证
│ ├── lib/ # 核心库
│ │ ├── csv.go # users.csv 解析
│ │ ├── client.go # HTTP client
│ │ ├── hdr.go # 延迟直方图 + per-stage 计数
│ │ ├── log.go ramp.go # 日志 + 阶梯调度
│ │ ├── circuit.go # 6 维熔断
│ │ ├── ssh_metrics.go # prod server metrics 抓取
│ │ ├── config.go
│ │ └── *_test.go # 16 个测试
│ ├── scenarios/ # 7 个场景
│ │ ├── s1_login.go
│ │ ├── s2_read.go
│ │ ├── s3_like.go
│ │ ├── s4_mint.go # 支持多 stage
│ │ ├── s5_dashboard.go
│ │ ├── s6_ranking.go
│ │ ├── s7_place.go
│ │ ├── common.go # doRequest + DefaultBaseURL
│ │ ├── scenarios.go # 注册表
│ │ ├── helpers.go
│ │ └── scenarios_test.go
│ └── reporter/ # 报告生成
│ ├── json.go # RunReport + StageReport
│ ├── csv.go # baseline.csv
│ ├── plot.go # PNG 曲线 (gonum)
│ ├── markdown.go # final-report.md
│ └── knee.go # KneeRPS 自动算
├── monitor/ # 监控栈 (可选)
│ ├── sample.sh # 后台采样到 metrics-feed.jsonl
│ ├── docker-compose.monitor.yml
│ ├── prometheus.yml
│ └── grafana-dashboards/ # 4 个预置面板
├── recover/ # 紧急灭火
│ ├── emergency-stop.sh
│ └── restore-from-backup.sh
├── scripts/ # prod 辅助
│ ├── mint_reset.sh # S4 之间的 mint 数据清理
│ └── prod_seed.sh # 一键跑 seed (读 prod env)
└── reports/ # 跑测产出 (gitignore)
```
---
## 详细设计
- **设计文档**: `docs/superpowers/specs/2026-06-12-load-testing-design.md`
- **实施计划**: `docs/superpowers/plans/2026-06-12-load-testing.md`
- **seed 工具说明**: [seed/README.md](seed/README.md)