原文链接: https://code.facebook.com/posts/964122680272229/web-performance-cache-efficiency-exercise/
任何网站都会考虑性能,不管是当地的理发店或者有巨大知识库的维基百科。这是一个无法被忽略的需求。因此缓存变得尤其重要 —— 一种让网站变快的极佳途径,通过保存部分数据,使得下次访问时不用再次计算或者下载。
我们团队最近在讨论Facebook.com没有被缓存的的部分网页,问题就来了:Facebook每两天发布一次代码,缓存的效率有多高?我们是否发布代码太频繁导致浏览器的缓存没有充分利用?为了找到答案,我们在 Yahoo's Performance Research blog 发现一篇研究关于浏览器缓存对网页性能的影响的文章。
我们从中惊讶的发现一个悲观的结论:有20%的页面访问没有经过缓存。但是这份研究至今已超过8年,那时候浏览器还无法显示如顶部截图那样的瀑布式流量图,那时候IE7和jQuery才发布了几个月。我想忘记这份研究吧,jQuery 1.0 —— 老掉牙了。为了得到更精确的结果,我们决定重新测试看看事情是否有所改观。
在原来的研究中,Yahoo构造的一种带特殊头的图片。这些头会告诉浏览器如果同样的图片被请求两次,不会进行正常的请求,而是根据图片是否变动这个条件发送GET请求。这种GET请求会把最后修改时间的头传给服务器,如果请求时间和图片最后修改时间间隔太小,则返回304没有修改而是不是200成功。Yahoo最后查看服务器日志进行分析。
与之类似,我们构建了一个PHP终端来提供图片以及向数据库记录请求。图片附带特殊的头来控制浏览器的缓存和中间件的缓存,而且我们会在请求同时记录所有头的信息。而响应头如下:
Cache-Control: no-cache, private, max-age=0 ETag: abcde Expires: Thu, 15 Apr 2014 20:00:00 GMT Pragma: private Last-Modified: $now // RFC1123 format
在IE7和IE8下,为了绕过一些已知问题,需要特殊修改:
Cache-Control: private, max-age=0 Pragma: no-cache
当浏览器请求图片时,会没有附带或者附带一到两个额外头部:
Status: 200 Success
以及 image data ,然后浏览器会缓存这些内容。并生成 Last-Modified
时间和 ETag
值会已备下次使用。 if-none-match
和 if-modified-since
头的一个或两个,它说明浏览器之前获取过图片。服务器会返回 Status: 304 Not Modified
并且不包含图片数据。我们还把 Last-Modified
头设置为 $header['if-modified-since']
而不是 $now
,这样浏览器每次就能获得相同的响应内容。 最后的问题就是什么时候和在哪里发送图片请求。我们决定在Facebook搜索条旁边包含一个 img
标签,这样Facebook每次重载的时候就会渲染。在一个整个页面的重载中,内存资源会被卸载,然后浏览器根据缓存头重新请求CSS,JavaScript和我们的图片。所以这是测量缓存是否工作的最佳位置。
在准备好终端去记录请求日志和让 img
标签去发送请求之后,我们马上开始...
经过几周的数据收集和填满缓存,对比最后超过7天的数据。初次结果同样出乎意料:25.5%的请求没有命中缓存。我们把数据按接口分类,桌面和移动设备,但是结果相似:桌面版24.8%以及移动版的26.9%请求没有命中缓存。这不是期望的结果,所以我们继续深入调试。
把桌面数据按浏览器划分后变得一目了然。
上图显示了桌面浏览器一周内的缓存命中率。使用Chrome和Opera显然从浏览器缓存受益更多。你可能注意到Fireforx并没有出现在表中,这是有原因的。Firefox v31和早期版本用当前方法测试的缓存命中率为80%,过v32版本及以上却大大的下降。 v32 版本说明 解释了缓存后台会 记录并重用最近的响应头 。如果重用响应,我们的终端就没法收到请求并记录日志。这样测试会错误的认为Firefox表现糟糕。但实际上仍在命中本地缓存;只是无法统计信息。考虑到这点,我们把Firefox排除在测试结果之外。
让我们看看移动端的情况
不同产品的缓存命中在68%和84%之间浮动,和之前的线差不多。移动端的变动性更多,因为许多不同 year class 的设备在访问移动网页,而且每一个浏览器版本都有一个可能的范围。这些数字普遍比桌面版的低但是排序基本一致。
我们还可以看看不同用户命中空缓存的比例
平均44.6%的用户获取了空缓存。这佐证了Yahoo在2007年关于每个用户命中率的结果。
还没有完呢。在Facebook,我们希望小步快走的每天两次地发布包含当天完成的所有优秀功能。这就引出一个问题:浏览器缓存生命周期是多少?我们可以通过把请求头 if-modified-since
的值减去当前时间值,这样就可以得到这个用户命中缓存的存活时间。
所以我们发掘数据。仍然是最近一周的数据,我们生成描述缓存持续命中(请求返回304)时间分布的柱状图。换一种说法,每次重新获取图片的间隔有多长。
横轴 duration 单位是小时,竖线 p50 (百分之50), mean (平均)和 p75(百分之75)分别表示对应请求比例的缓存时间。比如,_p50 表示50%的请求在命中最多47小时之前生成的缓存,类似的 p75 表明25%的请求的缓存存在的时间至少有260个小时。在移动端做同样的分析显示有50%的请求的缓存不会超过12个小时。
总的来看缓存命中率相比2007年有所提高。如果忽略Firefox v32及以上版本(无法测试),这样缓存命中率由2007年的80%左右提高到现在的84.1%。另一方面,缓存保持活跃的时间并不是很长。根据我们的研究,在桌面版,有42%概率任何请求的缓存的存在时间不超过47小时。这是一个新的维度,而且在某些网站影响突出。
很容易解释为什么缓存存在时间通常很短。看看因特网如何传输和 网页大小 size 从2007年至今的变化。在2007年,家庭有线带宽是2.5Mbps,Yahoo主页168.1KB。如今,我有8Mbps的LTE移动下载带宽,Yahoo主页是768KB。现在网页平均大小是1MB,对浏览器优化造成了更大的压力。
因此利用好浏览器缓存仍然重要,如果用好带来的收益也会比8年前更大。我们的最佳实践是使用外部样式和脚本,包含Cache-Control和Etag头,传输层数据压缩,使用URL使缓存资源失效,把平凡更新的资源和稳定资源分开。所有这些技术都能在任何网站协同工作,而不单单是Facebook。我们一开始担心自己的发布流程会给缓存性能带来负面影响,但结果并不是这样。实际上,我们使用这份数据专注于改进让所有人访问 www.facebook.com 都能通过缓存。真是有意思的一次缓存挖掘之旅。
扫码关注w3ctech微信公众号