
使用Cloudflare和Nginx优化你的CDN缓存
最近我做了一个个人项目,帮助支持我日常使用的Linux发行版,Manjaro。我建立了一个软件包镜像,允许社区的用户连接到我的服务器,拉取更新或新的软件包进行安装。在这篇文章中,我将谈谈我是如何设置的,将90%以上的流量卸载到Cloudflare的CDN。
入门
当发行版更新被推送时,流量可能会有很大的峰值,我必须准备好每周提供约1TB的流量。我在预算范围内做这件事,所以我试图找出最具成本效益的方式来支持所有这些流量。由于镜像服务器列表的发布方式,如果我遇到数据限制或其他成本障碍,我也无法迅速选择不提供镜像服务器。进入Cloudflare和它的CDN缓存!
我做了一些研究,并确定了一个相当基本的技术栈,可以减少我直接从原点服务器提供的数据量:
- 运行Linux的Linode服务器
- Nginx作为源服务器
- Cloudflare作为CDN运行
这里的主要角色是Cloudflare,它必须尽可能多地对流量进行缓存。在进行了一些设置和安全调整后,我准备开始测试,看看我可以得到什么样的缓存命中率。
最初的测试
最初的测试显示我只得到了30%的命中率。这对于我所要做的事情来说太小了。因此,我潜入文档,了解更多关于Cloudflare的缓存设置,以及镜像在与软件包管理器Pacman交互时如何工作。
Pacman开始时,从预定义的路径中获取一个特定的DB文件存储。这个DB文件包含了它需要的所有信息,包括哪些软件包是可用的,以及提供的版本。它进行本地比较,检查是否有任何已安装软件包的更新,然后根据这个DB文件中的信息,提取新的版本。
单个软件包的路径并不经常改变。软件包管理器从DB文件中发现更新的软件包。这不是从/latest 路径或其他符号链接的路径中提取的情况,在这种情况下,路径会保持不变,而它背后的软件包版本会改变。
所以这意味着我可以直接缓存一切,对吗?嗯,不,那个讨厌的DB文件必须保持更新。它是软件包管理器找到新版本软件包的唯一途径。
配置Cloudflare
因此,现在是时候玩玩Cloudflare的缓存规则了,看看我们的进展如何。我把缓存级别推到最长持续时间,并使用Cloudflare的页面规则为DB文件创建一个例外。
这样做的效果稍微好一点。我现在的缓存命中率达到了35%左右。
通过在浏览器中做一些调试,我能够看到哪些资源被缓存了,哪些资源是由我的原生服务器提供的。看起来很多软件包仍然没有被缓存。事实上,带有扩展名.tar.zst 的软件包都没有被缓存。这是一个主要用于Pacman软件包管理器中的arch包的扩展。
经过对Cloudflare文档的进一步研究,我可以看到他们默认会缓存什么。这个页面解释了他们只缓存网站上最常见的扩展名的文件。不出所料,我的.tar.zst 扩展名不在其中。
在那一页上,我还发现,我们可以缓存的最大文件是512MB。因此,这些包可以放入缓存中。我们只是需要一种方法来告诉Cloudflare对它们进行缓存,Cloudflare有一个缓存设置,叫做 "Cache Everything"。但其他一些文档指出,这将忽略我为.db 文件创建的例外。
所以我只剩下一个选择。Cloudflare的 "origin cache-control "设置 这允许你使用由你的原点服务器设置的头文件来控制Cloudflare的缓存。这意味着我可以配置我的Nginx服务器,以指定Cloudflare应该做什么样的缓存。
了解缓存头文件
Nginx的文档描述了如何使用location 指令来设置特定文件的头信息,以及Nginx将以何种顺序使用这些设置。然而,在这之前,我们需要更好地了解Cache-Control 头的具体内容。
为Cache-Control 标头选择正确的数值集合,比大多数人想象的要简单。最重要的问题是:"这应该被缓存吗?"。一旦你回答了这个问题,你就完成了90%。
你应该考虑缓存所有浏览器认为是静态的东西。因此,这意味着CSS、JS、图像、图标、gif等。HTML通常不被认为是静态的,因为不同的东西可以出现在一个HTML页面中,即使它的URL是不变的。
现在你需要看一下事物的寿命。它们的变化频率如何?与图像或图标相比,JS或CSS文件更有可能改变。你可以根据这个分析,使用Max-Age 这个变量来调整一个东西的缓存时间。如果它是你期望半频繁更新的东西,或者你希望它对最大年龄过期时的行为非常严格,你可以添加一个额外的值:must-revalidate 或proxy-revalidate 。
proxy-revalidate 的设置只适用于 Cloudflare,而不是用户的浏览器。must-revalidate 的设置对浏览器和 Cloudflare 都有效。它所启用的操作与最大年龄设置过期时的做法有关。如果你设置了其中一个,设备会被引导再次从原点请求该数据*。*
这对于确保你不提供陈旧的数据是很好的。但是,如果它是一张图片,或其他东西,使用陈旧的版本不会破坏事情呢?在这种情况下,你可以提供它,但要记住在你下一次请求时更新缓存。您可以使用stale-while-revalidate 头来获得这种行为。使用它可以让Cloudflare在更新缓存时继续提供该数据,而不是强制停止,让客户在数据更新时等待。
Cloudflare 文档对这一切做了很好的总结。
public,private, 和no-store 的值在向网络浏览器提供内容时的影响比我的用例大得多,但它仍然是有趣的考虑。这些值可用于调整互联网上的哪些设备在试图缓存响应时将存储哪些数据。public 这个值很适合所有的静态内容。private 值很适合于像帐户页面或任何其他会员专用的数据。no-store值对敏感或经常变化的数据来说是非常好的。Cloudflare的文档再次很好地总结了这一点。
我喜欢使用的最后一个值是no-transform 。它有利于确保特定项目在传输给用户时不被破坏。如果你有预压缩后的东西,或者你不想额外压缩的东西,比如图片,这个设置就很适合你。它告诉CDN和浏览器不要乱动文件的内容,只保留文件的所有位。
配置Nginx
所以,现在我能够通过配置Nginx来提供我想要的头信息,把这一切放在一起。为了开始,我设置了一个全站的默认头信息。
location / {
add header Cache-Control "public, max-age=3600, must-revalidate";
}
这意味着,默认情况下,它将缓存所有内容一个小时。但在那之后,内容必须再次被检查,但不必过期,也不必从缓存中清除。
接下来是DB文件。我使用了与全站设置相同的设置,但增加了no-transform ,以确保压缩不会扰乱SHA1哈希值等任何东西。
location ~* \.(db|db\.sig|gz|files|sig) {
add header Cache-Control "public, max-age=3600, must-revalidate, no-transform";
}
最后是软件包本身。它们的直接路径永远不会改变,所以我可以永远缓存它们以最大限度地提高点击率。
location ~* \.(tar|zst|xz|xz\.sig|zst\.sig) {
add header Cache-Control "public, max-age=604800, immutable";
}
immutable 这个值告诉Cloudflare,这永远不会改变,并且再次由Cloudflare的文档进行了最佳描述。
immutable- 向客户表明,响应主体不会随时间变化。该资源,如果没有过期,在服务器上是不变的,因此,客户端不应该为它发送一个条件重新验证(例如,If-None-Match或If-Modified-Since)以检查更新,即使用户明确刷新页面。这个指令对Cloudflare等公共缓存没有影响,但确实改变了浏览器行为。
你会注意到我在上面列出的所有标题中也使用了public 的值。这是因为我正在提供带有奇怪扩展的内容。你可以通过使用上面链接的Cloudflare页面上的可用值来进一步定制缓存行为。
总结
一旦我部署了这些变化,我的缓存命中率就上升到了屋顶,现在一直保持在90%以上。我提供的流量以兆字节计,但其中只有一小部分是来自我的网站。一个CDN和少量的标题配置就能实现这么大的差异,真是令人惊讶


