cloudflare缓存与绕过缓存

上一篇 Koikatsu CharaStudio MMD浅入浅出 的衍生文章。在这之前,cloudflare那边的配置我基本就没怎么动过,一直开cache everything的策略来降低回源数据的……

先说现象

上篇文章贴了个两视频:一个100多M的av1 60fps格式的视频,90来秒;还有一个是600多M的h264 120fps格式的视频,3分半多一点。并且两个视频的预加载都是metadata级别的,代码贴下面:

1
2
3
<video controls preload="metadata">
<source src="https://cdn.zhouxuebin.club/data/2024/11/uchoten_bibache_h264_120fps.mp4" type="video/mp4">
</video>

本地测的时候没啥问题,两个视频都能正常加载播放。后面把url改成cdn之后,100多M的视频能够正常加载。但600多M那个视频,预载metadata的时候直接把整个文件几百M下了一遍,然后播放的时候又把那几百M重新下一遍,一趟折腾下来,就1G多的数据量了,明显不对劲,而且也不是必现的,偶尔也只需要正常加载个1M不到的数据就好了。

开始分析背后的成因

chrome的video tag都是通过流式传输按需加载内容的,背后使用HTTP的Range header来指定从哪里下到哪里。

正常来说,web网关在收到Range请求后,理论上返的HTTP状态码应该是206 Partial Content:

但现实是什么个情况呢?返了个200……而且响应头里面关于请求Range部分的数据都被丢弃了

一时排查不清是chrome的问题,还是cloudflare的问题,还是web网关的问题 (当然现在都是后日谈了) 。开始用控制变量法排除问题。

第一步,把cdn的url改为不经cdn的直连url,在dev tools把disable cache勾上,刷了十几遍,没有复现200的情况,基本可以排除chrome这选项了。

第二步,把web网关的log level改成trace,直接把所有请求和响应的header都log出来。直连情况验证没问题,这次直接用cdn的url发get请求。然后,神奇的事情发生了,web网关的请求header里面没有Range字段,然后很自然返了个200回去……似乎web网关也没有做错什么事情。

那么,就剩下cloudflare了……搜了一下其他人用cloudflare中转视频数据时也会遇到这种情况来看,大概能猜到个大概缘由了。它到底干了什么?其实,它也只是在缓存数据罢了。 毕竟开了cache everything嘛。

不需要串流,且文件大小也不大的文件,cloudflare会直接回源拉一次数据,后面就直接从cloudflare缓存里面拉了。

需要串流,且文件大小也不大的文件,cloudflare似乎也只会回源拉一次数据,而回源拉的数据,是不带Range header的。后续的Range请求,cloudflare能够命中缓存,而且缓存也能够正常处理Range请求,所以没有暴露出问题。

需要串流,文件大小超过cache大小的文件,问题就暴露出来了,cloudflare回源拉的那次数据没有Range header,而且Content-Length超过cache上限……表现是这样的:浏览器发了一条range请求,cloudflare的cache机制把range丢掉,给web网关发了条回源的get请求,web网关就这样老实巴交地给了个200的状态码回去了。cloudflare提供的header里,可以清楚看到cf-cache-status状态是miss,content-length就是整个文件的大小……这就是所谓的聪明反被聪明误吧……

最后给个解决方案

核心思路其实很简单,能cache就cache,cache不了的串流文件,那就直接绕过cache嘛。谷歌一下,就有一堆的方法,但比较简单粗暴,不尽完善。比较常见的就是:关掉全局cache,又或者是绕过某类mime-type的缓存。但怎么说呢,这种规则能够处理场景似乎比较死板……思来想去,要不就在url query string上下点功夫得了。

我个人的方案其实也很简单,两条规则足矣:全局cache保持打开状态,绕过某些query string的url。这样配置cache影响范围比较小,用途也算比较灵活。

第一条是古早的全局cache:

第二条是新增的绕过cache的规则:

用途也很简单,在需要绕过cache的url,后面统一加上?_cf_bypass_cache=1就行,原本有query string的也能用

还是用上面的url测试,可以看到cf-cache-status现在固定是dynamic了,对应的Range请求也没丢掉

大功告成(鼓掌鼓掌),可以丝滑加载了。

后面有个想法把data里面的数据迁到云存储上,走动态fetch,至于会不会这么做,以后再说