用于提供 "stale"内容的 CDN 支持/配置,在后台刷新

标签 cdn next.js cloudflare cache-control fastly

目标

始终提供来自 CDN EDGE 缓存的内容,无论内容多么陈旧。尽可能在后台刷新它。

问题

我有一个 NextJS在服务器端呈现一些 React 组件并将它们交付给客户端的应用程序。对于这个讨论,让我们只考虑我的主页,它是未经身份验证的,对每个人都是一样的。

我想要的是将服务器呈现的主页缓存在 CDN 的 EDGE 节点上,并尽可能多地或始终从该缓存中为终端客户端提供服务。

从我读过的内容来看,CDN(如 FaSTLy)正确支持缓存相关的 header 设置,如 Surrogate-Control Cache-Control: stale-while-revalidate应该能够做到这一点,但在实践中,我没有看到它像我期望的那样工作。我看到的是:

  • 请求错过缓存并在先前的请求应该预热它时返回到原点
  • 请求由缓存提供,但在源发布新内容时永远不会更新

  • 例子

    考虑以下时间线:

    [T0] - 访客 1 请求 www.mysite.com - CDN 缓存完全冷,因此请求必须返回我的源(AWS Lambda)并重新计算主页。返回带有标题 Surrogate-Control: max-age=100 的响应和 Cache-Control: public, no-store, must-revalidate .
    然后访问者 1 提供了主页,但他们不得不等待高达 5 秒钟! 呸! 愿没有其他游客遭受同样的命运。

    [T50] - 访客 2 请求 www.mysite.com - CDN 缓存包含我的文档并立即将其返回给访问者。他们只需要等待 40 毫秒!惊人的。在后台,CDN 从我的源重新获取最新版本的主页。原来它没有改变。

    [T80] - www.mysite.com将新内容发布到主页,使任何缓存的内容真正过时。该网站的V2现已上线!

    [T110] - 访客 1 返回 www.mysite.com - 从 CDN 的角度来看,自访问者 2 的请求以来只有 60 秒,这意味着访问者 2 发起的后台刷新应该导致缓存中主页的 <100 秒陈旧副本(尽管主页的 V1,而不是 V2)。 Visitor1 从缓存中获得 60 年代陈旧的 V1 主页。这一次给 Visitor1 带来了更好的体验!
    本次请求对CDN缓存中的陈旧内容发起后台刷新,本次origin返回的是网站的V2版本(30s前发布的)。

    [T160] - 访客3次访问www.mysite.com - 尽管是新访问者,但 CDN 缓存现在从访问者 1 的最近一次后台刷新触发中恢复过来。 Visitor3 提供缓存的 V2 主页。

    ...

    只要每 100 秒至少有 1 位访问者访问我的网站(因为 max-age=100),任何访问者都不会遭受到我的原点的完整往返的等待时间。

    问题

    1. 这是对现代 CDN 的合理要求吗?我无法想象这比总是返回原点(没有 CDN 缓存)更费力,但我一直在努力从任何 CDN 提供商那里找到有关正确方法的文档。我现在正在与 FaSTLy 合作,但我也愿意尝试其他任何人(我首先尝试了 Cloudflare,但读到他们不支持 stale-while-revalidate)

    2. 什么是正确的标题来做到这一点? (假设 CDN 提供商支持它们)
    我玩过这两个 Surrogate-Control: maxage=<X> Cache-Control: public, s-maxage=<X>, stale-while-revalidate 在 FaSTLy 和 Cloudflare 中,但似乎没有一个正确地做到这一点(在 maxage 时间范围内的请求不会在源上发生变化,直到出现缓存未命中)。

    3. 如果这不受支持,是否有 API 调用可以让我将内容更新推送到我的 CDN 的缓存层,有效地说“嘿,我刚刚为这个缓存键发布了新内容。就在这里!”

    我可以使用 Cloudflare 工作人员使用他们的 KV 存储自己实现这种缓存,但我想在为一个似乎很常见的问题实现代码解决方案之前,我会做更多的研究。

    提前致谢!

    最佳答案

    我最近一直在部署一个类似的应用程序。我最终在 Next.js 服务器前运行了一个定制的 nginx 实例。

  • 忽略来自上游服务器的缓存头。
  • 我想缓存标记和 JSON,但我不想发送 Cache-Control头文件给客户端。您可以调整此配置以使用 Cache-Control 中的值如果 MIME 类型为 text/html,则从 Next.js 开始,然后在响应客户端之前删除该 header 或 application/json .

  • 考虑所有在 10 分钟内有效的响应。
  • 30 天后删除缓存的响应。
  • 最多使用 800 MB 的缓存。
  • 提供过时的响应后,尝试从上游服务器获取新响应。

  • 这并不完美,但它处理了重要的 stale-while-revalidate 行为。如果您想要全局传播的好处,您也可以在此之上运行 CDN。
    警告:这还没有经过广泛测试。我不相信围绕错误页面和响应代码的所有行为都是正确的。
    # Available in NGINX Plus
    # map $request_method $request_method_is_purge {
    #   PURGE   1;
    #   default 0;
    # }
    
    proxy_cache_path
      /nginx/cache
      inactive=30d
      max_size=800m
      keys_zone=cache_zone:10m;
    
    server {
      listen 80 default_server;
      listen [::]:80 default_server;
    
      # Basic
      root /nginx;
      index index.html;
      try_files $uri $uri/ =404;
    
      access_log off;
      log_not_found off;
    
      # Redirect server error pages to the static page /error.html
      error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 500 501 502 503 504 505 /error.html;
    
      # Catch error page route to prevent it being proxied.
      location /error.html {}
    
      location / {
        # Let the backend server know the frontend hostname, client IP, and
        # client–edge protocol.
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        # This header is a standardised replacement for the above two. This line
        # naively ignores any `Forwarded` header passed from the client (which could
        # be another proxy), and instead creates a new value equivalent to the two
        # above.
        proxy_set_header Forwarded "for=$remote_addr;proto=$scheme";
    
        # Use HTTP 1.1, as 1.0 is default
        proxy_http_version 1.1;
    
        # Available in NGINX Plus
        # proxy_cache_purge $request_method_is_purge;
    
        # Enable stale-while-revalidate and stale-if-error caching
        proxy_cache_background_update on;
        proxy_cache cache_zone;
        proxy_cache_lock on;
        proxy_cache_lock_age 30s;
        proxy_cache_lock_timeout 30s;
    
        proxy_cache_use_stale
          error
          timeout
          invalid_header
          updating
          http_500
          http_502
          http_503
          http_504;
    
        proxy_ignore_headers X-Accel-Expires Expires Cache-Control Vary;
        proxy_cache_valid 10m;
    
        # Prevent 502 error
        proxy_buffers 8 32k;
        proxy_buffer_size 64k;
        proxy_read_timeout 3600;
    
        proxy_pass "https://example.com";
      }
    }
    

    关于用于提供 "stale"内容的 CDN 支持/配置,在后台刷新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59813228/

    相关文章:

    javascript - redux 如何与 next.js 配合使用?

    webpack - 在 Next.js 中编译 npm 模块

    cloudflare - 如何确保 Sveltekit 项目能够真正运行在 Cloudflare workers 上?

    ssl - Cloudflare CNAME 扁平化和 Nginx

    ssl - 运行 HTTPS 网站时是否可以使用 CDN?

    seo - 将查询字符串附加到缓存的 Assets /图像(例如 Image.jpg?1234)是否会对 SEO 产生影响?

    php - 如何为我的站点设置内容分发网络?

    javascript - 警告 : Expected server HTML to contain a matching nav in div

    json - 尝试将其作为调试运行以对其进行测试时出现 ddclient 错误

    vue.js - 在使用 CDN 中的 Vuejs 并运行 Codekit 时,如何将组件拆分为更小的文件?