这是个很常见的困惑:你刚做完网站,反复刷新都是秒开,很满意。过两天再打开,转了一下才出来。你怀疑是服务器不行,或者是网络问题,但测又测不出所以然。
下面用我们自己的站做例子,把这件事拆开。所有数字都是实测的。
先看两个数字
同一个页面,从奥克兰访问:
缓存命中时:45 毫秒。你几乎感觉不到等待,点击和显示是连着的。
缓存没命中时:210 到 520 毫秒。这个区间里你会明确感觉到”顿了一下”。
十倍的差距,同一个页面,同一台服务器,同一个网络。区别只在于这一次请求有没有命中缓存。
缓存在哪里,为什么会没有
中间这层通常是 CDN,比如 Cloudflare。它在全球有很多节点,离新西兰最近的在奥克兰。第一个访客来的时候,节点没有这个页面,它去源服务器取一份,存下来,然后发给访客。之后的访客直接从奥克兰这个节点拿,不用再跑一趟。
问题出在”存下来”这件事上。
很多人以为缓存时间设长一点就行——设成一年,那就存一年。这个理解是错的,而且错得很关键。
缓存时间是上限,不是保证。CDN 节点的存储空间要给成千上万个网站共用,它按访问热度淘汰内容。一个流量不大的网站,即使你把缓存时间设成一年,几个小时没人访问,那份副本也会被挤掉给更热门的内容让位。
于是就出现了那个现象:你连续刷新的时候快,因为副本还在;隔一天再来,副本已经被淘汰了,这次请求要一路回到源服务器。
而且这个规律很残酷——越是流量小的网站,越容易碰到慢的那一次。而流量小的网站,往往每一个访客都更重要。
那 210 到 520 毫秒花在哪了
拆开看:从奥克兰到我们的源服务器(在悉尼),建立连接、TLS 握手、发请求、等响应、传回来,这一整趟大约 110 毫秒。这是物理距离决定的,改不了。
但实测的 CDN 回源比这个还慢,而且波动很大。原因是它每次都要重新建立到源服务器的加密连接,而不是复用一条已经开着的。
另外那个源服务器如果还要跑数据库查询、拼页面,再加几十到一百多毫秒。很多网站慢在这里,但要注意:即使你把服务器优化到极致,只要还需要回源,那一趟物理往返就省不掉。
常见的三种应对,以及各自的问题
把缓存时间调得更长。没用,上面说过了,那是上限不是保证。这是最常见的误区,很多人调完之后觉得好了,其实只是刚好在缓存还热的时候测的。
定时去访问自己的网站,让缓存保持热。方向对,但很难做对。CDN 是按节点分别缓存的,你从服务器上发起的请求只会暖到离服务器最近的那个节点,奥克兰的访客照样命中不到。
换更好的服务器。能改善回源那一段的处理时间,但改不了物理往返,也改不了缓存被淘汰这件事。花钱最多,效果最有限。
真正的解法是让源服务器退出这条路径
如果一个页面的内容在两次发布之间是不变的——绝大多数企业网站的页面都是这样——那它根本不需要每次去源服务器取。
做法是把页面提前生成成静态文件,随程序一起部署到 CDN 的网络里。这时它的身份变了:它不是缓存,是部署产物。缓存会被淘汰,部署产物不会——它就在那里,直到你发布下一个版本。
我们这个站改成这样之后的实测:所有页面稳定在 40 到 60 毫秒,连续测几十次没有一次超过 100 毫秒。之前是命中时 45 毫秒、没命中时 520 毫秒,现在没有”没命中”这个状态。
动态的部分——后台、表单提交、AI 客服接口——照常回源。它们本来就不该被缓存,而且量很小。
有一个代价,值得说清楚
这套做法有个新问题:每次发布新版本,各个节点上的文件要重新分发一次,第一个访问的人还是会慢一下(实测约 340 毫秒)。等于把”缓存被淘汰时慢”变成了”刚发布完慢”。
处理办法是发布流程的最后自己把这一次代价吃掉:发布完立刻从本地把所有页面请求几轮,让节点先填满,再交给访客。
这里有个细节容易漏:一个 CDN 节点内部有很多台服务器,请求会被分到不同机器上,每台都要各自填一次。我们实测跑两轮还剩四个页面是慢的,跑六轮才降到零。
你可以自己测
不需要任何工具,命令行一条命令就能看到自己网站的真实情况:
用 curl 请求你的首页,看 time_starttransfer 这个值,它是从发起请求到收到第一个字节的时间。连续测五次,再隔一天测五次。如果两次的结果差好几倍,那就是缓存淘汰在起作用,不是你的服务器有问题。
顺便看一眼响应头里的 cf-cache-status。HIT 是命中,MISS 是这次回源了。你会发现慢的那几次基本都是 MISS。
知道原因之后再决定要不要处理。我们交付的站点默认就是上面那套结构,不是作为加价项。