从 Cloudflare CDN 闲聊

X1a0t,5 min read

CDN 将内容缓存到离用户地理位置更近的服务器,可以提高加载速度,并且提供 DDoS 防护,WAF 防火墙,流量监控等额外好处。

配置 CDN 过程,以 Cloudflare 为例,在 Cloudflare 购买域名(因为对 Cloudflare 来说,一个 DNS Zone 是一个缓存集合),配置域名 DNS CNAME 代理到真正 host 静态资源的服务器域名/IP,然后将应用的静态资源加载域名配置成 Cloudflare 域名,就大功告成了。会发现这个过程中并没有接触任何实际的 CDN 存储资源,只是配置了 DNS。这时所有的静态资源都会经过 Cloudflare,根据缓存策略决定返回被缓存的资源或者请求服务器的静态资源。

例如 Cloudflare 域名是 assets.com, 静态资源服务器是 aaa.bbb.ccc.dev,那么将 assets.com proxy 到 aaa.bbb.ccc.dev,所有的 Request( assets.com/chunk.2262-4b0dfa22.js) 都可以经过 Cloudflare,根据策略被缓存,如果非命中,再打到 aaa.bbb.ccc.dev/chunk.2262-4b0dfa22.js。

Note: 因为对 Cloudflare 来说,一个域名 Zone 是一个缓存集合,所以如果新开了域名,缓存会是空的状态。

实际的静态服务器很灵活,可以是 nginx/Apache Static Server,也可以是 AWS S3/Cloudflare R2 等对象存储服务。或者被 k8s 部署,ingress 配好对外暴露域名的 docker 容器 (opens in a new tab)

CDN 和 HTTP 的缓存 Header 结合非常紧密,除了 Cloudflare 的默认缓存策略,Cloudflare 还会根据例如 Cache-Control 来决定缓存行为,也有定制的 Cloudflare-CDN-Cache-Control 头 (opens in a new tab)。例如如果 Request Header 的 Cache-control 设置成 no-cache,Cloudflare 就不会缓存这个资源 (opens in a new tab)

除了静态资源可以被缓存,动态资源是否可以?例如一个服务接口 /api/workspaces 返回的内容,(虽然可能意义不大,接口返回的数据往往没有静态资源体积大,而且经常变化)。

> 理论上任何 GET 请求都可以被 Cache,对于 Cloudflare 来说,使用 Cloudflare worker 来手动调 cache 的 api (opens in a new tab) 来定制这种行为比较方便。

通过调试工具查看是否命中 CDN

Cloudflare 有非常方便的调试工具 Dr.FLARE (opens in a new tab),帮助查看缓存命中情况,下面是界面的几张截图。

一个全新的 Network 面板

查看 Request 是否被 Cloudflare DNS proxy 以及是否被缓存;cache 相关的 Header。

不使用 Dr.Flare 的情况下,可以看 Response HeaderCf-Cache-Status 判断缓存是否 Cloudflare 命中。

Cloudflare Worker 和 CDN

Cloudflare Worker 也共享 DNS zone 的缓存集合,worker 的拓扑是:

防火墙 -> Cloudflare Workers -> CDN -> Origin Server

因为 worker 在 CDN 前面,所以需要手动调用缓存相关的 API。但好消息是 worker 内使用的 fetch 请求(也被称为 subsequent request)默认使用 CDN 缓存。另外 fetch 也支持使用 cf option 定制缓存行为:

const handler: ExportedHandler = {
  async fetch(request) {
    const url = new URL(request.url);
    // Only use the path for the cache key, removing query strings
    // and always store using HTTPS, for example, https://www.example.com/file-uri-here
    const someCustomKey = `https://${url.hostname}${url.pathname}`;
    let response = await fetch(request, {
      cf: {
        // Always cache this fetch regardless of content type
        // for a max of 5 seconds before revalidating the resource
        cacheTtl: 5,
        cacheEverything: true,
        //Enterprise only feature, see Cache API for other plans
        cacheKey: someCustomKey,
      },
    });
    // Reconstruct the Response object to make its headers mutable.
    response = new Response(response.body, response);
    // Set cache control headers to cache on browser for 25 minutes
    response.headers.set("Cache-Control", "max-age=1500");
    return response;
  },
};
 
export default handler;

除了使用 fetch 和 OPTION 定制缓存行为,worker 的运行时也提供了 cache API (opens in a new tab),可以将 Request 对象作为 key 来 CRUD 缓存。上面提到的缓存动态资源,就可以使用这个 API 手动 set/get 缓存来实现。

CC BY-NC 4.0 2023 © Powered by Nextra.