完整架构:从 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 监控面板 + 告警
组件详解
上传链路
# 完整上传流程(从用户选择图片到看到缩略图)
UPLOAD_PIPELINE = {
'step_1_client': {
'action': '前端校验文件类型和大小',
'time': '< 100ms',
},
'step_2_credential': {
'action': '从服务器获取 STS 临时凭证',
'time': '~50ms',
},
'step_3_upload': {
'action': '客户端直传 OSS(大文件分片)',
'time': '2~10s(取决于文件大小和网速)',
},
'step_4_callback': {
'action': 'OSS 回调通知上传服务',
'time': '~100ms',
},
'step_5_audit': {
'action': '内容审核(云 API,同步)',
'time': '200ms~1s',
},
'step_6_process': {
'action': '异步生成多尺寸缩略图',
'time': '1~3s(不阻塞用户)',
},
'step_7_cdn': {
'action': '缩略图自动预热到 CDN',
'time': '后台执行',
},
}
# 用户体验:
# - 上传完成后立即返回(step 1~4,约 3~12 秒)
# - 列表页先显示占位图,缩略图就绪后自动替换
# - 从用户角度看,上传后 2~5 秒就能看到缩略图存储架构
# 存储分层策略
STORAGE_LAYERS = {
'热数据(标准存储)': {
'data': '最近 30 天的原图 + 缩略图',
'price': '0.12 元/GB/月',
'access': 'CDN 回源',
'size': '~200 GB',
},
'温数据(低频存储)': {
'data': '30~90 天的原图',
'price': '0.08 元/GB/月',
'access': '需要时恢复',
'size': '~500 GB',
},
'冷数据(归档存储)': {
'data': '90 天以上的原图',
'price': '0.03 元/GB/月',
'access': '解冻后访问(1~5 分钟)',
'size': '~2 TB',
},
}
# 缩略图永远在标准存储(体积小,访问频繁)
# 原图按访问时间自动降级CDN 配置
# 生产级 CDN 配置
CDN_PRODUCTION_CONFIG = {
# 域名
'image_domain': 'img.guangying.com',
# 缓存规则
'cache_rules': [
{'path': 'thumbs/*.webp', 'ttl': 2592000}, # 缩略图 30 天
{'path': 'thumbs/*.avif', 'ttl': 2592000}, # AVIF 缩略图 30 天
{'path': 'originals/*', 'ttl': 7776000}, # 原图 90 天
{'path': 'temp/*', 'ttl': 3600}, # 临时文件 1 小时
],
# URL 参数化实时裁剪
'image_processing': {
'resize': True, # ?w=800&h=600
'quality': True, # ?q=80
'format': True, # ?format=webp
'crop': True, # ?crop=100,100,800,600
},
# HTTPS
'https': {
'enabled': True,
'force': True,
'http2': True,
},
# 防盗链
'referer_whitelist': [
'guangying.com',
'*.guangying.com',
],
# 监控
'metrics': ['hit_rate', 'traffic', 'request_count', 'origin_latency'],
}成本账本(月度)
# 当前规模下的月度成本
MONTHLY_COST = {
'用户数': '12,000',
'总图片数': '85,000',
'日均 PV': '150,000',
'存储费': {
'标准存储': 200 * 0.12, # 200GB × 0.12 = 24 元
'低频存储': 500 * 0.08, # 500GB × 0.08 = 40 元
'归档存储': 2000 * 0.03, # 2TB × 0.03 = 60 元
'小计': 124,
},
'CDN 流量费': {
'CDN 流量': 3000 * 0.24, # 3TB × 0.24 = 720 元
'回源流量': 150 * 0.50, # 150GB × 0.50 = 75 元
'小计': 795,
},
'处理费': {
'图片处理': 200 * 0.0015, # 20万次 × 0.0015 = 0.3 元
'内容审核': 200 * 0.0025, # 20万次 × 0.0025 = 0.5 元
'小计': 1,
},
'服务器': {
'应用服务器': 3 * 200, # 3 台 × 200元/月
'消息队列': 150,
'监控': 50,
'小计': 800,
},
'总计': 1720, # 元/月
}
# 其中 CDN 流量费占 46%,是最大开支
# 服务器占 46%,可以通过 Serverless 进一步降低演进路线图
阶段 1(第 1~2 周):
Flask + 本地磁盘 + 无 CDN
✅ 能用,但慢
阶段 2(第 3~4 周):
+ 缩略图生成
+ WebP 格式转换
+ 客户端直传 OSS
✅ 解决了速度和存储问题
阶段 3(第 2~3 月):
+ CDN 加速
+ 内容审核
+ 异步处理队列
✅ 解决了分发和安全问题
阶段 4(第 4~6 月):
+ 存储分层
+ CDN 边缘裁剪
+ 监控告警
+ 成本优化
✅ 生产级系统本节小结
从一张 8.7 MB 的图片拖垮网站,到支撑 12,000 用户的生产级图片系统——每一步都是在真实问题驱动下的演进。
没有过度设计,只有问题驱动的迭代。
我的思考
思考 1
如果”光影”的用户量增长 100 倍(120 万用户),这个架构的瓶颈在哪里?
参考答案
100 倍增长后的瓶颈分析:
用户数:12,000 → 1,200,000
图片数:85,000 → 8,500,000
日均 PV:150,000 → 15,000,000瓶颈 1:CDN 流量费暴涨
CDN 流量:3TB → 300TB
CDN 费用:720 元 → 72,000 元/月
优化方案:
- 全面推进 AVIF 格式(再减 30% 体积)
- CDN 流量包预付费(折扣约 40%)
- 考虑自建 CDN 节点(核心城市)瓶颈 2:处理服务扛不住
图片处理:20 万次/月 → 2,000 万次/月
单次处理时间:1~3 秒
优化方案:
- 从 4 台扩展到 40 台(弹性伸缩)
- 或迁移到 Serverless(按处理次数计费)
- 利用 CDN 边缘处理,减少中心处理量瓶颈 3:OSS 单 Region 流量
回源流量:150GB → 15TB
跨 Region 用户延迟增大
优化方案:
- 多 Region 部署(北京 + 上海 + 广州)
- 跨区域复制(异步)
- DNS 智能解析到最近的 Region核心原则:100 倍增长意味着每个组件都需要考虑水平扩展。好消息是 OSS 和 CDN 天然支持无限扩展,需要重点优化的是处理服务和多 Region 策略。
