Codex API 网关迁移与流量优化实战

AI API 网关迁移与流量优化实战

背景

最近我将一个自建的 AI API 网关从旧服务器迁移到新服务器,期间遇到了一些典型的运维问题,包括:

  • 数据库从 3.3GB 压缩到 30MB
  • 日均流量从 20GB 降到 10GB 以下
  • 反向代理从 Nginx 切换到 Caddy
  • 数据库异地自动备份

这篇文章记录了我遇到的问题和解决方案,供有类似需求的朋友参考。


一、数据库迁移:只带业务数据,不带日志

初始数据库有 3.3GB ,但其中 90% 以上是无价值的日志数据。通过 pg_dump 排除日志表,核心业务数据只有 几十 MB。

排除大日志表

sql 复制代码
-- 找出哪些表占空间最大
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 排除特定表

bash 复制代码
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),需要在目标库手动补上空表结构,只建表不插数据:

bash 复制代码
# 从源库导出这些表的 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 切换

原服务器使用 Nginx 做反向代理,切换到 Caddy 后获得了几个好处:

为什么换 Caddy

  1. 自动 HTTPS --- 无需手动申请和续期 Let's Encrypt
  2. 内置 zstd 压缩 --- 比 gzip 压缩率更高
  3. 更简洁的配置 --- 优雅的 Caddyfile 语法
  4. 原生 HTTP/2 和 HTTP/3 支持

Caddyfile 配置

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 日志中的实际流量

bash 复制代码
# 统计每日流量
sudo awk '{date=substr($4,2,11); bytes[date]+=$10} 
     END{for(d in bytes) printf "%s  %.2f GB\n", d, bytes[d]/1024/1024/1024}' 
     /var/log/caddy/api.log | sort

2. 按 URL 路径分析流量分布

bash 复制代码
sudo awk '{urls[$7]+=$10; count[$7]++} 
     END{for(u in urls) printf "%.2f GB  (%d reqs)  %s\n", urls[u]/1024/1024/1024, count[u], u}' 
     /var/log/caddy/api.log | sort -rn | head -10

3. 找出单次响应异常的请求

bash 复制代码
sudo awk '$7 == "/responses" {if($10>max){max=$10; line=$0}} 
     END{printf "最大响应: %.1f MB\n%s\n", max/1024/1024, line}' access.log

发现

  • /responses(AI 聊天 API)占流量的 80% 以上
  • 有人单次请求生成了 289.7 MB 的响应(疑似图片生成)
  • 大部分用户平均响应只有 220KB
  • 请求体限制之前配置为 256MB,过于宽松

优化措施

措施 效果
开启 zstd/gzip 压缩 API 响应缩小 50-60%
请求体限制 256MB → 50MB 防止单次异常消耗
给高消耗用户加 RPM 限速 控制总请求量

四、自动备份脚本

每天凌晨自动备份数据库,排除日志表,保留 7 天。备份同时传输到远程服务器做异地容灾。

bash 复制代码
#!/bin/bash
set -euo pipefail

BACKUP_DIR="/data/backups"
REMOTE_SERVER="user@backup.example.com"
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

设置定时任务:

bash 复制代码
echo "0 5 * * * root /opt/scripts/daily_backup.sh" > /etc/cron.d/db-backup

五、常见问题排查

503 Service Unavailable

切换到 Caddy 后可能遇到 503:

原因 :Caddy 的健康检查发现后端不可用后,会将后端标记为不可用一段时间(fail_duration 30s)。

解决:

bash 复制代码
# 重启后端后需要重载 Caddy
caddy reload --config /etc/caddy/Caddyfile

或者修改健康检查配置,降低判定阈值:

caddy 复制代码
reverse_proxy localhost:8080 {
    health_uri /health
    health_interval 10s
    health_timeout 5s
    fail_duration 10s
    max_fails 1
}

413 Payload Too Large

请求体超出限制时返回 413。

排查:

bash 复制代码
# 查看 nginx/Caddy 错误日志
grep "413" /var/log/caddy/*.log

# 确认当前限制值
grep max_size /etc/caddy/Caddyfile

如果通过 Cloudflare,还需要注意 Cloudflare 免费版限制了 100MB 的最大请求体,超过会被 Cloudflare 直接拦截。


总结

这次迁移总结了几点经验:

  1. 数据库日志要定期清理 --- 设计好保留策略,不然日志会占满磁盘
  2. 反向代理优先选 Caddy --- 配置简单,自动 HTTPS,自带 zstd 压缩
  3. 流量分析要找源头 --- 不要盲目扩带宽,先看流量花在哪
  4. 备份要验证 --- 用 pg_restore -l 检查备份完整性,否则等于没备份
  5. 异地备份 --- 主备两台服务器互相备份,防止单点故障

代码和配置示例仅供参考,实际部署需要根据具体环境调整。

相关推荐
夜雪一千7 分钟前
MySQL 数据字典如何获得
数据库·mysql
系统集成-鹰翔11 分钟前
ERP推单数据库怎么备份:SQLite WAL模式下只复制db文件为什么可能漏任务
javascript·数据库·经验分享·后端
QianhangQianping13 分钟前
撤销重做不是「存快照」:一个可视化编辑器的Operation 设计
java·前端·数据库·typescript·编辑器·前端架构·撤销重做
honsor14 分钟前
用 Grafana 做温湿度异常检测:从阈值告警到趋势预测
数据库·mysql·php
赵渝强老师27 分钟前
【赵渝强老师】YashanDB Developer Center(YDC,崖山数据库开发者中心)
数据库·国产数据库·yashandb·崖山数据库
lusklusklusk29 分钟前
SQL Server 为什么普通 SELECT 会阻塞 UPDATE?——行级 S 锁到表级 S 锁的锁升级原理与实验验证
数据库·mysql·oracle
Leo.yuan37 分钟前
企业业务人员如何独立完成数据分析?2026年自助BI工具选型与应用指南
大数据·数据库·人工智能
roman_日积跬步-终至千里38 分钟前
数仓开发的三层责任:业务定义、模型设计与 SQL 实现
数据库·数据仓库·sql·数据建模
瀚高PG实验室40 分钟前
瀚高数据库创建新数据库卡住问题排查
数据库·postgresql·瀚高数据库
瑶总迷弟1 小时前
银河麒麟 V10 ARM64 安装 Oracle 19c 静默部署操作手册
数据库·oracle