完整架构:从 0 到生产级

六个月后的架构图

从最简实现到现在,“光影”的图片系统经历了 6 个月、4 次大重构。这是最终的生产级架构:

最简实现:Flask + 本地磁盘,无 CDN,无压缩
用户层
用户浏览器 直接访问服务器
应用层
Flask 服务器 1 核 2G,5Mbps 带宽
存储层
本地磁盘 100GB,无冗余
客户端直传 OSS + 缩略图生成 + WebP 转换
用户层
用户浏览器 直传 OSS
应用层
上传服务 签发 STS 凭证
处理服务 缩略图 + WebP
存储层
阿里云 OSS 原图 + 缩略图
PostgreSQL 元数据
CDN 全球加速 + 内容审核 + 异步处理队列
用户层
用户浏览器 就近访问 CDN
CDN 层
阿里云 CDN 2800+ 节点,边缘裁剪
应用层
上传服务 ×3 签发凭证 + 回调
审核服务 ×2 鉴黄 + OCR
处理服务 ×4 多尺寸 + WebP/AVIF
数据层
阿里云 OSS 标准 + 低频 + 归档
RabbitMQ 异步处理队列
PostgreSQL 元数据 + 用户
生产级图片系统:12,000 用户,85,000 张图片,月成本 ¥1,720
用户层
用户浏览器 <picture> + 懒加载
CDN 层
阿里云 CDN 缓存命中率 96%,边缘裁剪
服务层
上传服务 ×3 STS 凭证 + 频率限制
审核服务 ×2 鉴黄 + OCR + 人审
处理服务 ×4 多尺寸 + EXIF
数据层
阿里云 OSS 标准 + 低频 + 归档
RabbitMQ upload→audit→process
辅助系统
Prometheus CDN 命中率 / 延迟
Grafana 监控面板 + 告警

组件详解

上传链路

上传链路的目标不是“把文件传上去”这么简单,而是让浏览器、业务服务和对象存储各自承担合适的职责。业务服务只负责鉴权、生成上传凭证和记录元数据;大文件直接进入对象存储,避免把应用服务器变成流量中转站。

这个设计有两个好处。第一,上传峰值不会直接压垮业务服务;第二,上传完成后的处理可以通过事件触发,压缩、审核、派生图生成都能异步执行。用户看到的是一个上传动作,系统内部其实已经把“接收文件”和“处理文件”拆开了。

存储架构

存储层保留三类对象:原图、派生图和元数据。原图是事实来源,派生图服务不同尺寸、格式和质量需求,元数据记录所有权、审核状态、访问热度和生命周期策略。

这里最容易犯的错,是为了省钱过早删除原图。短期看成本下降了,长期会失去重新生成派生图、修复压缩策略、适配新格式的能力。生产系统通常会保留原图,但根据访问热度迁移到低频或归档存储。

CDN 配置

CDN 层负责就近分发,但它不是一个孤立缓存。缓存键要包含图片版本、尺寸、格式和质量参数;审核状态变化时,要能刷新或失效缓存;对私有图片,不能让 CDN 缓存绕过权限判断。

因此,图片 CDN 的核心不是“接入 CDN 后变快”,而是让缓存规则、图片处理规则和业务状态保持一致。只要其中一个环节脱节,用户就可能看到旧图、禁图或错误尺寸。

成本账本(月度)

生产级图片系统的成本通常来自四部分:对象存储、CDN 流量、图片处理计算和审核服务。早期最贵的是存储,用户量上来后最贵的往往变成 CDN 流量和处理任务。

这套架构的成本控制思路是:

  • 热图留在标准存储和 CDN 边缘,保证访问速度。
  • 冷图迁到低频或归档存储,降低长期占用。
  • 派生图按访问价值保留,没人访问的尺寸不永久存。
  • 审核和处理任务异步化,避免高峰期把计算成本打满。

成本优化不能只看账单数字。删除一张图、降低一次质量、延迟一次处理,都会影响用户体验或恢复能力。真正可持续的优化,是让每一类图片都有清晰生命周期。

演进路线图

阶段 1(第 1~2 周):
  Flask + 本地磁盘 + 无 CDN
  ✅ 能用,但慢

阶段 2(第 3~4 周):
  + 缩略图生成
  + WebP 格式转换
  + 客户端直传 OSS
  ✅ 解决了速度和存储问题

阶段 3(第 2~3 月):
  + CDN 加速
  + 内容审核
  + 异步处理队列
  ✅ 解决了分发和安全问题

阶段 4(第 4~6 月):
  + 存储分层
  + CDN 边缘裁剪
  + 监控告警
  + 成本优化
  ✅ 生产级系统

小结

完整架构不是把所有能力一次性堆上去,而是每次被真实问题推动后,给系统补上一层清晰职责。上传服务解决入口压力,处理队列解决耗时任务,审核服务解决内容风险,CDN 解决分发速度,生命周期策略解决长期成本。

如果你从读者视角复盘这一章,最重要的不是记住每个云产品名字,而是能回答三个问题:图片现在处于什么状态?下一步由哪个模块处理?如果这个模块失败,系统怎么恢复?