feat:修改压缩工具的配置
This commit is contained in:
parent
c6ce7d2d25
commit
8ccb4e2565
@ -1,7 +1,7 @@
|
||||
# TopFans Backend Makefile
|
||||
# 用于简化开发流程
|
||||
|
||||
.PHONY: help install-swagger gen-swagger update-swagger start-swagger start-all stop-all clean build run all loadgen-build loadgen-test loadgen-vet loadgen-ci loadgen-seed-local loadgen-seed-prod-tunnel loadgen-cleanup-local loadgen-cleanup-prod-tunnel loadgen-cleanup-prod-tunnel-full loadgen-report
|
||||
.PHONY: help install-swagger gen-swagger update-swagger start-swagger start-all stop-all clean build run all loadgen-build loadgen-test loadgen-vet loadgen-ci loadgen-seed-local loadgen-seed-prod-tunnel loadgen-cleanup-local loadgen-cleanup-local-full loadgen-cleanup-prod-tunnel loadgen-cleanup-prod-tunnel-full loadgen-report
|
||||
|
||||
# 默认目标
|
||||
help:
|
||||
@ -32,6 +32,7 @@ help:
|
||||
@echo " make loadgen-seed-local - seed 写入本地 docker dev (top-fans:15432)"
|
||||
@echo " make loadgen-seed-prod-tunnel - seed 通过 SSH 端口转发写生产 (127.0.0.1:25432)"
|
||||
@echo " make loadgen-cleanup-local - 清理本地 docker 压测数据 (保留账号)"
|
||||
@echo " make loadgen-cleanup-local-full - 全清本地 docker 压测数据 (含 users + stars)"
|
||||
@echo " make loadgen-cleanup-prod-tunnel - 清理生产 docker 压测数据 (保留账号,走 SSH 转发)"
|
||||
@echo " make loadgen-cleanup-prod-tunnel-full - 全清生产压测数据 (含 users + stars,适合彻底收尾)"
|
||||
@echo ""
|
||||
@ -58,6 +59,7 @@ help:
|
||||
@echo " make loadgen-seed-local - seed 写入本地 docker dev (top-fans:15432)"
|
||||
@echo " make loadgen-seed-prod-tunnel - seed 通过 SSH 端口转发写生产 (127.0.0.1:25432)"
|
||||
@echo " make loadgen-cleanup-local - 清理本地 docker 压测数据 (保留账号)"
|
||||
@echo " make loadgen-cleanup-local-full - 全清本地 docker 压测数据 (含 users + stars)"
|
||||
@echo " make loadgen-cleanup-prod-tunnel - 清理生产 docker 压测数据 (保留账号,走 SSH 转发)"
|
||||
@echo " make loadgen-cleanup-prod-tunnel-full - 全清生产压测数据 (含 users + stars,适合彻底收尾)"
|
||||
@echo ""
|
||||
@ -162,11 +164,18 @@ loadgen-seed-local: loadgen-build
|
||||
--db-host=localhost --db-port=15432 --db-name=top-fans --db-user=postgres
|
||||
|
||||
loadgen-cleanup-local: loadgen-build
|
||||
@echo ">>> 清理本地 docker 压测数据 (top-fans:15432)"
|
||||
@echo ">>> 清理本地 docker 压测数据 (top-fans:15432,保留账号)"
|
||||
@DB_PASSWORD=$$(grep '^DB_PASSWORD=' ../docker/.env.local | cut -d= -f2) \
|
||||
./bin/seed --cleanup \
|
||||
--db-host=localhost --db-port=15432 --db-name=top-fans --db-user=postgres
|
||||
|
||||
# 全清版本:含 users + stars。跟 loadgen-cleanup-prod-tunnel-full 对称。
|
||||
loadgen-cleanup-local-full: loadgen-build
|
||||
@echo ">>> ⚠️ 全清本地 docker 压测数据 (top-fans:15432,含 users + stars)"
|
||||
@DB_PASSWORD=$$(grep '^DB_PASSWORD=' ../docker/.env.local | cut -d= -f2) \
|
||||
./bin/seed --cleanup --full \
|
||||
--db-host=localhost --db-port=15432 --db-name=top-fans --db-user=postgres
|
||||
|
||||
# --- 本地连生产 (ssh -L 25432 → 生产 docker 5432) ---
|
||||
# 调用前请确保已建立转发: ssh -L 25432:127.0.0.1:5432 -N -f root@101.132.250.62
|
||||
loadgen-seed-prod-tunnel: loadgen-build
|
||||
|
||||
@ -54,6 +54,23 @@ loadgen/
|
||||
|
||||
---
|
||||
|
||||
## ⭐ 推荐入口: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
|
||||
|
||||
@ -86,13 +86,29 @@ scp loadtest_bcrypt.txt root@101.132.250.62:/opt/topfans/loadtest/
|
||||
|
||||
## 2. 数据准备 (T0 = 02:00)
|
||||
|
||||
### 2.1 SSH 到 prod
|
||||
### 2.0 ⭐ 推荐:本地一键压测 (无需 SSH 到生产机)
|
||||
|
||||
> **新方式**:`scripts/prod_loadtest.sh` 一站式脚本,在本地跑,自动建 SSH 隧道/读凭据/调 seed+loadgen+cleanup。
|
||||
> 比"SSH 进去跑 prod_seed.sh"更安全、更可复现、更适合 CI。
|
||||
|
||||
```bash
|
||||
ssh root@101.132.250.62
|
||||
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
|
||||
```
|
||||
|
||||
### 2.2 一键跑 seed (生产数据灌入)
|
||||
子命令、状态查询、故障排查:见 [scripts/README.md](scripts/README.md)。
|
||||
|
||||
---
|
||||
|
||||
### 2.1 (旧方式) SSH 到 prod 后跑 prod_seed.sh
|
||||
|
||||
> ⚠️ **仅作为应急 / 兼容路径**。如果生产机没法 SSH 隧道、新流程出问题时,可以用这个。
|
||||
|
||||
```bash
|
||||
ssh root@101.132.250.62
|
||||
cd /opt/topfans/loadtest
|
||||
bash scripts/prod_seed.sh
|
||||
```
|
||||
|
||||
169
backend/scripts/loadgen/scripts/README.md
Normal file
169
backend/scripts/loadgen/scripts/README.md
Normal file
@ -0,0 +1,169 @@
|
||||
# scripts/ — 部署到生产机的辅助脚本
|
||||
|
||||
> 这些脚本不需要在生产机上跑。`prod_loadtest.sh` 是一站式包装,
|
||||
> 把所有 SSH 隧道、seed、loadgen、preflight、report、cleanup 操作
|
||||
> 串成子命令,本地直接调,目标始终是生产 DB / 生产网关。
|
||||
|
||||
---
|
||||
|
||||
## 📜 脚本清单
|
||||
|
||||
| 脚本 | 用途 | 谁要跑 |
|
||||
|------|------|--------|
|
||||
| **[prod_loadtest.sh](prod_loadtest.sh)** | 一站式压测:子命令模式 (`up` / `seed` / `loadgen` / `report` / `cleanup-all` / `pipeline` 等) | on-call、想跑压测的工程师 |
|
||||
| [prod_seed.sh](prod_seed.sh) | ⚠️ 旧脚本:只能在生产机上跑 (SSH 进去后 `bash prod_seed.sh`) | 不推荐使用,统一改用 prod_loadtest.sh |
|
||||
| [mint_reset.sh](mint_reset.sh) | mint 数据重置(每跑完一个 S4 stage 调一次) | loadgen S4 场景内部调用,不用手跑 |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始 (prod_loadtest.sh)
|
||||
|
||||
### 一键完成整个压测流程
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### 细粒度控制(推荐)
|
||||
|
||||
```bash
|
||||
SCRIPT=./backend/scripts/loadgen/scripts/prod_loadtest.sh
|
||||
|
||||
# 1. 建 SSH 隧道
|
||||
$SCRIPT up
|
||||
|
||||
# 2. 备份生产 DB
|
||||
$SCRIPT backup
|
||||
|
||||
# 3. 灌 1000 测试用户 + 23k 行数据
|
||||
$SCRIPT seed
|
||||
|
||||
# 4. 开压前 7 项检查
|
||||
$SCRIPT preflight
|
||||
|
||||
# 5. 跑压测 (所有 loadgen 参数透传)
|
||||
$SCRIPT loadgen \
|
||||
--scenarios=S1,S2,S4 \
|
||||
--stage=step --step-schedule='5,10,20' \
|
||||
--duration=60s
|
||||
|
||||
# 6. 生成 final-report.md
|
||||
$SCRIPT report
|
||||
|
||||
# 7. 全清压测数据 (含 users + stars,序列同步)
|
||||
$SCRIPT cleanup-all
|
||||
|
||||
# 8. 关隧道
|
||||
$SCRIPT down
|
||||
```
|
||||
|
||||
### 查看状态(任何时候)
|
||||
|
||||
```bash
|
||||
$SCRIPT status
|
||||
# == SSH 隧道 ==
|
||||
# ✅ 本地 25432 端口转发在跑 (PID: 12345)
|
||||
# == 生产 DB 容器 ==
|
||||
# topfans-postgres Up 22 hours (healthy) 0.0.0.0:5432->5432/tcp
|
||||
# == 最近 3 次备份 ==
|
||||
# pre-loadtest-20260616-2151.sql 1.2M
|
||||
# == 本地 reports/ ==
|
||||
# S1.json / S2.json / S4.json / final-report.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 子命令速查
|
||||
|
||||
| 子命令 | 说明 | 副作用 |
|
||||
|--------|------|--------|
|
||||
| `up` | 建 SSH 隧道 `本地 25432 → 生产 5432` | 启动 `ssh -f -N -L 25432:...` |
|
||||
| `down` | 关 SSH 隧道 | 杀掉 ssh 进程 |
|
||||
| `status` | 隧道/容器/备份/reports 状态 | 只读 |
|
||||
| `backup` | 备份生产 DB → `/opt/topfans/backups/pre-loadtest-<ts>.sql` | 写生产机磁盘 |
|
||||
| `seed` | 灌 1000 users + 23k 行 (通过隧道写生产 DB) | 写生产 DB |
|
||||
| `preflight` | 7 项开压前检查 | 只读 |
|
||||
| `loadgen [args]` | 跑压测,所有参数透传给 `./bin/loadgen --cmd=run` | 打生产网关 + 写 reports/ |
|
||||
| `report` | 把 reports/ 下的 S*.json 合成 final-report.md | 写 reports/final-report.md |
|
||||
| `cleanup` | 清理压测数据(**保留** users + stars) | 写生产 DB(删 rows) |
|
||||
| `cleanup-all` | 全清(**含** users + stars,序列同步重置) | 写生产 DB(删 rows + setval) |
|
||||
| `pipeline [args]` | `up → backup → seed → preflight → loadgen → report → cleanup-all → down` | 一条龙,loadgen args 透传 |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 环境变量(可选覆盖)
|
||||
|
||||
```bash
|
||||
PROD_HOST=root@101.132.250.62 # 默认
|
||||
TUNNEL_PORT=25432 # 默认
|
||||
BACKEND_DIR=... # 默认脚本所在目录的 ../../..
|
||||
```
|
||||
|
||||
例:测不同端口:
|
||||
```bash
|
||||
TUNNEL_PORT=35432 $SCRIPT up
|
||||
TUNNEL_PORT=35432 $SCRIPT seed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ 设计要点
|
||||
|
||||
### 1. SSH 端口转发为啥必要?
|
||||
- 生产机 `topfans-postgres` 容器是 PostgreSQL **18.3**
|
||||
- 本地 `pg_dump` 是 **17.4** → 版本不兼容
|
||||
- 解决:seed/cleanup 走 `docker exec topfans-postgres pg_dump` 容器内跑 (版本匹配)
|
||||
- `prod_loadtest.sh` 把这个细节封装在 `seed` / `cleanup` / `cleanup-all` 里
|
||||
|
||||
### 2. 凭据从哪来?
|
||||
- 所有子命令从 `docker/.env.prod` 读 `DB_PASSWORD` / `JWT_SECRET`
|
||||
- 不在命令行明文泄露
|
||||
- 调用方看不到 .env.prod 文件(只要 prod_loadtest.sh 内部读)
|
||||
|
||||
### 3. 数据安全
|
||||
- `--cleanup`(默认):保留 1000 users,只清资产(支持"多轮压测")
|
||||
- `--cleanup-all`:**含** users + stars(适合"压测彻底收尾")
|
||||
- 序列同步:cleanup 末尾会 `setval()` 所有相关表的 sequence(避免后续 GORM 报 duplicate key)
|
||||
|
||||
### 4. 为什么不用 prod_seed.sh?
|
||||
- `prod_seed.sh` 需要 SSH 到生产机跑,**有歧义**(以为是 prod-only 工具)
|
||||
- `prod_loadtest.sh` 全部在本地,**语义清晰**(本地调,目标生产)
|
||||
- 保留 `prod_seed.sh` 是为了向后兼容(已经在生产机上跑过的人),但 README/工具集都优先推荐 `prod_loadtest.sh`
|
||||
|
||||
---
|
||||
|
||||
## 🔍 故障排查
|
||||
|
||||
### "本地 25432 端口未监听"
|
||||
跑 `$SCRIPT up` 建隧道。
|
||||
|
||||
### "DB_PASSWORD 未找到"
|
||||
检查 `docker/.env.prod` 是否存在并有 `DB_PASSWORD=` 行。
|
||||
|
||||
### preflight ③ backup 报 < 50MB
|
||||
生产库本身小(几十 MB),RUNBOOK 红线是历史经验值,不是阻断错误。
|
||||
|
||||
### loadgen 报 `circuit breaker tripped!`
|
||||
看 `loadgen-*.log` 找 `🚨 circuit breaker tripped!` 上下文:
|
||||
- 如果错误率/p99 正常,可能是 `metrics-feed.jsonl` 缺失导致误 trip(已修复:`latestServer` 改 nil 判断)
|
||||
- 如果是真有错,看报告里 `S*.json` 的 `errors` 字段
|
||||
|
||||
### SSH 隧道被挂起或断
|
||||
```bash
|
||||
$SCRIPT down
|
||||
$SCRIPT up
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📂 相关文件
|
||||
|
||||
- 上层: [../README.md](../README.md) | [../RUNBOOK.md](../RUNBOOK.md) | [../REPORT_GUIDE.md](../REPORT_GUIDE.md)
|
||||
- seed 工具: [../seed/README.md](../seed/README.md)
|
||||
- reports 输出: [../../reports/](../../reports/) (gitignore)
|
||||
346
backend/scripts/loadgen/scripts/prod_loadtest.sh
Executable file
346
backend/scripts/loadgen/scripts/prod_loadtest.sh
Executable file
@ -0,0 +1,346 @@
|
||||
#!/bin/bash
|
||||
# ===================================================================
|
||||
# prod_loadtest.sh — 本地一键压测生产环境
|
||||
# ===================================================================
|
||||
# 设计目的: 不用 SSH 上生产机,本地直接调 seed/loadgen/cleanup
|
||||
# 打生产 DB(走 SSH 端口转发)和生产网关(走公网)
|
||||
#
|
||||
# 架构:
|
||||
# 1. SSH 端口转发: 本地 25432 → 生产机 docker 5432
|
||||
# (绕开 host pg_dump 17.4 vs 容器 PG 18.3 版本不匹配问题)
|
||||
# 2. seed/loadgen/cleanup: 直接在本地跑,通过环境变量/flag 传生产凭据
|
||||
#
|
||||
# 用法:
|
||||
# ./prod_loadtest.sh <command> [args...]
|
||||
#
|
||||
# Commands:
|
||||
# up 建立 SSH 端口转发 (本地 25432 → 生产 5432)
|
||||
# down 关闭 SSH 端口转发
|
||||
# status 查看隧道/DB/容器状态
|
||||
# backup 备份生产 DB 到 /opt/topfans/backups/
|
||||
# preflight 跑 7 项 preflight 检查
|
||||
# seed 灌 1000 测试用户 + 23k 行数据
|
||||
# loadgen [args...] 跑压测,所有参数透传给 ./bin/loadgen
|
||||
# 例: ./prod_loadtest.sh loadgen --scenarios=S1,S2,S4 \
|
||||
# --stage=step --step-schedule='5,10,20' \
|
||||
# --duration=60s
|
||||
# report 把 reports/ 下的 S*.json 合成 final-report.md
|
||||
# cleanup 清理压测数据(保留 users + stars,适合多轮压测)
|
||||
# cleanup-all 全清(含 users + stars,适合彻底收尾)
|
||||
# pipeline [args...] 一键: up → backup → seed → preflight → loadgen
|
||||
# → report → cleanup-all → down(loadgen args 透传)
|
||||
# help 显示本帮助
|
||||
#
|
||||
# 环境变量(可覆盖):
|
||||
# PROD_HOST 生产 SSH 地址 (默认 root@101.132.250.62)
|
||||
# TUNNEL_PORT 本地转发端口 (默认 25432)
|
||||
# BACKEND_DIR backend 绝对路径 (默认脚本所在目录的 ../../.. 即 backend/)
|
||||
#
|
||||
# 典型工作流(交互):
|
||||
# ./prod_loadtest.sh up
|
||||
# ./prod_loadtest.sh backup
|
||||
# ./prod_loadtest.sh seed
|
||||
# ./prod_loadtest.sh preflight
|
||||
# ./prod_loadtest.sh loadgen --scenarios=S1,S2,S4 --stage=step \
|
||||
# --step-schedule='5,10,20' --duration=60s
|
||||
# ./prod_loadtest.sh report
|
||||
# ./prod_loadtest.sh cleanup-all
|
||||
# ./prod_loadtest.sh down
|
||||
#
|
||||
# 一键完成:
|
||||
# ./prod_loadtest.sh pipeline --scenarios=S1,S2,S4 --stage=step \
|
||||
# --step-schedule='5,10,20' --duration=60s
|
||||
# ===================================================================
|
||||
set -euo pipefail
|
||||
|
||||
# ===== 路径与默认值 =====
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BACKEND_DIR="${BACKEND_DIR:-$(cd "$SCRIPT_DIR/../../.." && pwd)}"
|
||||
REPO_ROOT="$(cd "$BACKEND_DIR/.." && pwd)"
|
||||
PROD_HOST="${PROD_HOST:-root@101.132.250.62}"
|
||||
TUNNEL_PORT="${TUNNEL_PORT:-25432}"
|
||||
PROD_DB_NAME="topfans"
|
||||
PROD_DB_USER="postgres"
|
||||
ENV_PROD="$REPO_ROOT/docker/.env.prod"
|
||||
|
||||
# ===== 颜色输出 =====
|
||||
if [[ -t 1 ]]; then
|
||||
RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[0;33m'
|
||||
BLUE=$'\033[0;34m'; BOLD=$'\033[1m'; RESET=$'\033[0m'
|
||||
else
|
||||
RED=""; GREEN=""; YELLOW=""; BLUE=""; BOLD=""; RESET=""
|
||||
fi
|
||||
|
||||
info() { echo "${BLUE}>>>${RESET} $*"; }
|
||||
ok() { echo "${GREEN}✅${RESET} $*"; }
|
||||
warn() { echo "${YELLOW}⚠️${RESET} $*"; }
|
||||
err() { echo "${RED}❌${RESET} $*" >&2; }
|
||||
fatal() { err "$*"; exit 1; }
|
||||
|
||||
# ===== 通用工具 =====
|
||||
read_env() {
|
||||
# 读 docker/.env.prod 里的 key,值通过 stdout 返回
|
||||
local key="$1"
|
||||
if [[ ! -f "$ENV_PROD" ]]; then
|
||||
fatal "$ENV_PROD 不存在"
|
||||
fi
|
||||
grep "^${key}=" "$ENV_PROD" | cut -d= -f2
|
||||
}
|
||||
|
||||
ensure_built() {
|
||||
# 跑命令前确保 bin/seed bin/loadgen 已编译
|
||||
if [[ ! -x "$BACKEND_DIR/bin/seed" || ! -x "$BACKEND_DIR/bin/loadgen" ]]; then
|
||||
info "编译 seed + loadgen → $BACKEND_DIR/bin/"
|
||||
(cd "$BACKEND_DIR" && make loadgen-build >/dev/null)
|
||||
fi
|
||||
}
|
||||
|
||||
require_tunnel() {
|
||||
if ! lsof -iTCP:"$TUNNEL_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
fatal "本地 $TUNNEL_PORT 端口未监听,先跑: $0 up"
|
||||
fi
|
||||
}
|
||||
|
||||
# ===== 子命令:up =====
|
||||
cmd_up() {
|
||||
if lsof -iTCP:"$TUNNEL_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
ok "隧道已存在 (PID: $(lsof -t -iTCP:$TUNNEL_PORT -sTCP:LISTEN), port $TUNNEL_PORT)"
|
||||
return 0
|
||||
fi
|
||||
info "建立 SSH 隧道: 本地 $TUNNEL_PORT → $PROD_HOST:5432"
|
||||
ssh -o StrictHostKeyChecking=no \
|
||||
-o ServerAliveInterval=30 \
|
||||
-L "$TUNNEL_PORT":127.0.0.1:5432 \
|
||||
-N -f "$PROD_HOST"
|
||||
sleep 1
|
||||
if lsof -iTCP:"$TUNNEL_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
ok "隧道已建立 (PID: $(lsof -t -iTCP:$TUNNEL_PORT -sTCP:LISTEN))"
|
||||
else
|
||||
fatal "隧道建立失败"
|
||||
fi
|
||||
}
|
||||
|
||||
# ===== 子命令:down =====
|
||||
cmd_down() {
|
||||
if ! lsof -iTCP:"$TUNNEL_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
ok "隧道已关闭 (或没建过)"
|
||||
return 0
|
||||
fi
|
||||
local pid
|
||||
pid=$(lsof -t -iTCP:"$TUNNEL_PORT" -sTCP:LISTEN)
|
||||
kill "$pid" 2>/dev/null || true
|
||||
sleep 1
|
||||
if lsof -iTCP:"$TUNNEL_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
warn "普通 kill 没关掉,试 kill -9"
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
ok "隧道已关闭 (PID: $pid)"
|
||||
}
|
||||
|
||||
# ===== 子命令:status =====
|
||||
cmd_status() {
|
||||
echo "${BOLD}== SSH 隧道 ==${RESET}"
|
||||
if lsof -iTCP:"$TUNNEL_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
|
||||
ok "本地 $TUNNEL_PORT 端口转发在跑 (PID: $(lsof -t -iTCP:$TUNNEL_PORT -sTCP:LISTEN))"
|
||||
else
|
||||
warn "本地 $TUNNEL_PORT 端口转发没建"
|
||||
fi
|
||||
echo ""
|
||||
echo "${BOLD}== 生产 DB 容器 ==${RESET}"
|
||||
ssh "$PROD_HOST" "docker ps --filter name=topfans-postgres --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'"
|
||||
echo ""
|
||||
echo "${BOLD}== 最近 3 次备份 ==${RESET}"
|
||||
ssh "$PROD_HOST" "ls -lh /opt/topfans/backups/ 2>/dev/null | tail -3" || warn "无备份或 backups 目录不存在"
|
||||
echo ""
|
||||
echo "${BOLD}== 本地 reports/ ==${RESET}"
|
||||
ls -lh "$BACKEND_DIR/reports/"*.md "$BACKEND_DIR/reports/"*.json 2>/dev/null | tail -5 || warn "无 reports"
|
||||
}
|
||||
|
||||
# ===== 子命令:backup =====
|
||||
cmd_backup() {
|
||||
local ts
|
||||
ts=$(date +%Y%m%d-%H%M)
|
||||
local backup_path="/opt/topfans/backups/pre-loadtest-${ts}.sql"
|
||||
info "备份生产 DB → $backup_path"
|
||||
local db_pass
|
||||
db_pass=$(read_env DB_PASSWORD)
|
||||
ssh "$PROD_HOST" "DB_PASSWORD=\$(grep '^DB_PASSWORD=' /opt/topfans/docker/.env.prod | cut -d= -f2)
|
||||
mkdir -p /opt/topfans/backups
|
||||
docker exec -e PGPASSWORD=\"\$DB_PASSWORD\" topfans-postgres \
|
||||
pg_dump -U postgres -d topfans -Fc > $backup_path
|
||||
ls -lh $backup_path"
|
||||
ok "备份完成"
|
||||
}
|
||||
|
||||
# ===== 子命令:seed =====
|
||||
cmd_seed() {
|
||||
require_tunnel
|
||||
ensure_built
|
||||
|
||||
# bcrypt 文件(seed 用)
|
||||
local bcrypt_file="$BACKEND_DIR/loadtest_bcrypt.txt"
|
||||
if [[ ! -f "$bcrypt_file" ]]; then
|
||||
info "生成 $bcrypt_file (匹配 tokens.go 硬编码密码 'Test@123')"
|
||||
python3 -c "import bcrypt; print(bcrypt.hashpw(b'Test@123', bcrypt.gensalt(rounds=10)).decode())" > "$bcrypt_file"
|
||||
fi
|
||||
|
||||
info "seed: 1000 users + 23k 行 (写生产 DB 走 SSH 隧道)"
|
||||
local db_pass
|
||||
db_pass=$(read_env DB_PASSWORD)
|
||||
(
|
||||
cd "$BACKEND_DIR"
|
||||
DB_PASSWORD="$db_pass" \
|
||||
JWT_SECRET=$(read_env JWT_SECRET) \
|
||||
./bin/seed \
|
||||
--db-host=127.0.0.1 --db-port="$TUNNEL_PORT" \
|
||||
--db-name="$PROD_DB_NAME" --db-user="$PROD_DB_USER" \
|
||||
--db-password="$db_pass"
|
||||
)
|
||||
ok "seed 完成"
|
||||
}
|
||||
|
||||
# ===== 子命令:preflight =====
|
||||
cmd_preflight() {
|
||||
ensure_built
|
||||
local db_pass
|
||||
db_pass=$(read_env DB_PASSWORD)
|
||||
info "跑 preflight (7 项检查)"
|
||||
(
|
||||
cd "$BACKEND_DIR"
|
||||
DB_PASSWORD="$db_pass" \
|
||||
JWT_SECRET=$(read_env JWT_SECRET) \
|
||||
./bin/loadgen --cmd=preflight \
|
||||
--target=http://101.132.250.62:8080 \
|
||||
--prod-ssh="$PROD_HOST"
|
||||
)
|
||||
}
|
||||
|
||||
# ===== 子命令:loadgen =====
|
||||
cmd_loadgen() {
|
||||
require_tunnel
|
||||
ensure_built
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
fatal "loadgen 需要参数,例: $0 loadgen --scenarios=S1,S2,S4 --stage=step"
|
||||
fi
|
||||
local db_pass
|
||||
db_pass=$(read_env DB_PASSWORD)
|
||||
info "loadgen ${*}"
|
||||
(
|
||||
cd "$BACKEND_DIR"
|
||||
DB_PASSWORD="$db_pass" \
|
||||
JWT_SECRET=$(read_env JWT_SECRET) \
|
||||
./bin/loadgen --cmd=run \
|
||||
--target=http://101.132.250.62:8080 \
|
||||
--prod-ssh="$PROD_HOST" \
|
||||
"$@"
|
||||
)
|
||||
}
|
||||
|
||||
# ===== 子命令:report =====
|
||||
cmd_report() {
|
||||
ensure_built
|
||||
info "生成 final-report.md"
|
||||
(
|
||||
cd "$BACKEND_DIR"
|
||||
./bin/loadgen --cmd=report \
|
||||
--input=./reports \
|
||||
--output=./reports/final-report.md
|
||||
)
|
||||
ok "final-report.md 已更新: $BACKEND_DIR/reports/final-report.md"
|
||||
}
|
||||
|
||||
# ===== 子命令:cleanup =====
|
||||
cmd_cleanup() {
|
||||
require_tunnel
|
||||
ensure_built
|
||||
local db_pass
|
||||
db_pass=$(read_env DB_PASSWORD)
|
||||
warn "清理压测数据(保留 users + stars,适合多轮压测)"
|
||||
(
|
||||
cd "$BACKEND_DIR"
|
||||
DB_PASSWORD="$db_pass" \
|
||||
./bin/seed --cleanup \
|
||||
--db-host=127.0.0.1 --db-port="$TUNNEL_PORT" \
|
||||
--db-name="$PROD_DB_NAME" --db-user="$PROD_DB_USER" \
|
||||
--db-password="$db_pass"
|
||||
)
|
||||
ok "cleanup 完成"
|
||||
}
|
||||
|
||||
# ===== 子命令:cleanup-all =====
|
||||
cmd_cleanup_all() {
|
||||
require_tunnel
|
||||
ensure_built
|
||||
local db_pass
|
||||
db_pass=$(read_env DB_PASSWORD)
|
||||
warn "⚠️ 全清压测数据(含 users + stars,序列同步重置)"
|
||||
(
|
||||
cd "$BACKEND_DIR"
|
||||
DB_PASSWORD="$db_pass" \
|
||||
./bin/seed --cleanup --full \
|
||||
--db-host=127.0.0.1 --db-port="$TUNNEL_PORT" \
|
||||
--db-name="$PROD_DB_NAME" --db-user="$PROD_DB_USER" \
|
||||
--db-password="$db_pass"
|
||||
)
|
||||
ok "cleanup-all 完成"
|
||||
}
|
||||
|
||||
# ===== 子命令:pipeline =====
|
||||
cmd_pipeline() {
|
||||
info "=== pipeline: 1/8 up ==="
|
||||
cmd_up
|
||||
|
||||
info "=== pipeline: 2/8 backup ==="
|
||||
cmd_backup
|
||||
|
||||
info "=== pipeline: 3/8 seed ==="
|
||||
cmd_seed
|
||||
|
||||
info "=== pipeline: 4/8 preflight ==="
|
||||
cmd_preflight || warn "preflight 有 FAIL,继续 (③ backup <50MB 预期会 FAIL)"
|
||||
|
||||
info "=== pipeline: 5/8 loadgen ==="
|
||||
cmd_loadgen "$@" || warn "loadgen 出错,继续到 cleanup"
|
||||
|
||||
info "=== pipeline: 6/8 report ==="
|
||||
cmd_report || warn "report 生成失败,继续"
|
||||
|
||||
info "=== pipeline: 7/8 cleanup-all ==="
|
||||
cmd_cleanup_all || warn "cleanup 失败"
|
||||
|
||||
info "=== pipeline: 8/8 down ==="
|
||||
cmd_down
|
||||
|
||||
ok "pipeline 完成"
|
||||
}
|
||||
|
||||
# ===== 帮助 =====
|
||||
cmd_help() {
|
||||
sed -n '3,50p' "$0"
|
||||
}
|
||||
|
||||
# ===== 入口 =====
|
||||
if [[ $# -eq 0 ]]; then
|
||||
cmd_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cmd="${1:-help}"
|
||||
shift || true
|
||||
|
||||
case "$cmd" in
|
||||
up) cmd_up ;;
|
||||
down) cmd_down ;;
|
||||
status) cmd_status ;;
|
||||
backup) cmd_backup ;;
|
||||
seed) cmd_seed "$@" ;;
|
||||
preflight) cmd_preflight ;;
|
||||
loadgen) cmd_loadgen "$@" ;;
|
||||
report) cmd_report ;;
|
||||
cleanup) cmd_cleanup ;;
|
||||
cleanup-all) cmd_cleanup_all ;;
|
||||
pipeline) cmd_pipeline "$@" ;;
|
||||
help|-h|--help) cmd_help ;;
|
||||
*) err "未知子命令: $cmd"; cmd_help; exit 2 ;;
|
||||
esac
|
||||
@ -4,6 +4,22 @@
|
||||
|
||||
---
|
||||
|
||||
## ⭐ 推荐入口:prod_loadtest.sh
|
||||
|
||||
> **如果你目标是打生产压测,优先用 [`../scripts/prod_loadtest.sh`](../scripts/prod_loadtest.sh) 一站式脚本**。
|
||||
> 它会把下面的所有步骤(SSH 隧道/seed/preflight/loadgen/report/cleanup)封装成子命令,
|
||||
> 凭据从 `docker/.env.prod` 自动读,避免明文泄露。
|
||||
|
||||
```bash
|
||||
# 一键完整流程
|
||||
./../scripts/prod_loadtest.sh pipeline --scenarios=S1,S2,S4 \
|
||||
--stage=step --step-schedule='5,10,20' --duration=60s
|
||||
```
|
||||
|
||||
详见 [../scripts/README.md](../scripts/README.md)。
|
||||
|
||||
---
|
||||
|
||||
## 一句话总结
|
||||
|
||||
跑 `./seed`,数据库里多出 1000 个用户 + 5000 个 assets + 2000 个 exhibitions,本地多出 `users.csv` (含 JWT)。
|
||||
|
||||
@ -20,6 +20,14 @@ func Cleanup(db *sql.DB, starID int64, full bool) error {
|
||||
"DELETE FROM friendships WHERE star_id = $1",
|
||||
"DELETE FROM assets WHERE star_id = $1",
|
||||
"DELETE FROM fan_profiles WHERE star_id = $1",
|
||||
// 通知系统 (2026-06-16 加的) — 压测期间 user 关注 loadtest star
|
||||
// 触发了 1000 条 notification + 1000 条 notification_stats。
|
||||
// 用 OR 同时覆盖 user_id 和 star_id 两条线索,保证清干净。
|
||||
// 必须在 fan_profiles 删完之后跑(避免 fan_profiles FK 锁)。
|
||||
// 注意: lib/pq 的 $1 占位符是按"unique 编号"算的,不能重复 $1 —
|
||||
// 重复 $1 会让 pq 期望 1 个参数,我们传 2 个就报 got 2/requires 1。
|
||||
"DELETE FROM notifications WHERE user_id IN (SELECT id FROM users WHERE id >= $1) OR star_id = $2",
|
||||
"DELETE FROM notification_stats WHERE user_id IN (SELECT id FROM users WHERE id >= $1) OR star_id = $2",
|
||||
}
|
||||
if full {
|
||||
queries = append(queries,
|
||||
@ -37,6 +45,11 @@ func Cleanup(db *sql.DB, starID int64, full bool) error {
|
||||
switch q {
|
||||
case "DELETE FROM users WHERE id >= $1":
|
||||
_, err = db.Exec(q, LoadtestUserMin)
|
||||
case "DELETE FROM notifications WHERE user_id IN (SELECT id FROM users WHERE id >= $1) OR star_id = $2",
|
||||
"DELETE FROM notification_stats WHERE user_id IN (SELECT id FROM users WHERE id >= $1) OR star_id = $2":
|
||||
// notifications / notification_stats 用 LoadtestUserMin 删 user 维度,
|
||||
// 用 starID 删 star 维度。$1 / $2 顺序必须跟 SQL 出现顺序一致。
|
||||
_, err = db.Exec(q, LoadtestUserMin, starID)
|
||||
default:
|
||||
_, err = db.Exec(q, starID)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user