在以视频分发为主业的CDN平台上,图片资源经常被当成配角,但实际统计下来,封面图、剧集海报、缩略图、用户头像这类小图的请求次数往往比视频分片高出两到三个数量级。这些图片如果继续以原始JPEG或PNG格式透传,占用的带宽和回源压力都相当可观。WebP和AVIF这两种下一代格式能在同等视觉质量下把体积压缩三到五成,把它们接入CDN的图像处理链路,是投入产出比很高的一项优化。不过格式转换并不是装个编码器那么简单,从浏览器协商、边缘转码到缓存设计,每个环节都有具体的工程问题要解决,下面结合实际落地经验逐个展开。

为什么格式转换要放在边缘节点而不是源站
最直接的想法是在资源上传时就把原图转成WebP和AVIF各存一份,源站和CDN只做透传。这个方案在小规模场景可行,但放到每天新增数百万张图片的视频平台上,问题很快暴露:存储成本直接翻两三倍,长尾图片占了绝大多数却几乎没人访问,转换算力大量浪费;上传链路被同步转码拖慢,业务方接入成本高;将来编码参数升级时,存量图片要全量重刷,周期动辄数周。
把转换动作下沉到边缘节点按需触发,情况就完全不同。用户请求到达边缘节点时先查缓存,未命中才回源取原图并现场转换,转换结果写入本地缓存供后续请求复用。这样只有真正被访问过的图片才会消耗转码算力,存储也只占用热点部分,源站一行代码都不用改。代价是首次请求的延迟会增加,这需要靠预热机制和超时兜底来控制,后文会专门讨论。
从收益上看,业界普遍的实测结论是:同等主观质量下WebP比JPEG小百分之二十五到三十五,AVIF在此基础上还能再小百分之二十到五十;对带透明通道的PNG,WebP的压缩优势更明显。假设图片流量占平台总流量的百分之十五,全量切换到AVIF后整体带宽能下降百分之五上下,对于带宽成本以亿元计的业务,这是不能忽视的数字。
格式协商:让同一个URL返回不同的格式
CDN做格式转换的前提是知道浏览器支持什么。HTTP协议里现成的机制就是Accept请求头,Chrome、Edge等浏览器会在其中携带image/avif,image/webp字样,较新版本的Safari同样两者都支持,老版本则只有image/jpeg这类传统类型。边缘节点解析这个头,按AVIF优先、WebP次之、原图兜底的顺序选择目标格式,前端不需要改动任何URL,存量页面零成本接入。
前端侧其实也有替代方案,用<picture>元素配合<source>标签列出多个候选格式,让浏览器自己挑选。这种方式控制粒度细,但要求业务方改造模板、维护多份URL,对已经上线的大量页面并不友好。CDN侧协商的优势恰恰在于对前端完全透明,这也是它在大型平台上更受欢迎的原因。
在Nginx层面可以用map指令完成协商逻辑,配置如下:
map $http_accept $img_format {
default "origin";
"~*image/avif" "avif";
"~*image/webp" "webp";
}
server {
listen 80;
location ~* \.(jpg|jpeg|png)$ {
# 把格式编进缓存键,隔离不同变体
proxy_cache_key "$uri|$img_format";
add_header Vary "Accept";
proxy_pass http://img_origin;
}
}
这里有个容易被忽略的细节:响应头里必须带上Vary: Accept,否则浏览器缓存或中间代理可能把AVIF响应缓存下来,再返回给不支持的客户端,页面直接裂图。但Vary对CDN自身缓存是个麻烦,不少CDN实现并不解析它,所以更稳妥的做法是把格式直接编进缓存键,也就是上面配置里的$uri|$img_format组合键,用键空间隔离不同变体,不依赖Vary的语义正确性。
协商顺序上建议AVIF优先于WebP。有团队担心AVIF编码慢,倾向只上WebP,但客户端能力是现成的,两种格式的体积收益差距又很明显,正确的做法是AVIF为主,WebP做AVIF转码失败或超时时的降级目标,两层兜底比单层稳得多。
边缘转码实现:编码器选型与参数调优
转码库的选择上,libvips系是目前工程上的主流,Node.js生态里的sharp封装了它,底层同时调用libwebp和libaom(AVIF的参考编码器),一条链路搞定两种格式,内存占用也比ImageMagick低一个量级,很适合在边缘节点这种资源受限的环境里跑。Go技术栈可以用bimg或者cgo直接绑定libvips,思路一致。下面是sharp的核心转换代码:
const sharp = require('sharp');
async function convertImage(input, target) {
if (target === 'avif') {
// effort控制编码速度,3是延迟与压缩比的平衡点
return sharp(input)
.avif({ quality: 55, effort: 3, chromaSubsampling: '4:2:0' })
.toBuffer();
}
if (target === 'webp') {
return sharp(input)
.webp({ quality: 78, alphaQuality: 90 })
.toBuffer();
}
return input;
}
// 带超时保护,避免大图把节点CPU拖垮
async function convertWithTimeout(input, target, ms) {
const timer = new Promise((resolve) => setTimeout(() => resolve(null), ms));
const result = await Promise.race([convertImage(input, target), timer]);
if (result === null) {
// AVIF超时就降级到WebP,再不行由上层回退原图
return convertImage(input, 'webp');
}
return result;
}
质量参数没有万能值,需要拿自家图片做盲测再定。经验上WebP的quality设在75到80,观感与质量85的JPEG基本持平;AVIF的quality在50到60区间就有不错表现,因为它的压缩效率更高,同等体积下画质明显好于WebP。另外AVIF的effort参数直接影响编码耗时,0最快但体积明显变大,6以上单张图可能要编码好几秒,边缘节点这种延迟敏感的场景,建议卡在3到4。
AVIF编码慢是落地时最突出的矛盾。一张两百万像素的图,effort为3时编码耗时可能达到几百毫秒,节点CPU一旦被转码任务打满,正常缓存命中的请求也会被拖累。工程上有两个缓解手段:一是给转码任务设独立线程池并做并发限流,与请求处理路径隔离;二是对超过一定分辨率的图先缩放再编码,海报图、封面图这类场景本来就不需要输出原始尺寸。
还有一类边界情况要单独处理:动图。WebP支持动画,但GIF转WebP的体积收益并不稳定,帧序列复杂时甚至可能变大,所以转换前应该比较输出与输入的体积,变大就回退原图。AVIF的动画支持在部分老版本浏览器上存在兼容问题,动图建议先只走WebP通道,观察一段时间再放开。
缓存键设计与命中率优化
按需转换的价值完全建立在缓存命中率上,首次转换的成本要靠后续成千上万次命中来摊薄。缓存键的设计有几个要点:键里必须包含格式与质量参数;键的命名空间要预留版本号,编码器升级或参数调整时递增版本,让旧缓存自然过期,避免执行全量刷新这种危险操作。参考实现如下:
func buildCacheKey(uri string, format string, quality int) string {
// v2是版本号,编码器或参数升级时递增即可让旧缓存整体失效
key := uri + "|" + format + "|" + strconv.Itoa(quality) + "|v2"
sum := md5.Sum([]byte(key))
return fmt.Sprintf("%x.%s", sum, format)
}
除了正向缓存,负缓存同样重要。转码失败、原图损坏、源站返回非图片内容这些情况,要把失败结果也缓存一小段时间,比如六十秒,否则一张坏图会反复触发回源和转码,形成故障的自我放大。命中率的监控要按格式维度拆开看,AVIF刚上线时命中率会明显低于WebP,属于正常现象,随着缓存逐步预热会慢慢追平。
对于剧集封面、首页banner这类可预测的热点资源,可以在业务发布时主动推送预热请求,让边缘节点提前完成转换,用户首次访问就能直接命中。预热不需要复杂设计,一个内部HTTP接口批量回放热门URL即可,关键是和业务的发布流程打通,形成固定动作。
灰度上线与效果验证
直接全量开启格式转换的风险不小,一旦某个机型的解码出现兼容问题,影响面很难控制。稳妥的路径是先按地域或客户端版本灰度,比如先放百分之一的流量走新链路,观察一周核心指标再逐步扩大。服务端要保留开关能力,能按URL前缀、UA、地域随时切回原图直出,这个开关要在方案设计阶段就规划好,而不是出事之后再补。
监控指标至少覆盖四项:各格式的流量与请求占比、边缘缓存命中率、转码耗时的P99值、图片相关的客户端错误率。日志里把格式和缓存状态打出来,排查问题会轻松很多:
log_format imgcdn '$time_local "$request" $status $body_bytes_sent '
'rt=$request_time ct=$convert_time '
'format=$img_format cache=$cache_status';
效果验证不要只盯着带宽数字,还要看业务侧指标。可以在客户端埋点图片加载耗时,对比灰度前后的首屏数据,体积变小之后,弱网环境下的加载完成时间通常会有可感知的改善,这是向上汇报时最有说服力的证据。最后附一份主流浏览器对两种格式的支持情况,作为灰度范围决策的参考:
| 浏览器 | WebP | AVIF |
| Chrome | 32及以上版本支持 | 85及以上版本支持 |
| Firefox | 65及以上版本支持 | 93及以上版本支持 |
| Safari | 14及以上版本支持 | 16.4及以上版本支持 |
| Edge及国产Chromium内核浏览器 | 普遍支持 | 新版本内核支持 |
整体来看,WebP与AVIF的接入在技术上是渐进式的:先上WebP把协商、转码、缓存这一整条链路验证跑稳,再叠加AVIF提升收益上限,风险可控,收益却能随格式占比的提升持续放大。视频CDN平台手握现成的边缘节点资源和缓存体系,做这件事几乎是顺水推舟,值得排进最近的迭代计划里。

