断点调试Chromium源码,验证不命中disk cache的问题

485 阅读6分钟

近期在公司做性能优化,发现一个神奇的现象,就是我们的js,总是无法命中disk cache,最多偶尔有memory cache,导致资源加载耗时比较严重,现象如下图 image.png cache-control头的设置是public, max-age=31536000;,按理应该缓存一年,但实际一分钟都没有...

了解immutable

最开始去找相关资料,发现基本是在讲cache-control: immutable的,说浏览器即使缓存了资源,但会启发式地尝试验证,如果设置immutable,就是告诉了浏览器,不要验证,放心用
不过根据 Reload, reloaded: faster and leaner page reloadsImplement Cache-Control: immutable,chrome并不支持,而且也不认可immutable会有很大价值
我们的现象很奇怪,是几乎一次都不会命中disk cache,而其他网站,基本是能命中的,感觉哪里有问题。但对比了CDN的响应头,并没发现什么异常,然后safari又是可以命中disk cache的

是不是没写cache到disk?

再搜了一通,发现深度解读chrome浏览器对资源文件的缓存规则里介绍的 chrome://net-export 可以观察详细的网络行为和日志,尝试了一把,发现在请求main.js时,是有DISK_CACHE_ENTRY行为写入了数据的

image.png

然后下一次访问页面,在请求main.js时,当解析到header的304后,接下来会有许多HTTP_CACHE_READ_DATA日志, 所以当协商请求完成后,浏览器确实读了disk cache里的main.js:

image.png

因此缓存的写入是正常的。只是浏览器好像始终会对main.js走协商逻辑,不会直接读取disk cache?

怀疑末尾的分号

然后我再仔细对比了一下,我们的js的cache-control头,最后有一个分号,别人的没有,问了gpt得到初步答复,这里应该有问题 image.png 后面找了关于规范的链接,里面提到格式规则是逗号分隔,可以有空格,未提到允许有分号,确认了分号不符合规范。不过也有同事觉得奇怪,难道chrome这点保护都不做?
能不能尝试验证一下?

浏览器插件

试了两个浏览器插件,ModHeader、Requestly,用是好用的,改header也免费,但是改了cache-control去掉了末尾分号后,DevTools的显示虽然变了,但浏览器行为没有变化,还是走了协商缓存

难道我的猜想错了?不甘心放弃,把cache-control改为no-store试试看,好家伙,浏览器行为依然没有变化,还是协商缓存,这就不对了,no-store时浏览器是不会缓存的,看来在这修改cache-control,是没用的,估计是插件改cache-control之前,浏览器的网络进程已经消费过了,改在了下游,无济于事

用Charles改应该是可以的,不过我司不允许使用,放弃...

本地起服务

有老哥建议可以本地起一个服务来验证,是个办法,不过我觉得并不一定可行,localhost域名下浏览器的行为可能有变化,不过成本较低,试试看

好家伙,直接把我干懵了,在这居然disk cache了….

image.png

各种排查对比,只发现分号这一处疑点,难道不是?还是不愿意放弃希望

先默默保留这个疑问…

测试其他CDN

此时传来了服务端老哥的好消息:和相关同事做了沟通,这个分号存在了很多年,但确实没有特殊意图,计划去掉

老哥建议我测试一下新CDN us01st-cf.zoom.us的行为,这个地址是去掉了分号的
测试结果比较正面,新CDN命中了disk cache,看起来大概率切到去了分号的新CDN后,disk cache可以命中了

Screenshot2024_10_16_171826.jpg

不过还是存在疑惑,为什么本地服务里带分号的也可以disk cache?是否还有其他影响因素?

调试Chromium源码

为了得到更准确的答案,我期望从Chromium源码里找找答案,根据从chrome源码解读cache缓存策略,我得到了部分变量名字,尝试去源码里搜搜找找线索

看源码

我使用git clone的方式下载了源码,在http_response_headers.cc里看到了部分相关逻辑,但其实不是很清晰,需要去各种子方法里来回确认,然后也不知道上游是否做了清洗处理,不是很能确认分号是否有影响

image.png

准备调试

还是要调试上才好,找了一些资料看了一圈:

Chromium源码学习(1)—— 构建,调试

Mac上本地编译Chrome浏览器笔记(2023.12更新)

Checking out and building Chromium for Mac

从文档来看,我上述的看源码操作算是走了一点弯路,拿到的源码好像比较旧,应该用官方推荐的depot_tools

跟着文档一通操作,感谢公司的高性能M3 Mac,编译速度没文档里说的那么夸张。由于我已经装了Xcode、python等环境,配合科学上网,过程还比较顺利,只遇到一个缺失LASTCHANGE.committime文件的问题,通过这里的方案,执行了下gclient sync --with_branch_heads --with_tags解决了

然后开始按预期编译了

使用Xcode调试

第二天,编译完成,打开得到的这个蓝色Chromium,刚用的时候仿佛开了0.5倍速

image.png

接着按照官方文档的推荐准备调试:Debugging Chromium on macOS,由于我是iOS出身,我选择Xcode来调试,梦回老本行。

Chromium是多进程架构,我要调试的是网络相关,要先找到网络进程的pid:在Chrome右上角三个点里,找More Tools - Task Manage,找到网络进程:

image.png

启动创建好的Xcode空工程,去Debug - Attach to Process里把网络进程97872关联上

image.png

然后设置好断点,访问main.js,等待断点进入

image.png

验证猜想

断点顺利触发,顺着堆栈观察,找到了解析max-age的方法,可以看到,确实因为分号的存在,导致GetMaxAgeValue最终返回了空:

image.png

然后,再换了一个百度的可以命中disk cache的js,可以顺利取到max-age的值,OK,看来稳了,切换新的不返回分号的CDN后,能确认可以解决不命中disk cache的问题

不过还没完全下班,为什么我本地起的服务,加载的main.js能命中disk cache?断点都架上了,就再看一眼吧

本地起的为什么可以?

看了下,上图的结论还是生效的,本地起的服务,加载的main.js,取到的max-age也是空,本来已经game over了

RFC 2616的13.4和14.9.4规定,当没有用must-revalidate明确声明使用缓存前必须校验的话,浏览器会尝试缓存成功的请求,把响应头Date减Last-Modified的值的十分之一,作为max-age,而在我本地起的服务的例子里,这两个头的差值有2天多,所以可以命中

image.png

而我们的网站上,这两者差值很小,所以没命中…

image.png

真相大白,坐等切到新CDN后,disk cache命中率上升 ⬆️