压缩优化

一张 8.7MB 的照片

我记得第一次意识到图片压缩重要性的场景:用户上传了一张 8.7MB 的手机照片,原图直接展示,首屏加载花了 6 秒。

后来我把这张图压缩到 45KB(WebP 格式,质量 80),肉眼几乎看不出区别,加载时间降到了 200ms。体积下降 99.5%,这就是图片压缩的威力。

图片压缩不是在上线前随手调一个质量参数,而是图片系统的核心收益点:它直接影响首屏速度、CDN 带宽、移动端流量、缓存命中率和用户留存。

异步处理链路

上传链路只保证原图安全落盘,压缩链路通过消息队列异步消费:

用户上传 → 对象存储(原图) → 发布上传事件

                          压缩服务消费消息

                    生成多格式、多尺寸派生图

                          写回元数据表 + 通知 CDN 预热

这样设计的好处是:

  • 用户上传不需要等待压缩完成,上传即返回
  • 压缩失败不影响原图存储,可以重试
  • 可以根据图片类型和业务场景选择不同的压缩策略

格式选择

不同格式适合不同场景,不能一刀切:

格式适合场景优势劣势
JPEG照片、复杂色彩兼容性好、体积小不支持透明、有损
PNG截图、图标、透明图无损、支持透明体积大
WebP大多数场景有损/无损、支持透明、体积比 JPEG 小 25-35%老浏览器不支持
AVIF高质量照片体积比 WebP 再小 20%编码慢、兼容性稍差
GIF简单动画兼容性好体积大、仅 256 色

我的策略是:服务端同时生成 WebP 和 AVIF,CDN 根据客户端 Accept 头自动返回最优格式。老浏览器退回 JPEG/PNG。

场景化参数

头像、商品图、长图、动图不能套用同一套参数:

const compressStrategies = {
  avatar: {
    formats: ['webp', 'avif'],
    sizes: [48, 96, 160, 320],
    quality: 80,
    // 头像用圆形裁剪,中心对齐
    crop: { type: 'circle', focus: 'center' }
  },
  product: {
    formats: ['webp', 'avif', 'jpeg'],
    sizes: [200, 400, 800, 1200],
    quality: 85,
    // 商品图保留白底,不裁剪
    crop: { type: 'contain', background: 'white' }
  },
  content: {
    formats: ['webp', 'avif'],
    sizes: ['original', 800, 1200],
    quality: 82,
    // 内容图按比例缩放,不裁剪
    crop: { type: 'scale', fit: 'inside' }
  }
};

处理任务模型

压缩服务必须记录每次处理的元数据,方便回溯和优化:

处理记录 = {
  原图ID, 原始尺寸, 原始格式, 原始体积,
  目标格式, 目标尺寸, 质量因子,
  输出体积, 压缩率, 处理耗时,
  处理状态, 失败原因, 处理时间, 处理节点
}

有了这些数据,就能回答:

  • 哪种格式压缩率最高?
  • 哪个尺寸的派生图访问量最大?
  • 处理延迟的 P99 是多少?
  • 失败率最高的是哪种图片?

降级策略

当压缩失败、格式不被客户端支持、或输出质量低于阈值时,系统要能降级,而不是让图片请求直接失败:

  1. 压缩失败 → 标记派生图不可用,CDN 回源时返回原图
  2. 客户端不支持 WebP → CDN 根据 Accept 头自动返回 JPEG
  3. 输出质量低于阈值 → 自动提高质量参数重新处理
  4. 处理服务过载 → 暂停非热门尺寸的生成,优先保证热门尺寸

验收标准

完成本章后,你应当能够回答三个工程问题:

  1. 如何为不同业务场景选择压缩策略? —— 根据图片类型、使用场景、终端设备选择格式、尺寸和质量
  2. 如何设计可扩展的图片处理任务模型? —— 异步消费、可重试、可观测、可降级
  3. 如何判断压缩系统是否真的有效? —— 体积下降率、清晰度抽检、处理延迟 P99、失败率

学习时可以把压缩链路当成一个独立子系统来验收:输入必须可追溯,输出必须可复用,策略必须可配置,异常必须可恢复。

章节