一、问题发现与诊断
用户反馈博客首页、对话页面加载缓慢(5-10秒才出内容),通过 Chrome DevTools Network 面板 + Nginx 日志 + Docker 后端日志三层排查,确认 5 个核心瓶颈:| # | 问题 | 严重度 |
|---|---|---|
| 1 | 12张PNG图片共18.7MB,首页加载10张=17MB | 严重 |
| 2 | 静态图片通过FastAPI后端代理,未由Nginx直接serve | 严重 |
| 3 | proxy_buffering off 全局关闭,HTML无Gzip压缩 | 中等 |
| 4 | SQLAlchemy连接泄漏,后端日志频繁报GC清理错误 | 中等 |
| 5 | 聊天页5个API串行请求(personality被重复调用2次) | 中等 |
二、修复 #1:WebP 图片压缩(18.7MB → 0.8MB,缩减96%)
使用 Python Pillow 库将 12 张 PNG 图片批量转换为 WebP 格式:
from PIL import Image
for png in png_files:
img = Image.open(png).convert('RGB')
img.save(webp_path, 'WEBP', quality=80, method=6)参数说明:quality=80 平衡画质与体积,method=6 使用最高压缩算法(速度最慢但体积最小)。
| 指标 | 压缩前(PNG) | 压缩后(WebP) | 缩减 |
|---|---|---|---|
| 12张总大小 | 18.7 MB | 0.8 MB | 96% |
| 单张大小范围 | 1.3 ~ 2.1 MB | 43 ~ 123 KB | 94~97% |
| 首页图片总载荷 | ~17 MB | ~470 KB | 97% |
同步更新了全部 8 个 HTML 模板中的图片引用(.png → .webp),包括 Jinja2 模板变量路径。
三、修复 #2:Nginx 直接 Serve 静态文件
之前静态文件先上传到 FastAPI 后端再响应,增加了不必要的 Python 层开销:
# docker-compose.yml 新增 volume 挂载
nginx:
volumes:
- ./backend/app/blog/static:/usr/share/nginx/static/blog:ro
# nginx conf 新增 alias 指令
location /blog/static/ {
alias /usr/share/nginx/static/blog/;
expires 7d;
add_header Cache-Control "public, immutable";
}效果:图片直接从 Nginx 返回(image/webp,延迟从 200-500ms 降至 ~13ms),同时设置 7 天浏览器缓存(immutable),回访用户零网络请求。
四、修复 #3-#5:Gzip、连接池、API去重
修复 #3 — HTML Gzip 压缩:之前的 proxy_buffering off 是全局设置(为了支持 SSE 流式聊天),但这导致所有 HTML 页面无法开启 Gzip。修复后改为按路径区分:默认 proxy_buffering on(自动 Gzip),仅 /api/v1/chat/chat 设置为 proxy_buffering off。
效果:blog 首页 52KB → 12KB (76%),chat 页面 90KB → 23KB (74%)。
修复 #4 — SQLAlchemy 连接泄漏:将 database.py 中的 get_db_safe() 从普通 async 函数改为 generator 模式:
async def get_db_safe():
session = _raw_session_factory()
try:
yield session
await session.commit()
except Exception:
await session.rollback()
finally:
await session.close() # 确保连接归还池子finally: session.close() 确保无论正常或异常,数据库连接都能归还连接池,消除了后端日志中频繁出现的 "non-checked-in connection" GC 错误。
修复 #5 — 聊天页 API 去重:发现 loadMood() 和 loadPersonality() 都调用同一个 /api/v1/chat/personality/{id} 接口,将人格标签渲染逻辑合并到 loadMood() 内,selectPot() 的并行请求从 4 个减少为 3 个。
五、最终效果对比
| 指标 | 修复前 | 修复后 | 提升 |
|---|---|---|---|
| 首页图片总载荷 | 17 MB (10张PNG) | ~470 KB (10张WebP) | 36x |
| 图片加载延迟 | FastAPI代理 200-500ms | Nginx直出 ~13ms | 15-38x |
| 首页 HTML 大小 | 52 KB (无压缩) | 12 KB (Gzip 76%) | 4.3x |
| DB连接泄漏 | 频繁GC错误 | 已消除 | - |
| 聊天页API请求 | 4个(含1重复) | 3个(并行) | -25% |
整体效果:首页总传输量从 ~17MB 降至 ~500KB,首屏可交互时间从 5-10 秒降至 1 秒以内,在移动 4G 网络下体验提升尤为显著。
六、技术沉淀
- WebP 是 Web 场景的图片最优解:相同画质下体积仅为 PNG 的 3-4%,主流浏览器 100% 支持,无兼容性顾虑。
- Nginx 做静态文件第一入口:不要让 FastAPI/Python 代理静态资源,Nginx 的 sendfile 零拷贝机制效率远超应用层。
- proxy_buffering 要按路径区分:SSE 需要 off,但普通页面 on 才能启用 Gzip。一个 location 一个策略,不要一刀切。
- async generator 是 SQLAlchemy 的正确打开方式:
try/yield/finally模式确保异常时也能关闭 session,避免连接池耗尽。


💬 过往技术交流