给图片生成服务做预算的时候,我一开始只算了 GPU 成本,后来发现账单出来的时候,GPU 只占三分之一,剩下的全是存储和出站流量。
这篇是我把账重新算了一遍之后的结果。
一次生成留下多少字节
先看最直观的:一张 1024×1024 的图,各种格式存下来多大。
| 格式 | 单张体积 | 说明 |
|---|---|---|
| PNG | 1.5-2.5MB | 无损,AI 生成图里最费空间 |
| JPEG q90 | 200-400KB | 有损,通用 |
| JPEG q80 | 100-200KB | 有损,画质损失可接受 |
| WebP q85 | 150-300KB | 比 JPEG 略小,兼容性要确认 |
| AVIF q60 | 80-150KB | 最小,但转码吃 CPU |
| 原图(模型直出) | 2-4MB | 通常是 PNG 或带元数据的格式 |
这个表里最刺眼的是第一行和最后一行的差距:同一张图,PNG 存是 WebP 的十倍。
我的服务一开始把模型直出的原始 PNG 直接落盘,然后原样返回给前端。一张图 2MB,用户看十张就是 20MB。移动端弱网下,那个加载条能转半分钟。
反直觉的一点:用户要的不是原图
我的用户(一个做海报生成的小团队)最初的要求是"给我最高画质",我照做了。
结果发现他们真正会做的操作是:生成 → 缩小到海报尺寸 → 排版。生成时的 1024×1024 原图,在他们的工作流里几乎从来不是最终交付物。
于是我加了一条规则:原图存起来,但对外只发缩放过的版本。
原图(1024×1024 PNG) → 对象存储,加密,只给创作台用
展示图(512×512 WebP) → CDN,对外
缩略图(160×160 WebP) → CDN,对外,用于列表
一次生成留三份,磁盘占用没怎么涨(缩略图极小),但对外流量降到了原来的五分之一。
关键在于展示图的尺寸要按实际场景定。我一开始把展示图也做成 1024,后来看访问日志,一半的请求其实只需要列表里那个小方块。
带宽账:按访问模式算,不按生成量算
这是最容易搞错的地方。直觉上"生成得多 = 流量大",但实际上:
出站流量 ≈ 生成量 × 平均每次生成的访问次数 × 展示图体积
我用自己站点的一个月数据算:
| 场景 | 月生成量 | 月展示访问 | 展示图体积 | 月出站流量 |
|---|---|---|---|---|
| 内部工具,几乎没人看 | 2000 | 5000 | 200KB | 1GB |
| 小众产品,有人看 | 5000 | 40000 | 200KB | 8GB |
| 有分享功能的 | 8000 | 300000 | 200KB | 60GB |
你看,同一个生成量,因为访问模式不同,出站流量差了 60 倍。流量是访问量的函数,不是生成量的函数。
所以预算的时候我不看"我打算生成多少张",我看"每个生成结果会被看几次"。带分享功能的产品,光是分享出去的图片被反复打开,就能吃掉大半预算。
存储的坑:元数据比图片还能塞
有一件事我是在账单里看出来的——对象存储的请求次数比数据量还贵。
因为我的实现里,每张图生成完要写一次元数据、算一次缩略图、算一个校验和,这些全是独立的对象操作。生成一张图可能对应七八次存储请求。
后来改成:原始文件一次 PUT,中间的处理在内存里做完,最后合并成一次批量写。请求次数降下来了。
还有一个更隐蔽的:我给每张图生成的路径里带了时间戳和随机 ID。
# 改之前
key = f"images/{timestamp}_{uuid}.png"
# 改之后
key = f"images/{content_hash}.png"
用内容哈希做 key 有两个好处。一是重复上传自动去重——用户重试同一张图不会存两遍。二是 CDN 缓存能命中,因为 URL 稳定。
原来的时间戳 URL 每次请求都是新地址,CDN 完全没法缓存,所有流量都回源。改完之后,CDN 命中率从 20% 涨到 95% 以上。
CDN 上的三个配置
第一,缓存头要长。 图片内容不可变(key 是内容哈希),缓存时间直接设一年:
Cache-Control: public, max-age=31536000, immutable
第二,回源带宽要做限制。 我在源站前面加了一层限流,避免缓存失效瞬间(比如批量刷新)把源站打挂。
第三,过期之后主动刷新。 内容哈希理论上不会变,但如果我后来改了压缩参数、想让老图用新编码,得让 CDN 重新拉。做法是保留一个"生成参数版本"字段,参数变了就在 key 里带上版本号:
key = f"images/{content_hash}_v{encoder_version}.webp"
版本号一变 URL 就变,CDN 自然回源重新拉。用户拿到的是新图,老 URL 依然有效。
转码放在哪儿
有一段时间我把 WebP 转换放在云主机上做,结果 CPU 打满,图片展示接口响应变慢。
后来挪到了对象存储的事件通知上:图片写完触发一个函数去转码,转完再写展示图,CDN 指向转好的那份。
这样做的代价是用户第一次请求时可能拿到 404(转码还没完成)。我的处理方式是:原图的 URL 也保留一个,几秒后自动切到转码后的地址。
如果你的场景要求严格不能有 404,那就得在上传接口里同步完成转码——那就要给转码服务留出足够的 CPU 核数,别让它和 Web 服务抢。
一份我现在用的检查清单
- 生成量、存储量、出站流量分开算,别混成一个数
- 原图和对外图分开存,对外只发缩放版
- 对外图的尺寸按真实使用场景定,别默认给最大
- 用内容哈希做 key,顺便解决去重和缓存两件事
- 缓存时间设长,但要有主动刷新的手段
- 存储请求次数单独看,它可能比数据量更贵
价格这块我不写具体数字,各家计费口径差得挺远(按量、按请求次数、流量阶梯都不一样),以官方页面为准。上面的体积和流量量级是我自己站上的实测,换个分辨率和编码参数会明显不一样。