用自家产品构建自家产品:Cloudflare Images 的工程架构解析
原文链接:https://blog.cloudflare.com/building-cloudflare-images-in-rus... 当一家公司在构建自己的产品时,有一种选择往往被低估:用自己的基础设施来构建自己的产品。这种做法既是对自家产品能力的真实检验,也能让内部团队成为产品的第一批深度用户。 Cloudflare Images 就是这样诞生的。它的整个技术栈建立在 Rust 可复用库和 Cloudflare Workers 之上,而不是另起炉灶搭一套全新的系统。 这篇文章就来拆解这套架构,看看它的设计决策背后有什么值得借鉴的东西。 Cloudflare 的图像处理业务线不止一个,在 Images 之前,已经有两个产品在跑: Image Resizing 是一个边缘图像处理服务,用 Rust 写成。它接收带有缩放参数的 HTTP 请求,从源站拉取原图,做缩放、压缩等处理,再把优化后的图像返回给客户端。 Polish 是另一个产品,基于 Golang,负责对缓存中的图像做重压缩优化。它原本依赖 当 Cloudflare 决定开始做 Cloudflare Images 时,工程师做了一个关键决定:不重新造轮子,而是把 Image Resizing 的核心图像处理逻辑拆分成独立的 Rust crate(库),让多个产品共享这些库。 效果非常直接: 这种做法的好处不只是减少重复代码,更重要的是消除了不同产品之间的行为差异。同一张图,通过不同入口处理,结果应当一致——代码共享从机制上保证了这一点。 Cloudflare Images 整体由四个组件构成: Images 核心服务,负责对外暴露管理 API,用于上传、删除、查询图像资产,以及管理变体(variant)配置。 Image Resizing 服务,负责实际的图像变换和缓存。每当有图像请求进来,需要裁剪、缩放、转格式时,都是这个服务在处理。 图像分发 Worker,一个运行在 Cloudflare 边缘的 JavaScript Worker,负责解析请求 URL,提取参数,然后把参数传递给 Image Resizing 服务。 图像存储,存放原始图像资产,Image Resizing 服务从这里拉取原图。 这四层分工明确。用户请求打来时,Worker 先接住,解析 URL 参数,Image Resizing 服务从存储里取出原图做处理,处理结果进入 Cloudflare 的缓存,下次同样的请求直接命中缓存返回,不再重复处理。 Cloudflare Images 的数据分散在多个位置,每个位置的选择都有明确的理由。 图像元数据(比如某张图是否公开访问)存在核心数据中心,不放到边缘。原因很简单:元数据会随时更新,而 Cloudflare 有数百个边缘节点,把元数据同步到所有边缘成本极高,且容易产生一致性问题。 变体定义(即各种尺寸预设配置)通过 Cloudflare 内部的分布式键值存储 Quicksilver 下发到边缘节点。变体的数量是有限的(每个账号有上限),所以数据量可控,适合在边缘缓存。 原始图像存在核心数据中心。原图只需要在处理时访问一次,处理结果会被缓存在边缘,不需要把原图分散到全网。 处理后的图像缓存在距离用户最近的边缘节点,这是真正高频访问的数据,必须离用户近。 为了进一步提升缓存命中率,图像分发域名 有一个设计取舍值得单独说一下:图像的访问权限标志(公开还是需要验证)被编码在 URL 里,而不是存在某个数据库或 KV 中。原因是如果把每张图的元数据都放进 Quicksilver,数据量会随图像数量线性增长,很快超出边缘存储的合理范围。把这个标志放进 URL,代价是:一旦图像的访问权限发生变化,URL 也必须跟着变。这是一个刻意选择的权衡,不是疏漏。 任何有缓存的系统都需要回答一个问题:数据更新了,缓存怎么失效? Cloudflare Images 有两类需要触发缓存清除的场景: 场景一:图像的访问权限改变了。 这时候需要清除所有相关缓存。做法是直接对该图像的完整 URL 发起缓存清除请求——精准,不会误伤其他图像。 场景二:某个变体定义更新了(比如把"缩略图"尺寸从 200x200 改成了 300x300)。这时候需要清除该账号下所有使用了这个变体的缓存图像。做法是按标签批量清除:Image Resizing 服务在生成处理后图像时,会给缓存条目打上 这两种策略对应两种不同的失效粒度:单 URL 清除适合精确操作,标签清除适合批量操作。二者结合,覆盖了所有需要清缓存的场景。 Cloudflare Images 支持私有图像,需要通过签名 URL 才能访问。签名机制基于 SHA-256 HMAC,流程如下: 一个带签名的 URL 看起来类似: 带过期时间的版本则额外包含 这个方案的好处是无状态:服务端不需要存储任何会话信息,只需要一个签名密钥,就能验证任意请求的合法性,实现起来简单,扩展性也好。 Cloudflare Images 支持"直传上传"(direct creator uploads):用户可以不通过 API Token,直接把图像上传到 Cloudflare。这个能力常用于 Web 应用或移动 App,让终端用户能够直接上传内容,而不需要在客户端暴露后端 API Token。 实现方式是这样的: 整套流程没有新增任何专用的后端服务,完全用 Workers + KV 拼出来了。这是 Cloudflare 那套"用自家产品搭自家产品"风格最典型的体现。 回顾整个架构,有几个决策思路值得单独拎出来: 库复用优先于服务复制。 三个不同的图像处理产品,共享同一套 Rust crate,而不是各自维护一份相似的代码。这在工程上要求提前做好抽象,但收益是长期的:行为一致性有了保证,一处修复所有产品受益。 用自己的平台做内部产品。 Workers、KV、Quicksilver,这些都是 Cloudflare 对外销售的产品,同时也是构建 Cloudflare Images 的基础设施。用自家产品踩自家产品的坑,是发现问题最快的方式。 数据的存放位置应该匹配其访问模式。 高频只读的数据(处理后的图像)放在最近的边缘缓存;有限且需要全网一致的数据(变体定义)通过分布式 KV 同步;量大且只在处理时才访问的数据(原始图像)留在核心数据中心。每类数据的位置都有对应的逻辑,不是一刀切。 无状态设计降低运维复杂度。 URL 签名验证不需要查数据库,服务端只存一个密钥;直传上传的临时凭证存在 KV 里自动过期,不需要手动清理。减少需要主动维护的状态,是这套架构在运维层面保持简洁的重要原因。 Cloudflare Images 不是一个从零搭建的全新系统,而是在已有基础设施上做了有节制的扩展:复用 Image Resizing 的 Rust 库,调用已有的 Workers 和 KV,利用 Quicksilver 做配置分发,用 Tiered Caching 优化命中率。 这种"组合已有能力"的建设方式,比"重新造轮子"风险更低、迭代更快,也更能发现自家基础设施的真实边界。 对于工程团队来说,这套思路的参考价值不仅限于图像服务:在设计新系统时,先想清楚能复用什么,比想清楚要新建什么更重要。本文基于 Cloudflare 官方博客,介绍 Cloudflare Images 产品背后的技术架构——它如何用 Rust 和 Cloudflare Workers 搭建,以及 Cloudflare 在这个过程中一以贯之的工程哲学。
一个很有意思的构建方式
Rust Crate 复用:一套代码,多个产品共用
jpegtran、pngcrush 这类第三方命令行工具。Cloudflare Images 的四层架构
数据存在哪里,是一门学问
imagedelivery.net 配置了 Cloudflare 的 Tiered Caching(分层缓存),让热门图像尽可能在边缘命中,减少回源次数。缓存失效:两种场景,两种策略
account-id/variant-name 这样的标签,清除时按标签一次性扫掉所有匹配项。URL 签名:访问控制的实现细节
?exp=<timestamp> 的形式追加到 URL;sig=<64位hex> 的形式追加到 URL。https://imagedelivery.net/<account>/<image-id>/public
?sig=6293f9144b4e9adc83416d1b059abcac750bf05b2c5c99ea72fd47cc9c2ace34exp 参数。服务端在收到请求时重新计算签名,比对结果,不一致或已过期则拒绝访问。直传上传:再一次靠 Workers KV 解决问题
值得提炼的几个工程判断
小结