在阅读本文前推荐你先阅读我的前两篇文章《 扼杀 304,Cache-Control: immutable》和《关于缓存和 Chrome 的“新版刷新”》;下面要说的两个问题是在淘宝(包括天猫等等)任意主流页面中都存在的,所以你可以随便打开一个页面进行测试;这两个问题我去年在微博上都简单提到过,这里做一下梳理总结。

一. 部分图片文件始终 304,无法直接读取缓存

淘宝网站上什么类型的请求最多?当然是图片了。拿淘宝首页举例,在 Chrome 的新标签页中先打开开发者工具,再打开淘宝首页,然后滚动到页面最底部,在开发者工具的网络面板中点击 Img 筛选条件后能够看到左下角有类似如下的数字:

有高达 80% 的请求数都是图片,淘宝的其它主流页面也有同样的规律。然而在这些图片中,有一部分图片无法直接读取浏览器缓存,即便已经被下载过,浏览器也要再发个条件请求,在收到 CDN 返回的空的 304 响应后再读取缓存:

<div><a href="javascript:location+=''">点击该链接,从而使当前页面重新加载,下面的两张图片应该直接读取缓存,不发起任何 HTTP 请求</a></div>
<img width=100 src="http://images2015.cnblogs.com/blog/116671/201702/116671-20170222201116820-1249825884.png">
<img width=100 src="https://img.alicdn.com/imgextra/i2/272205633/TB2J1fpaurAQeBjSZFwXXa_RpXa_!!272205633.jpg">

上面这个 demo 中有两张图, 一张是存在淘宝 CDN 上的,另一张是我转存到博客园的。当你点击测试链接时会发现,博客园上的图片能够直接读取缓存(没发送请求),而淘宝 CDN 上的那张图产生了个 304 请求(响应码为 304 的请求):

这个视频演示有下面三个要关注的点:

1. 那个 304 请求的响应有 49 个字节。

2. 那个 304 请求的响应时间为十几毫秒到几百毫秒不等。

3. 页面中直接读取缓存的图片丝毫不动,而经过 304 后再读取缓存的图片有明显的闪动。

别看请求头加上响应头也就 100 个字节,但架不住多啊,这种图片的日 pv 我估计至少得用百亿做单位,浪费的日流量得用 T 作单位,这种小钱也许对淘宝这种大厂来说完全可以忽略,但用户体验却是没法忽略的,在理想的网络情况下图片展现都有明显的闪动,那在移动端或者网络环境较差的 PC 端,延时就会更加明显了。

原因是什么?让我们看看这张图片的响应头(已经删掉了 x- 开头的):

$ curl -I 'https://img.alicdn.com/imgextra/i2/272205633/TB2J1fpaurAQeBjSZFwXXa_RpXa_!!272205633.jpg'

HTTP/1.1 200 OK

Server: Tengine

Content-Type: image/jpeg

Content-Length: 94506

Connection: keep-alive

Date: Mon, 31 Oct 2016 02:43:49 GMT

last-modified: Fri, 09 Sep 2016 08:42:30 GMT

Cache-Control: max-age=3600, s-maxage=31536000

Access-Control-Allow-Origin: *

Via: cache64.l2et2[0,200-0,H], cache23.l2et2[21,0], cache4.cn395[0,200-0,H], cache3.cn395[0,0]

Age: 9938377

Timing-Allow-Origin: *

EagleId: 8ccd3b4314878202062633285e

问题就出在标红的这两个头上,浏览器看一个缓存有没有过期是通过看 Date 头返回的时间点加上 Cache-Control 头中 max-age 字段指定的时间段算出的时间点有没有小于客户端的时间,也就是说看那个算出的时间是不是还没有到来。对于这张图片的话,过期时间可以通过如下的 JS 代码计算出来:

new Date(+new Date("Mon, 31 Oct 2016 02:43:49 GMT") + 3600) + "" // "Mon Oct 31 2016 10:43:52 GMT+0800 (CST)"

其实这个例子根本不需要笔算,口算都能算出来,2016 年 10 月份的某个时刻加上一个小时肯定还是 2016 年,小于我的客户端时间 2017 年,所以浏览器刚刚获取到这张图片就已经过期了。Firefox 有个内部调试工具可以看到每个缓存的过期时间,在 Firefox 中打开那张图片后再打开 about:cache-entry?storage=disk&context=&eid=&uri=https://img.alicdn.com/imgextra/i2/272205633/TB2J1fpaurAQeBjSZFwXXa_RpXa_!!272205633.jpg 页面:

expires 字段就是 Firefox 计算出来的过期时间,没有显示 2016 年是因为如果 Firefox 计算出的过期时间是过去的某个时间,会用当前时间来代替。

所以问题就是为什么 CDN 返回的 Date 会是 2016 年?是因为这张图片是在 2016 年回源的,回源的时候 CDN 缓存了当时图片源站返回的 Date 头,作为以后给浏览器返回的 Date 头,所以用户浏览器接受到的 Date 就固定在了 2016 年。

既然是 CDN 的问题,为什么 CDN 上的其它图片和文件没有同样的表现?是因为 Cache-Control。这类图片的 Cache-Control 有个特点,那就是 max-age 比 s-maxage 小,我们知道 max-age 是 CDN 给浏览器用的,而 s-maxage 是源站给 CDN 用的,max-age=3600, s-maxage=31536000 代表的含义就是浏览器只能缓存这张图片一小时,而 CDN 会缓存这张图片一年,所以只有等到了 Mon, 31 Oct 2017 02:43:49 GMT 年的时候,这张图片的 Date 响应头才会更新,也就是对于用户来说,一年中有 364 天 23 小时 访问这张图片都是直接过期的。

因此只要 max-age 不比 s-maxage 小,就不会有这种下载立刻过期的情况,比如 Cache-Control: max-age=2592000,s-maxage=3600,或者完全不指定 s-maxage,Cache-Control: max-age=31536000 都行。那 max-age 比 s-maxage 小就完全是错的吗?我觉的并不是,我猜测这么设置的理由是:这些图片有更新的需求,所以给浏览器设置的缓存时间是一小时,给 CDN 设置的缓存时间是一年是因为更新图片毕竟是小概率事件,不是大批量的,所以都是通过 CDN 提供的 purge 接口进行强制回源的,不需要 CDN 因资源过期发起大量主动回源。

所以我个人觉的 CDN 返回的 Date 响应头应该使用 CDN 服务器的当前时间,而不是用缓存的陈旧的源站时间。

二. 大部分 JS/CSS 文件在页面刷新时无法发送条件请求

也就是无法产生 304 响应。上篇文章已经说过了,Chrome 在页面刷新时已经不再为子资源文件发送条件请求了(直接读取缓存),但在国内,国产浏览器才是王道,尤其是移动端(没几个用 Chrome 的),目前这些浏览器仍然会在刷新时为页面中的 JS/CSS 文件发起条件请求。

如果你使用 Chrome 56(当前稳定版本)的话,需要把 chrome://flags/#enable-non-validating-reload-on-normal-reload 调成已停用(更高版本的 Chrome 已经没有这个选项,请换个浏览器),再运行下面的 demo:

<div>刷新当前页面,两个 JS 文件都应该是 304 响应</div>
<script src="http://common.cnblogs.com/script/jquery.js"></script>
<script src="https://g.alicdn.com/kissy/k/1.4.2/seed-min.js"></script>

上面这个 demo 中有两个 JS 文件,一个是淘宝 CDN 上的 KISSY,一个是博客园上的 jQuery。当你刷新页面时,会发现 jQuery 这个请求的确是 304 响应,而 KISSY 这个每次是 200,也就是像完全没缓存一样:

上面的视频演示中,我为了模拟较差的网络环境,故意将网速节流成了 3G 模式。在 waterfall 列里可以看到,获取完整响应的 200 请求比只拿响应头的 304 请求多了 100 多毫秒的加载时间(蓝色条部分)。考虑到现在的网页没有 JS/CSS 基本什么都展现不了,所以这个问题会让页面刷新后的白屏时间大幅增加。

原因是什么?我们看一下这个 JS 文件的响应头(已经删掉了 x- 开头的):

$ curl -I 'https://g.alicdn.com/kissy/k/1.4.2/seed-min.js'

HTTP/1.1 200 OK

Server: Tengine

Content-Type: application/javascript

Content-Length: 44971

Connection: keep-alive

Date: Thu, 23 Feb 2017 05:50:47 GMT

Vary: Accept-Encoding

Accept-Ranges: bytes

Cache-Control: max-age=2592000,s-maxage=3600

Access-Control-Allow-Origin: *

Via: cache16.l2eu6-1[0,200-0,H], cache17.l2eu6-1[1,0], cache4.cn298[0,200-0,H], cache5.cn298[1,0]

Age: 2605

Timing-Allow-Origin: *

EagleId: 8ccd84cd14878316529776698e

问题就在,响应头里没有 Last-Modified 和 ETag,因此浏览器没法生成 If-Modified-Since 和 If-None-Match 请求头,所以没法发送条件请求,只能发个普通的非条件请求。

刷新并不算是极端情况,比如移动端的下拉刷新,是很常见的,因此刷新的用户体验也是需要保障的。

淘宝网站上的 HTTP 缓存问题两则的更多相关文章

  1. 如何:使用PicturBox实现类似淘宝网站图片的局部放大功能

    转载至http://xuzhihong1987.blog.163.com/blog/static/267315872011822113131823/ 概要: 本文将讲述如何使用PictureBox控件 ...

  2. Android仿淘宝继续上拉进入商品详情页的效果,使用双Fragment动画切换;

    仿淘宝继续上拉进入商品详情页的效果,双Fragment实现: 动画效果: slide_above_in.xml <?xml version="1.0" encoding=&q ...

  3. 仿淘宝头像上传功能(三)——兼容 IE6 浏览器。

    前两篇目录: 仿淘宝头像上传功能(一)——前端篇. 仿淘宝头像上传功能(二)——程序篇. 仿淘宝头像上传功能(三)——兼容 IE6 浏览器 之前的这两篇虽然实现了功能,但不兼容低版本浏览器,而且有些浏 ...

  4. 淘宝UED上关于chrome的transition闪烁问题的解决方案

    前段时间,有同事和会员反馈使用Chrome访问淘宝首页会出现画面闪动的现象,但是我在Mac和Win下面的Chrome都无法重现这个问题,后来重装了一遍Win7下的Chrome Beta版本,终于重现了 ...

  5. 【转】淘宝UED上关于chrome的transition闪烁问题的解决方案

    最近在用BetterScroll实现一个功能的时候,在滚动区域中会有一个绝对定位的按钮,结果在IOS中出现了快速滚动,停止的时候,会先消失后显现的问题,所以查找了相关的文章,发现是transition ...

  6. 【Python3 爬虫】14_爬取淘宝上的手机图片

    现在我们想要使用爬虫爬取淘宝上的手机图片,那么该如何爬取呢?该做些什么准备工作呢? 首先,我们需要分析网页,先看看网页有哪些规律 打开淘宝网站http://www.taobao.com/ 我们可以看到 ...

  7. (转)从P1到P7——我在淘宝这7年

    (一) 2011-12-08 [原文链接] 今天有同事恭喜我,我才知道自己在淘宝已经七周年了.很多人第一句话就是七年痒不痒,老实说,也曾经痒过,但往往都是一痒而过,又投入到水深火热的工作中去.回家之后 ...

  8. 从P1到P7——我在淘宝这7年(转)

    作者: 赵超  发布时间: 2012-02-25 14:47  阅读: 114607 次  推荐: 153   [收藏] (一) 2011-12-08 [原文链接] 今天有同事恭喜我,我才知道自己在淘 ...

  9. 从P1到P7——我在淘宝这7年 - 子柳撰写

    http://kb.cnblogs.com/page/132752/来自博客园的整理版本,作者是子柳,博客地址:http://blog.sina.com.cn/calvinzhaoc (一) 2011 ...

随机推荐

  1. 高性能队列——Disruptor

    背景 Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级).基于Disruptor开发的系统单线程能 ...

  2. Ubuntu 18.04.1 下快速搭建 LNMP环境

    1.Nginx的安装 Nginx安装是属于最简单的,只需要在命令行执行 sudo apt-get install nginx 就能自动安装 Nginx,其中过程中需要 选择 Y/n 的选择Y就行了,当 ...

  3. 从开始到头皮炸裂的python第5天

    头皮炸裂的一天从学到一个新的数据类型开始,这个数据类型的新成员叫做字典,基本的格式为data={键:值,键:值},info.keys()表示所有的键,info.values()表示所有的值,info. ...

  4. 一 Struts2 开发流程

    SSH与SSM简介SSM:Spring+SpringMVC+MybatisSSH:Struts2+Hibernate+SpringStruts2:是侧重于控制层的框架Hibernate:是一个ORM( ...

  5. 【Git】+ 新建+删除+上传+覆盖

    上传代码时邮箱格式不符合:https://blog.csdn.net/u012558695/article/details/64921922 在本地新建一个分支: git branch newBran ...

  6. 在Mac OS X下使用Apache、PHP、MySQL、Netbeans、Yii

    本文环境: Mac OS X:10.8.4 Apache:2.2.22 PHP:5.3.15 Netbeans:7.3.1 Yii:1.1.14 Mac OS X是内置了Apache服务器的,不过默认 ...

  7. 洛谷P4431

    题意翻译 题目大意: 给定一个n∗m的矩阵,每次你可以选择前进一格或转弯(90度),求在不出这个矩阵的情况下遍历全部格点所需最少转弯次数.有多组数据 输入格式: 第一行一个整数k,表示数据组数 以下k ...

  8. PyInstaller Extractor安装和使用方法

    PyInstaller Extractor是可以提取出PyInstaller所创建的windows可执行文件的资源内容. 关于PyInstaller的介绍:PyInstaller安装使用方法 使用Py ...

  9. python购物车demo

    product_list = [        ('Iphone',11800),        ('Mac Pro',13800),        ('BMW CAR',480000),      ...

  10. 如何查杀stopped进程

    在Linux系统下面,top命令可以查看查看stopped进程.但是不能查看stopped进程的详细信息.那么如何查看stopped 进程,并且杀掉这些stopped进程呢? ps -e j | gr ...