核心需求
在设计图片存储和 CDN 加速系统时,我们需要平衡三个核心需求:加载速度、图片质量和存储成本。这三个目标往往相互制约,需要根据具体业务场景找到最佳平衡点。
加载速度
/\
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/____________________\
图片质量 ←----------→ 存储成本
不可能三角:在资源有限的情况下,很难同时达到最优的加载速度、最高的图片质量和最低的存储成本。
加载速度是用户体验的核心指标,直接影响页面加载时间、用户留存率和转化率。
1.1 关键指标
图片系统的”快”不能概括成”服务器响应多快”。用户真正关心的是这张图什么时候完整出现在屏幕上,它由首字节延迟、图片体积、缓存命中率和设备渲染能力共同决定。我把指标分成三层来盯:
| 层次 | 指标 | 定义 | 数据来源 | 为什么重要 |
|---|---|---|---|---|
| 用户感知 | LCP | 页面最大内容元素(通常是首屏大图)完成渲染的时刻 | 真实用户监控 / PerformanceObserver | 一号指标,直接对应”用户等多久看到图” |
| 用户感知 | TTFB | 收到首个响应字节的时间 | 边缘访问日志 | 反映调度与回源链路,不含图片传输本身 |
| 用户感知 | 首屏图片字节数 | 首屏可见图片的总体积 | 真实用户监控的传输资源汇总 | 比单张体积更接近真实体验,也是流量账单的分子 |
| 传输效率 | 边缘命中率 | 边缘节点直接返回的请求数 / 总请求数 | CDN 访问日志 | 决定回源量和首字节延迟,是成本的分水岭 |
| 传输效率 | 回源率 | 回源请求数 / 总请求数 | CDN 回源日志 | 命中率每降一个百分点,回源带宽和源站压力同步抬升 |
| 传输效率 | 图片平均体积 | 每次成功返回的字节数 | 边缘日志按 URL 维度聚合 | 体积是带宽费用和移动端流量的直接乘数 |
| 内容质量 | 格式分布 | WebP / AVIF / JPEG 各自的请求占比 | 边缘日志按 Content-Type 聚合 | 现代格式占比过低,说明格式转换没有覆盖到位 |
| 成本 | 单位展示成本 | 本月 CDN + 存储 + 回源费用 / 本月展示次数 | 云厂商账单 + 业务埋点 | 把技术指标翻译成业务能讨论的单位成本 |
三个使用上的注意事项:
- 看分位数,不看均值。图片场景里 P99 往往对应”一张从没被访问过的大图触发了回源”,均值会把这个尖刺抹平。
- 按维度下钻。同一个命中率,全国平均值可能很好看,但某个省份或某家运营商的边缘节点缺失会被完全掩盖。至少要按地域、运营商、设备类型分组看。
- 指标必须能对应到动作。如果一个指标涨了却没人知道该改什么(改格式、改 TTL,还是补节点覆盖),它就不该出现在面板上。
1.2 影响加载速度的因素
1.2.1 网络传输延迟
用户请求 → DNS 解析 → TCP 握手 → TLS 握手 → 服务器响应 → 数据传输 → 渲染
│ │ │ │ │ │ │
~10ms ~20-100ms ~20ms ~50ms ~50-200ms 可变 ~16ms
优化策略:
- 使用 CDN 就近访问,减少网络延迟
- DNS 预解析和预连接
- HTTP/2 或 HTTP/3 多路复用
- TCP 连接复用
1.2.2 图片文件大小
| 图片类型 | 推荐大小 | 加载时间(5Mbps) |
|---|---|---|
| 缩略图 | 10-30 KB | 16-48 ms |
| 头像 | 20-50 KB | 32-80 ms |
| 文章配图 | 100-300 KB | 160-480 ms |
| 全屏 Banner | 200-500 KB | 320-800 ms |
| 高清原图 | 500-2000 KB | 800-3200 ms |
1.2.3 CDN 节点覆盖
CDN 加速的前提是”用户附近有节点”。节点覆盖不是厂商宣传的节点总数,而是你的用户分布与边缘节点分布的重合度。
| 评估角度 | 看什么 | 为什么重要 |
|---|---|---|
| 覆盖广度 | 有可用节点的省份 / 城市,占用户所在省份 / 城市的比例 | 决定多少用户必须走回源路径 |
| 覆盖深度 | 用户到最近边缘节点的距离与跨网 RTT | 决定命中时的首字节延迟上限 |
| 运营商覆盖 | 电信 / 联通 / 移动各自是否有可用节点 | 跨运营商互联常常是延迟毛刺的来源 |
| 回源距离 | 边缘节点回源到源站的平均距离 | 决定未命中时的总延迟 |
| 边缘命中率分布 | 按地域拆分后的命中率 | 平均值会掩盖局部覆盖缺口 |
三个常见的坑:
- 节点多不等于覆盖好。厂商的节点数常按”部署机房”统计,用户实际落到的是这些机房里的某台机器,中间还隔着骨干网和城域网。
- 跨运营商互联不可控。同城电信用户命中了电信节点,但回源时源站在联通机房,这一段仍是跨网,延迟通常明显高于同网。
- 覆盖不足时用户仍然能拿到图,只是慢。所以它不会立刻报错,只表现为某些地区的回源率偏高、源站压力不均。这类问题必须靠按地域拆分指标才能发现。
因此验证节点覆盖不能只看”有没有接入 CDN”,而应该按地域拆开看回源率:如果某个地区的回源率明显高于全国均值,先怀疑覆盖,再怀疑缓存键设计。反过来,如果回源率在地域上分布均匀,说明瓶颈多半不在覆盖,而在缓存策略本身。
1.3 速度优化技术
1.3.1 图片格式优化
格式优化的收益是一次编码、长期兑现:体积降下来之后,CDN 流量费、边缘存储占用、回源带宽、用户手机流量同时受益。
| 格式 | 压缩方式 | 透明 | 动画 | 相对 JPEG 体积 | 典型用途 |
|---|---|---|---|---|---|
| JPEG | 有损 | ❌ | ❌ | 基准 | 兜底格式、原图 |
| PNG | 无损 | ✅ | ❌ | 通常更大 | 截图、图标、透明图 |
| WebP | 有损 / 无损 | ✅ | ✅ | 小约 30% | 主力展示格式 |
| AVIF | 有损 / 无损 | ✅ | ✅ | 比 WebP 再小约 20% | 增强格式,按浏览器能力渐进下发 |
格式不是越多越好,而是越多越贵。每多一种格式,就多一份存储、多一份处理计算、多一份派生图治理成本。所以策略应该是”一个主力格式 + 一个增强格式 + 一个兜底格式”,而不是把所有格式都生成一遍。
转换时机决定了体验和成本:
- 上传时同步转换:用户要等编码结束,AVIF 这类编码慢的格式尤其明显;好处是派生图在第一次访问前就绪。
- 上传后异步转换:上传立刻返回,后台队列生成派生图。这是主流做法,代价是短时间内可能拿到尚未就绪的尺寸,需要前端兜底。
- 边缘按需转换:不预生成,用户请求时由边缘节点实时转换。存储最省,但首次访问有额外延迟,且边缘资源有限。
我的选择是:WebP 异步预生成(主力)、AVIF 异步预生成(增强)、JPEG 兜底,长尾尺寸交给边缘按需处理。
格式协商发生在边缘:浏览器在请求头里声明支持哪些格式,CDN 按 Accept 头返回对应版本。协商的前提是同一张图的不同格式版本在缓存里是不同对象——如果缓存键没带上格式,一个用户拿到的 JPEG 会被缓存下来发给所有用户,这是一类很隐蔽的缓存污染。
1.3.2 响应式图片
同一张原图,在手机上只需要 400px 宽,在高像素屏上可能要 2000px 宽。响应式图片就是让浏览器自己挑合适的那一版。
机制由三个属性配合完成:
| 属性 | 作用 | 由谁决定 |
|---|---|---|
srcset | 列出候选图片及各自的真实宽度(带 w 描述符) | 服务端提供候选集 |
sizes | 告诉浏览器”这个图在当前布局下会占多少 CSS 像素” | 前端按 CSS 断点写死 |
width / height | 声明固有尺寸,浏览器提前预留位置 | 服务端或前端按原图比例给出 |
浏览器选图的过程是:先用 sizes 和视口宽度算出这张图需要多少 CSS 像素,再乘以设备像素比,最后从 srcset 里挑出不小于这个需求、且最接近的一版下载。
<img
src="https://cdn.example.com/i/a3f8c2e1_800.webp"
srcset="https://cdn.example.com/i/a3f8c2e1_400.webp 400w,
https://cdn.example.com/i/a3f8c2e1_800.webp 800w,
https://cdn.example.com/i/a3f8c2e1_1200.webp 1200w,
https://cdn.example.com/i/a3f8c2e1_2000.webp 2000w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 800px"
width="800"
height="533"
alt="夜景"
/>
候选尺寸由谁生成,有两条路线:
- 服务端预生成:在固定尺寸白名单(如 400 / 800 / 1200 / 2000)上批量产出派生图。命中率高、首次访问快,代价是存储和无效派生图。
- 边缘按需生成:候选集可以更细,用户请求什么尺寸就生成什么尺寸。存储最省,代价是首次访问有延迟,且必须限制参数空间,否则恶意请求会造出海量变体。
两条路线可以叠加:白名单里的热门尺寸预生成并预热,白名单之外的长尾尺寸走边缘按需。
最容易踩的坑是 sizes 与 CSS 布局不一致。如果 sizes 写的是 50vw,实际布局里图片却被限制成 300px 宽,浏览器会按 50vw 去买更大的图——用户拿到的是一张用不上的大图,钱花在流量上,收益却在画质上。
1.3.3 懒加载(Lazy Loading)
首屏之外的大图如果立刻开始下载,会挤占带宽,让首屏真正重要的那张图变慢。懒加载就是让浏览器推迟加载视口之外的图片。
原生实现的判定机制:
浏览器决定是否加载
│
├─ 图片距离视口的距离在阈值内 → 立即加载
│ 阈值受"有效连接类型"影响:
│ 慢网络 → 拉得更远(提前加载,避免滚到时才发现没网)
│ 快网络 → 拉得更近(不浪费流量)
│
└─ 图片在阈值之外 → 暂不下载,滚动接近时再触发
关键点:
loading="lazy"只对”尚未进入视口附近”的图片有意义。首屏图片加上 lazy,等于让 LCP 元素主动推迟自己的加载,是负优化。- 懒加载不会让图片消失。触发加载后依然要走完整的 DNS + 连接 + 传输流程,只是把发起时刻推迟了。
- 降级方案不可省。不支持原生 lazy 的浏览器需要在
onload里再赋src,这时要注意别让浏览器把原src当成”真实地址”缓存下来。 - 占位图要先画出来。用 CSS 给图片容器预留宽高比,避免懒加载生效后发生布局抖动。
首屏图片的正确做法是:不写 lazy,用 fetchpriority="high" 提优先级,其余图片统一 loading="lazy" + decoding="async",让解码不阻塞主线程。
图片质量直接影响用户的视觉体验和产品形象,需要在清晰度和文件大小之间找到平衡。
2.1 质量评估指标
“看起来还行”不能作为验收标准。要判断一张压缩后的图能不能上线,得先有一个可量化的口径。
| 指标 | 含义 | 取值 | 优点 | 局限 | 适用场景 |
|---|---|---|---|---|---|
| 主观 MOS | 多人按 5 级尺度打分取平均 | 1~5 | 直接反映人眼感受 | 成本高、慢、难以自动化 | 验收抽检、版本对比 |
| SSIM | 结构相似性,比较亮度、对比度、结构三项 | 0~1 | 无需训练、易复现 | 对亮度偏移敏感,纹理丰富时偏低 | 自动化质量门槛 |
| PSNR | 峰值信噪比 | 通常大于 0 dB | 经典、工具链完善 | 高质量区间区分度差 | 编码器回归测试 |
| LPIPS | 深度特征距离 | 越小越相似 | 更接近人眼判断 | 需要模型,计算重 | 主观质量的自动近似 |
| Butteraugli | 基于心理视觉模型 | 越小越相似 | 对色偏和局部伪影敏感 | 参数不易解释 | 高端图像的细看 |
| 像素差 | 逐像素绝对差 | 越小越好 | 最直接 | 对轻微色偏过于敏感,容易误判 | 验证”是否真无损” |
实践中的三条经验:
- 单一指标不够。SSIM 高的图可能存在色带,PSNR 低的图可能只是整体亮度偏了一点。要按内容类型组合使用——照片看 SSIM + 人工抽检,UI 截图重点看文字区域的清晰度。
- 阈值要按内容类型分开定。同一张夜景照和同一张白底产品图,在同一个质量参数下的 SSIM 差异很大,用一个全局阈值一定会误伤其中一类。
- 把”是否无损”和”是否够用”分开验。PNG 转无损 WebP 要求像素完全一致,用像素差判定;而展示图只要达到目标 SSIM 就够了,剩下的是体积的账。
2.2 质量与大小的权衡
图片体积可以用一个粗略模型理解:
其中 和 是输出分辨率, 是每像素比特数, 是质量因子, 是内容复杂度(纹理、噪点越多,同样的质量下每像素比特数越高)。
这个模型给出三个可以直接用起来的判断:
- 降分辨率是线性降本。宽高各减半,体积大致降到四分之一。
- 降质量因子是边际递减。质量从 80 提到 90,体积涨得比想象中快,而主观差异越来越难察觉。
- 内容复杂度决定曲线位置。同一张图,噪点多的夜景比平滑的风景更”费比特”,两者不能共用一套参数。
于是权衡的实践形态是先选分辨率,再选质量:
| 决策顺序 | 做什么 | 理由 |
|---|---|---|
| 1 | 按展示位置确定目标 CSS 宽度 | 超出实际显示尺寸的分辨率纯属浪费 |
| 2 | 按设备像素比放大到候选宽度 | 高 DPI 屏需要更多像素,但只在用户看得清的范围内放大 |
| 3 | 在该分辨率下选最低但够用的质量因子 | 质量因子是连续可调的旋钮,用来榨出最后一点空间 |
| 4 | 复核目标 SSIM 是否达标 | 达标即停,不再往上加质量 |
常见的一个反直觉结论:把质量因子从 80 降到 70,省下的体积通常不如把宽度从 1200 降到 800。因为宽度是线性项,而质量因子的收益递减。
2.3 智能质量调整
固定质量参数意味着”所有用户、所有场景、所有网络”拿到同一张图。智能质量调整是把这些差异显式建模,在几个可调的档位之间选择。
决策输入(信号)与对应动作:
| 信号 | 从哪来 | 调整动作 | 边界条件 |
|---|---|---|---|
| 有效连接类型 | 浏览器 navigator.connection | 慢网降一档、极慢网降两档 | 只在客户端有效,CDN 看不到 |
| 实时带宽 | 客户端测速或真实用户监控反馈 | 连续多次低于阈值才降档 | 单次波动不能触发降档 |
| 设备像素比 | window.devicePixelRatio | 决定分辨率倍率 | 高 DPI 屏可以放大,但要有上限 |
| 设备算力 | UA 粗判或客户端上报 | 低端机优先下发小尺寸 | 上报不可信时只能当弱信号 |
| 页面性质 | 前端业务标记 | 首屏 / 详情 / 列表分别设基线 | 由业务决定,不由网络决定 |
| 用户档位 | 登录态 | 付费用户优先保画质 | 不能被弱网覆盖 |
| 缓存状态 | 是否已在本地缓存 | 已缓存则不降质 | 降质对已缓存对象无效 |
判定顺序很重要:先由业务和用户档位定”画质下限”,再由网络和设备在这个下限之上做降级,最后才轮到网络。这样可以避免弱网把付费用户的作品压成糊图。
三档位策略长这样:
档位 分辨率上限 质量因子 格式 适用
─────────────────────────────────────────────
high 1600px q80+ AVIF/WebP 付费用户、详情页、良好网络
medium 900px q75 WebP 默认档
low 480px q65 WebP/JPEG 弱网、列表页预览
降质要设下限:再差的网络也不应该给用户一张完全糊掉的图,保留可辨识度比省流量更重要。另一个更稳妥的做法是先给低质、再升级:先返回 480px 版本让页面成形,检测到网络良好后再请求高质版本替换,用户感知是”图慢慢变清晰”,而不是”一直糊”。
最后一个提醒:动态调整会让同一个 URL 在不同用户那里对应不同画质。如果 CDN 按 URL 缓存,同一个 URL 只会有一份缓存,动态调整必须落在 URL 上(不同档位不同 URL)或者干脆由前端选择 URL,否则动态调整就失效了。
2.4 质量监控
质量是唯一不能只靠上线前抽检的维度:压缩策略会升级、设备会变化、内容类型分布也会变。质量监控要回答两个问题:现在的产出是否仍然达标,以及用户是否在投诉。
| 监控项 | 采集方式 | 频率 | 异常信号 | 处置动作 |
|---|---|---|---|---|
| 派生图 SSIM 分布 | 处理服务按比例抽样对比原图 | 每次处理 + 定时汇总 | 低于目标阈值的比例上升 | 定位是哪类内容、哪档参数导致 |
| 平均体积分布 | 处理记录聚合 | 每小时 | 同一场景体积突然抬升 | 查参数变更或编码器版本 |
| 质量档位分布 | 处理记录聚合 | 每小时 | 档位集中在一个极端值 | 检查策略是否退化成固定参数 |
| 格式分布 | CDN 日志按 Content-Type 聚合 | 每天 | AVIF / WebP 占比异常下跌 | 查协商逻辑和缓存污染 |
| 压缩失败率 | 队列与处理服务日志 | 实时告警 | 失败率超过阈值 | 熔断并降级到原图 |
| 用户反馈 | 站内”图片模糊”举报、客服工单 | 每天 | 同一批用户集中反馈 | 反查该批图片的 SSIM 与档位 |
| 人工抽检 | 定期人工对比原图与展示图 | 每周 | 抽检不通过 | 调整质量因子或内容分类规则 |
两个容易忽略的要点:
- 抽样要按内容类型分层。整体抽样时,自然图像会淹没 UI 截图这类小众但敏感的类型。抽样比例要保证每种内容类型都有足够样本,而不是靠总量稀释。
- 监控口径要和验收口径一致。开发时用某个 SSIM 阈值验收,线上监控也必须盯同一个阈值;换了口径,指标就失去了对照意义。
质量监控真正的闭环是:处理时记录 → 抽样计算 → 发现偏差 → 调整参数 → 灰度 → 复验。只有”记录”没有”调整”的监控,几个月后只会变成没人看的图表。
存储成本是图片系统运营的主要开支,需要精细管理和优化。
3.1 成本构成
图片系统的账单不是一个总数,而是若干个独立计费项叠出来的。把它们拆开,才能知道该往哪使劲。
| 成本项 | 计费维度 | 主要驱动因素 | 优化手段 |
|---|---|---|---|
| 对象存储 | 按容量 GB·月 | 原图体积、派生图数量与体积 | 体积压缩、分层、清理无用派生图 |
| 存储取回 | 按取回量 | 归档层被读取的次数 | 用 CDN 缓存吸收取回、避免反复穿透 |
| CDN 下行流量 | 按出网流量 | 展示次数 × 平均体积 | 格式转换、响应式、缓存命中 |
| CDN 请求数 | 按请求次数 | 页面曝光量、图片数量 | 合并请求、减少图片数量、懒加载 |
| 回源流量 | 按回源出网流量 | 未命中请求 × 体积 | 提高命中率、延长 TTL |
| 图片处理 | 按处理次数或计算时长 | 派生图数量、编码格式 | 限制参数空间、异步批处理、复用结果 |
| 审核调用 | 按调用次数 | 上传量、审核轮次 | 低清图审核、规则前置过滤 |
| 业务服务器 | 按实例规格与时长 | 图片处理与审核的 CPU 消耗 | 异步化、弹性伸缩、热点尺寸预生成 |
两个反直觉的点:
- 请求数可能比流量更贵。在流量单价下降或图片都很小的场景里,海量小请求的费用会盖过流量本身。列表页一次加载 20 张图,就是 20 次请求。
- 存储看着便宜,但它是”复利”支出。原图永不删除、派生图无限增长,容量只会单向上升;而流量费是每天重新结算的。长期看,存储和生命周期管理决定成本曲线是否失控。
3.2 成本估算模型
有了成本构成,就可以写一个模型来做”改一个参数,账单会怎么变”的推演。
月度总成本:
其中流量相关的两项最值得单独拆出来。设 为月请求数, 为边缘命中率, 为平均单图体积, 与 分别为 CDN 出网和回源的单价:
存储项则由分层决定,设 为第 类对象的容量, 为对应存储类型的单价, 为计费月数:
这个模型最有价值的地方在于它暴露了三个弹性完全不同的杠杆:
| 杠杆 | 影响的成本项 | 弹性 | 说明 |
|---|---|---|---|
| 降低平均体积 | CDN 流量、回源流量、存储 | 线性,且同时命中多项 | 格式转换和响应式是最划算的优化 |
| 提高命中率 | 回源流量 | 只影响未命中的那一部分 | 命中率已经很高时,边际收益很小 |
| 降低容量 | 存储 | 线性 | 靠分层和生命周期,而不是靠删除原图 |
几个实用推论:
- 体积是第一杠杆。体积减半,CDN 流量和回源流量同时减半;命中率从九成提到九成六,回源流量只减少六成左右,而命中率越往上越难提。
- 模型里的单价必须来自配置而不是硬编码。云厂商的单价会调整,把单价做成可替换的输入,模型才能长期使用。
- 模型是用来做敏感度分析的。把单价、请求量、体积做成变量,输出”哪个变量动一下省得最多”,比月底看账单再猜要高效得多。
实践上建议把模型做成一张可维护的表:输入假设(请求量、平均体积、命中率、容量分布),输出预估月成本,再和实际账单对比,偏差超出容忍范围时说明模型或实现有问题。
3.3 成本优化策略
3.3.1 存储分层
分层的本质是:用”访问代价”换”存储单价”。热数据留在贵的存储里,冷数据挪到便宜的存储里。
| 存储类型 | 典型用途 | 读取延迟 | 关键代价 |
|---|---|---|---|
| 标准 | 热数据、近期访问 | 毫秒级 | 单价最高 |
| 低频 | 温数据、偶尔访问 | 毫秒级 | 有最短存储时长,不足时长按满周期计费;取回另计费 |
| 归档 | 冷数据、合规留存 | 分钟级,需先解冻 | 解冻有等待期;未解冻前直接访问会失败 |
| 冷归档 | 长期留存 | 小时级,需先解冻 | 同上,解冻等待更久 |
划分依据不是单纯的时间,而是三个信号叠加:
- 访问频次:最近一段时间的真实访问量,这是最客观的信号。
- 业务语义:头像不能因为没人访问就归档,付费作品不能因为超过天数就降级;用户主动删除的图片要立刻进删除流程,而不是归档。
- 可重建性:派生图可以随时重新生成,所以先清理、再降级;原图是事实来源,只能降级,不能随便删。
三个必须注意的坑:
- 分层迁移本身要花钱。大批量迁移会产生请求费用和一次性的操作开销,迁移频率不宜太高。
- 归档后不是”能读”,而是”要先解冻”。任何取回路径都要处理”对象处于归档状态”的分支,否则会变成一串 403。
- 最小存储时长会让”频繁进出”变贵。把对象在低频和标准之间来回搬,看起来省钱,实际可能因为计费周期取整而更贵。
3.3.2 图片生命周期管理
分层解决的是”存得便宜”,生命周期解决的是”该消失的会消失”。
一个图片对象的生命周期可以分为四个阶段:
| 阶段 | 进入条件 | 该做什么 | 依据 |
|---|---|---|---|
| 临时态 | 分片上传开始 | 保留分片,定期清理未完成的残留 | 一次上传失败不该留下永久垃圾 |
| 有效态 | 审核通过 | 生成派生图,进入分发链路 | 业务可见的前提 |
| 冷却态 | 长期无访问 | 原图降级到低频或归档,派生图按需保留 | 成本治理 |
| 终止态 | 用户删除 / 业务下线 / 合规要求 | 先软删除保留可恢复窗口,再物理删除 | 可恢复性与合规 |
配套的规则要落到三类对象上:
| 对象 | 规则方向 | 注意事项 |
|---|---|---|
| 原图 | 长期保留,按热度分层 | 不做物理删除,除非业务或合规明确要求 |
| 派生图 | 长期无访问即可清理 | 清理前确认能按当前策略重新生成 |
| 临时分片 | 短周期强制清理 | 清理任务要和上传流程解耦,独立运行 |
生命周期规则必须绑定业务语义,不能只按文件年龄粗暴处理。同样是一张很久没访问的图片,头像、营销素材、付费作品、已下架商品图的处理完全不同。规则引擎要能读到这些语义,生命周期才不是”定时炸弹”。
另外两层容易被忽略的工作:
- 可恢复窗口:物理删除前先标记为软删除,保留一段可恢复时间。这既防误删,也给审核召回留了后路。
- 删除要联动清理:删图片不只是删一个对象,还要清掉它的派生图、CDN 缓存和元数据。CDN 缓存不清理的话,即使源文件没了,边缘仍可能在缓存期内继续对外提供。
3.3.3 CDN 缓存优化
缓存优化有三件事要同时做对:缓存键稳定、TTL 分级、刷新与预热有章法。
缓存键决定”什么算同一张图”。参与缓存键的应该只有影响图片内容的参数:
缓存键 = 对象路径 + 尺寸 + 格式 + 质量 + 裁剪方式 + 版本号
不参与缓存键:签名、时间戳、追踪参数、随机数
这些参数在边缘节点校验,而不是用来区分缓存对象
把时间戳放进缓存键是最常见的错误——每次请求都是一个新对象,命中率会掉到接近零。
TTL 分级让不同热度的图片用不同的过期策略:
| 图片类型 | TTL | 理由 |
|---|---|---|
| 热门推荐图 | 长 | 几乎不变,长 TTL 能把回源压到最低 |
| 一般内容图 | 中 | 内容会更新,但不需要频繁回源 |
| 头像、小尺寸缩略图 | 中偏长 | 体积小,变化少,回源收益大 |
| 用户可编辑的图 | 短 | 配合版本号与主动刷新,保证尽快生效 |
配合 stale-while-revalidate,可以在缓存过期后先返回旧内容、后台异步更新,用户几乎无感。
刷新解决”必须立刻生效”的场景:审核召回、违规下架、内容被替换。这类场景靠 URL 刷新或目录刷新,把边缘缓存主动清掉,同时保留一个版本号用于整目录切换。刷新是有成本的(逐节点下发),所以只在必要时用。
预热解决”热点来得比缓存填充更快”的场景:运营活动、首页推荐位、突发热点。提前把指定的 URL 推送到边缘节点,避免第一批用户集体触发回源。
最后是污染与排查:
- 用参数白名单限制允许的尺寸和格式组合,防止恶意请求造出海量变体把边缘缓存撑爆。
- 排查”用户看到旧图”时,先确认缓存键是否包含版本号,再看刷新是否覆盖了全部节点,最后才怀疑源站。
- 命中率要按地域、格式、URL 类型分组看。整体命中率被热门 URL 拉高,长尾 URL 的命中率崩塌往往看不出来。
4.1 场景化配置
同一套压缩参数套所有场景,是图片系统最常见的浪费。场景化配置的意思是:按图片在业务里的角色,规定它该长什么样。
| 场景 | 尺寸档 | 格式 | 质量 | 缓存 TTL | 说明 |
|---|---|---|---|---|---|
| 列表缩略图 | 200 / 320 | WebP | 中 | 长 | 只求辨识度,体积最小化 |
| 头像 | 96 / 192 / 384 | WebP + JPEG 兜底 | 中偏高 | 长 | 圆形裁剪,头像不能糊 |
| 封面 / 推荐位 | 800 / 1200 | WebP / AVIF | 中偏高 | 中长 | 首屏可见,质量优先 |
| 详情大图 | 原始宽度 / 1600 | WebP / AVIF | 高 | 中 | 用户会细看,必要时可放大 |
| 下载原图 | 原始 | 原格式 | 不压缩 | 短 | 原图保护 + 鉴权访问 |
| 后台预览 | 512 | JPEG | 中 | 短 | 内部使用,够看即可 |
配置要落在一份可版本化的规则表里,而不是散落在各个业务代码中:
场景规则 = {
场景标识, 允许的尺寸列表, 允许的格式列表,
质量因子, 裁剪方式, 是否允许放大, 缓存 TTL
}
三个配套要求:
- 规则要能约束请求,而不只是指导生成。CDN 回源时要按白名单校验参数,非法参数直接拒绝,否则”场景化配置”会被恶意请求绕过去。
- 规则变更要走灰度。把规则做成版本号,新旧版本共存一段时间,按比例或按图片 ID 分组切换,出问题能一键回退。
- 场景要跟业务归属绑定。图片在元数据里就记录了它的业务类型,处理时直接读,不需要前端传参——前端传的场景值是不可信的。
4.2 动态调整策略
场景化配置是静态的,动态调整是运行时的。两者结合,才是完整的平衡策略。
调整发生在三个不同的时间尺度上:
| 时间尺度 | 谁来做 | 依据 | 例子 |
|---|---|---|---|
| 构建 / 发布期 | 规则表 | 业务场景与内容分布 | 调整某场景的质量档位与尺寸白名单 |
| 请求期 | CDN 边缘 | 客户端能力、边缘节点配置 | 按 Accept 协商格式,按地域选回源节点 |
| 会话期 | 前端 | 网络状况、滚动行为 | 弱网先出小图,滚动到再加载大图 |
触发调整的信号与护栏:
| 信号 | 调整动作 | 护栏 | 观测指标 |
|---|---|---|---|
| 网络类型判定为慢网 | 降一档画质与尺寸 | 不低于 low 档;连续两次判定才降 | 弱网用户的 LCP、跳出率 |
| 边缘命中率下降 | 延长 TTL / 预热热点 | 不超过业务允许的最长 TTL | 命中率、回源流量 |
| 体积分布异常 | 调整质量因子 | 抽样 SSIM 不低于目标阈值 | 平均体积、SSIM 分布 |
| 成本超预算 | 收紧派生图生成 | 不影响已有页面的展示规格 | 单位展示成本 |
| 合规要求变化 | 强制刷新与下架 | 保留审计记录 | 拦截生效时延 |
三条必须坚持的原则:
- 调整要可逆。每次变更都有版本号、灰度比例和一键回滚开关。成本优化最怕的不是没优化,而是优化到一半回不去。
- 调整要有观测窗口。参数改动后至少观察完整的业务周期再下结论——一个页面的流量高峰在晚上,用早上的数据评估晚上的改动,结论一定是错的。
- 动态调整不能破坏缓存语义。只要调整会改变图片内容,它就必须体现在 URL 上(尺寸、格式、质量、版本),否则 CDN 拿缓存里的旧内容发给新请求,等于没调整。
把这三层放在一起,整体策略就是:静态规则定边界,边缘协商做适配,前端在会话内兜底。三者都只做自己能看到的那部分信号,不要越界猜测。
| 维度 | 关键指标 | 优化方向 | 典型目标 |
|---|---|---|---|
| 加载速度 | LCP、下载时间 | CDN、格式、懒加载 | LCP < 2.5s |
| 图片质量 | SSIM、PSNR、MOS | 智能压缩、格式选择 | SSIM > 0.95 |
| 存储成本 | 存储量、流量、请求 | 分层、生命周期、缓存 | 命中率 > 95% |
核心原则:
- 速度第一:用户不会等待慢加载的图片
- 质量够用:在可接受范围内最大化压缩
- 成本可控:根据业务价值合理投入
在下一节中,我们将讨论具体的技术方案和架构设计。
发现内容有问题?
告诉我们哪里需要改进。
