导航菜单

技术方案全景

一张图片的生命周期

做完缩略图方案后,我在白板上画了一张图——一张图片从上传到用户看到,到底要经历哪些环节?

最简实现: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 监控面板 + 告警

六个环节,每一个都有技术挑战。我需要为每个环节选择合适的技术方案。

我整理了一份技术选型清单

上传方案

方案 A:直传服务器(当前方案)
  客户端 → 我的服务器 → 对象存储

方案 B:客户端直传对象存储
  客户端 → 对象存储(服务器只签发上传凭证)

方案 C:分片上传 + 断点续传
  客户端 → 分片 → 对象存储(适合大文件)

审核方案

方案 A:自研审核模型
  自己训练 NSFW 检测模型,部署推理服务

方案 B:云服务 API
  调用阿里云/腾讯云的内容安全 API

方案 C:开源模型
  使用 NSFW.js / open_nsfw 等开源方案

处理方案

方案 A:上传时处理(同步)
  用户上传后立即生成所有尺寸

方案 B:上传后异步处理
  上传后丢到消息队列,异步处理

方案 C:CDN 边缘处理
  不预生成,用户请求时在 CDN 边缘节点按需裁剪

存储方案

方案 A:服务器本地磁盘(当前方案)
  简单但不可靠,无法扩展

方案 B:对象存储(S3/OSS/COS)
  可靠、可扩展、按量付费

方案 C:对象存储 + 分层
  热数据标准存储,冷数据低频/归档存储

分发方案

方案 A:源站直出(当前方案)
  所有用户直接访问源站

方案 B:全站 CDN
  所有图片通过 CDN 分发

方案 C:CDN + 源站回源
  CDN 缓存未命中时回源站拉取

我的选择

基于当前的业务规模(用户数千人,日新增图片几百张),我做了第一版技术选型:

环节选择理由
上传客户端直传 OSS减轻服务器压力,支持大文件
审核云服务 API快速上线,准确率高
处理异步处理 + CDN 边缘处理平衡实时性和成本
存储阿里云 OSS + 分层可靠,可扩展
分发CDN + 回源就近访问,降低延迟

整体架构

最简实现: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 监控面板 + 告警

这个架构看起来很美好。但我知道,真正的挑战在于每个环节的细节实现

接下来,我要从最基础的开始——理解图片本身。

我的思考

思考 1

为什么我选择”客户端直传 OSS”而不是”服务器中转”?两种方案各有什么优缺点?

参考答案

服务器中转方案

客户端 → 我的服务器 → OSS

优点:
- 服务器可以对文件做预处理(校验、压缩)
- 可以控制上传权限,更安全
- 上传逻辑集中管理

缺点:
- 服务器带宽是瓶颈(8.7MB 的图片 × 并发上传 = 带宽爆炸)
- 服务器磁盘 I/O 压力大
- 单点故障——服务器挂了就全挂了
- 服务器成本高(需要更大的带宽和磁盘)

客户端直传方案

客户端 → OSS(服务器只签发上传凭证)

优点:
- 上传流量不经过服务器,节省带宽
- OSS 的上传能力远强于单台服务器(支持分片、断点续传)
- 服务器只做轻量操作(签发凭证),不容易成为瓶颈

缺点:
- 需要设计安全的凭证机制,防止滥用
- 客户端需要集成 OSS SDK
- 服务器无法在上传时预处理

典型实现:

```python
import oss2
import time

def generate_upload_credentials(user_id, file_type):
    """生成临时上传凭证"""
    auth = oss2.StsAuth(
        access_key_id='your_key',
        access_key_secret='your_secret',
        security_token=sts_token
    )
    
    # 生成带签名的上传 URL,15 分钟有效
    bucket = oss2.Bucket(auth, 'oss-cn-beijing.aliyuncs.com', 'my-bucket')
    
    object_key = f'uploads/{user_id}/{uuid.uuid4().hex}.{file_type}'
    
    upload_url = bucket.sign_url('PUT', object_key, 15 * 60)
    
    return {
        'upload_url': upload_url,
        'object_key': object_key,
        'expires_in': 900,
    }

对于图片场景,客户端直传几乎总是更好的选择。 图片文件大,走服务器中转纯属浪费带宽。

思考 2

架构图中,审核和处理是并行还是串行?如果审核发现图片违规,已经处理好的缩略图怎么办?

参考答案

我的方案:先审后处理

上传 → 审核 → (通过) → 异步处理 → 存储 → CDN
              → (拒绝) → 删除原图 → 通知用户

原因:

  1. 避免资源浪费:如果先处理再审核,违规图片的处理算力白花了。处理 5 种缩略图需要 1~2 秒,如果最终是违规图片,这些算力就浪费了。

  2. 避免”违规内容短暂可访问”的问题:如果先处理再审核,在审核完成前,违规图片的缩略图可能已经被 CDN 缓存了。

  3. 审核本身很快:云 API 的审核通常在 200ms~1s 内返回结果,不会成为上传流程的瓶颈。

审核通过后的处理流程

def on_upload_complete(object_key, user_id):
    """图片上传完成后的处理流程"""
    
    # 第一步:审核(同步,快速判断)
    audit_result = content_audit(object_key)
    
    if not audit_result.passed:
        # 审核不通过:删除原图,通知用户
        oss_client.delete_object(object_key)
        notify_user(user_id, "图片内容不符合规范,请修改后重新上传")
        return
    
    # 第二步:审核通过,异步处理
    message_queue.publish({
        'task': 'process_image',
        'object_key': object_key,
        'user_id': user_id,
    })
    
    # 同时更新数据库状态
    db.execute(
        "UPDATE images SET status = 'processing' WHERE object_key = %s",
        (object_key,)
    )


def process_image_async(object_key):
    """异步处理图片"""
    # 下载原图
    image_data = oss_client.get_object(object_key)
    
    # 生成多种尺寸
    thumbnails = generate_thumbnails(image_data)
    
    # 上传缩略图到 OSS
    for size_name, thumb_data in thumbnails.items():
        thumb_key = f'thumbs/{object_key}_{size_name}.webp'
        oss_client.put_object(thumb_key, thumb_data)
    
    # 更新数据库状态
    db.execute(
        "UPDATE images SET status = 'ready', thumbs = %s WHERE object_key = %s",
        (json.dumps(thumbnails), object_key)
    )

极端情况:如果审核漏放了一张违规图片怎么办?

  • CDN 层面:支持紧急刷新(purge),可以快速从 CDN 节点删除
  • OSS 层面:直接删除源文件,CDN 缓存过期后自动不可访问
  • 数据库层面:标记为违规,前端不再展示

搜索