《猫猫钓游记》可爱+收集+钓鱼游戏试玩
2026-06-30
2026-07-04 0
最近我将一个自建的 AI API 网关从旧服务器迁移到新服务器,期间遇到了一些典型的运维问题,包括:

这篇文章记录了我遇到的问题和解决方案,供有类似需求的朋友参考。
初始数据库有 3.3GB,但其中 90% 以上是无价值的日志数据。通过 pg_dump 排除日志表,核心业务数据只有 几十 MB。
-- 找出哪些表占空间最大
SELECT table_name,
pg_size_pretty(pg_total_relation_size(table_name::text)) as total_size
FROM (SELECT tablename FROM pg_tables WHERE schemaname = 'public') t
ORDER BY pg_total_relation_size(table_name::text) DESC
LIMIT 10;
结果发现前 5 张日志表占了绝大部分空间:
| 表 | 大小 | 说明 |
|---|---|---|
| ops_system_logs | 2.2 GB | 系统操作日志 |
| usage_logs | 425 MB | API 调用记录 |
| usage_billing_dedup | 273 MB | 计费去重 |
| ops_error_logs | 260 MB | 错误日志 |
| scheduler_outbox | 63 MB | 调度队列 |
pg_dump -U user -d database -T ops_system_logs -T ops_error_logs -T usage_logs -T scheduler_outbox --no-owner --no-acl -Fc -f backup.dump
注意:如果应用代码里引用了这些表的索引或外键(比如 billing_usage_entries 引用了 usage_logs.id),需要在目标库手动补上空表结构,只建表不插数据:
# 从源库导出这些表的 schema(不含数据) pg_dump -U user -d database -t ops_system_logs --schema-only > missing_tables.sql # 在目标库执行 psql -U user -d database < missing_tables.sql
| 指标 | 迁移前 | 迁移后 |
|---|---|---|
| 数据库大小 | 3.3 GB | 31 MB |
| 用户数 | 3,234 | 3,234(一致 ✅) |
| 迁移耗时 | — | 15 秒 |
原服务器使用 Nginx 做反向代&理,切换到 Caddy 后获得了几个好处:
api.example.com {
# 反向代&理到后端
reverse_proxy localhost:8080 {
health_uri /health
health_interval 30s
health_timeout 10s
health_status 200
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
transport http {
keepalive 120s
keepalive_idle_conns 256
read_buffer 16KB
write_buffer 16KB
}
fail_duration 30s
max_fails 3
unhealthy_status 500 502 503 504
}
# 压缩配置(zstd + gzip 双支持)
encode {
zstd
gzip 6
minimum_length 256
match {
header Content-Type application/json*
header Content-Type application/javascript*
header Content-Type text/*
}
}
# 请求体限制
request_body {
max_size 50MB
}
}
以 JSON API 响应(35KB)为例:
| 压缩方式 | 大小 | 压缩比 |
|---|---|---|
| 无压缩 | 35.7 KB | — |
| gzip | 15.7 KB | 56% |
| zstd | 15.9 KB | 55% |
实际测试对文本类 API 响应,压缩率在 50-70%。
服务器流量消耗异常快,怀疑是被攻击或有异常请求。
1. 检查 nginx/Caddy 日志中的实际流量
# 统计每日流量
sudo awk '{date=substr($4,2,11); bytes[date]+=$10}
END{for(d in bytes) printf "%s %.2f GBn", d, bytes[d]/1024/1024/1024}'
/var/log/caddy/api.log | sort
2. 按 URL 路径分析流量分布
sudo awk '{urls[$7]+=$10; count[$7]++}
END{for(u in urls) printf "%.2f GB (%d reqs) %sn", urls[u]/1024/1024/1024, count[u], u}'
/var/log/caddy/api.log | sort -rn | head -10
3. 找出单次响应异常的请求
sudo awk '$7 == "/responses" {if($10>max){max=$10; line=$0}}
END{printf "最大响应: %.1f MBn%sn", max/1024/1024, line}' access.log
/responses(AI 聊天 API)占流量的 80% 以上| 措施 | 效果 |
|---|---|
| 开启 zstd/gzip 压缩 | API 响应缩小 50-60% |
| 请求体限制 256MB → 50MB | 防止单次异常消耗 |
| 给高消耗用户加 RPM 限速 | 控制总请求量 |
每天凌晨自动备份数据库,排除日志表,保留 7 天。备份同时传输到远程服务器做异地容灾。
#!/bin/bash set -euo pipefail BACKUP_DIR="/data/backups" REMOTE_SERVER="[email protected]" RETENTION_DAYS=7 # 排除的日志表 EXCLUDE_TABLES=( ops_system_logs ops_error_logs usage_logs scheduler_outbox usage_billing_dedup ) mkdir -p "$BACKUP_DIR" BACKUP_FILE="db_backup_$(date +%Y%m%d_%H%M%S).dump" # 构建排除参数 EXCLUDE_ARGS="" for table in "${EXCLUDE_TABLES[@]}"; do EXCLUDE_ARGS="$EXCLUDE_ARGS -T $table" done # 备份 docker exec postgres pg_dump -U user -d database $EXCLUDE_ARGS --no-owner --no-acl -Fc > "$BACKUP_PATH" # 验证备份完整性 docker run --rm postgres:18-alpine pg_restore -l "$BACKUP_FILE" || exit 1 # 传输到远程 scp "$BACKUP_PATH" "${REMOTE_SERVER}:${BACKUP_DIR}/" # 清理过期备份 find "$BACKUP_DIR" -name "*.dump" -type f -mtime +$RETENTION_DAYS -delete
设置定时任务:
echo "0 5 * * * root /opt/scripts/daily_backup.sh" > /etc/cron.d/db-backup
切换到 Caddy 后可能遇到 503:
原因:Caddy 的健康检查发现后端不可用后,会将后端标记为不可用一段时间(fail_duration 30s)。
解决:
# 重启后端后需要重载 Caddy caddy reload --config /etc/caddy/Caddyfile
或者修改健康检查配置,降低判定阈值:
reverse_proxy localhost:8080 {
health_uri /health
health_interval 10s
health_timeout 5s
fail_duration 10s
max_fails 1
}
请求体超出限制时返回 413。
排查:
# 查看 nginx/Caddy 错误日志 grep "413" /var/log/caddy/*.log # 确认当前限制值 grep max_size /etc/caddy/Caddyfile
如果通过 Cloudflare,还需要注意 Cloudflare 免费版限制了 100MB 的最大请求体,超过会被 Cloudflare 直接拦截。
这次迁移总结了几点经验:
pg_restore -l 检查备份完整性,否则等于没备份代码和配置示例仅供参考,实际部署需要根据具体环境调整。