242 lines
8.6 KiB
Markdown
242 lines
8.6 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 | **写(重)** | (摆展事务) |
|
|
|
|
---
|
|
|
|
## 🚀 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)
|