最常被遗忘的Web性能优化:浏览器缓存

一提起缓存,Web开发者们老是在想数据库缓存、页面静态化、使用Redis内存缓存。这些方法都有一个共性,就是集中在后台,目的就是加快数据的读取,少用比较容易产生瓶颈的部分。php

后台该优化的都优化到了最佳状态,却每每疏忽了一个很是重要的过程,就是数据传输。想着如何快速读取数据,却忘了如何减小请求数据,或者根本不请求数据。因此,今天咱们就来聊一聊这个常常被咱们遗忘的浏览器缓存。css

认识浏览器缓存

当浏览器请求一个网站的时候,会加载各类各样的资源,好比HTML文档、图片、CSSJS等文件。对于一些不常常变的内容,浏览器会将他们保存在本地的文件中,下次访问相同网站的时候,直接加载这些资源,加速访问。html

这些被浏览器保存的文件就被称为缓存。(不是指Cookie或者Localstorage)。web

那么如何知晓浏览器是读取了缓存仍是直接请求服务器?咱们就使用Segmentfault网站来作个示例(见下图)。chrome

图片描述

第一次打开该网站后,若是再次刷新页面。会发现浏览器加载的众多资源中,有一部分size有具体数值,然而还有一部分请求,好比图片、cssjs等文件并无显示文件大小,而是显示了from dis cache或者from memory cache字样。这就说明了,该资源直接从内存或者本地硬盘直接读取,而并无请求服务器。数据库

查看缓存

知道了浏览器从缓存中读取文件,那么浏览器缓存文件存储在哪里?以chrome为例,直接在浏览器地址栏输入:chrome://cache/便可打开近期的全部缓存文件连接,固然你能够直接点击打开缓存内容。segmentfault

至于背后的文件,通常存在于:C:\Users\yanying\AppData\Local\Google\Chrome\User Data\Default\Cache路径中,其中yanying是你的windows用户名称。windows

缓存协商

从上面的图片能够看出。一部分请求使用了缓存,而有一部分缓存并无使用缓存。浏览器若是想判断什么时候该作什么操做,就必需要有一个断定标准。这里就须要用到缓存协商。简单来讲就是Web浏览器和服务器之间协定一个法则,什么状况下请求资源,什么状况下不请求。后端

缓存协商方式和CookieUser-Agent同样,经过浏览器header进行传输。浏览器

缓存协商方式有3种:

  1. Last-Modified
  2. ETag
  3. Expires

Last-modified

定义

Last-Modified标签表明是文件的最后修改时间,其格式是标准的GMT时间。注意: GMT是标准的格林威治时间,咱们国家是GMT+8时区。因此,你看到的Last-Modified和咱们的时间有8个小时差距,不过不影响使用。

通常的动态资源没有所谓的最后修改时间。而静态文件好比css文件、图片等文件能够经过stat()系统调用得到文件的最后修改时间。

可是,实际网站运行中,Web服务器(好比Apache)会自动获取静态资源的最后修改时间,同时会自动在HTTP头文件中添加Last-Modified标签。静态资源的相应头文件以下图所示:

图片描述

包含了Last-Modified标签的资源,在下次的请求中,浏览器会带着该时间。当服务器接收到请求后会核对该时间后,文件是否被修改,若是修改了就直接返回数据,没有修改就直接返回304状态码,告知浏览器直接使用本地缓存。这样,一次数据传输流量就被免除了,速度稍有加快。

动态资源中使用

动态资源虽然没有相对意义上的最后修改时间,可是咱们仍是能够直接经过发送header头来手动定义Last-Modified。这样,经过动态程序判断,也能够达到静态资源节省数据传输流量的做用。

这里使用PHP举个例子:

一、首先建立一个php文件,发送一个Last-Modified头标签:

<?php
header("Last-Modified:".gmdate("D, d M Y H:i:s")." GMT");

二、使用浏览器请求该文件,咱们获得了以下的服务器返回头:

HTTP/1.1 200 OK
Date: Tue, 27 Jun 2017 15:13:02 GMT
Server: Apache/2.4.9 (Win32) PHP/5.5.12
X-Powered-By: PHP/5.5.12
Last-Modified: Tue, 27 Jun 2017 15:13:02 GMT
Content-Length: 0
Keep-Alive: timeout=5, max=97
Connection: Keep-Alive
Content-Type: text/html

观察上面服务器返回的头文件,包含了一个Last-Modified: Tue, 27 Jun 2017 15:13:02 GMT,这就是上面动态代码生成的最后修改时间。

三、当咱们再次请求该文件的时候,咱们看下浏览器发送给服务器的头文件。

GET /php/last.php HTTP/1.1
Host: localhost
Connection: keep-alive
Cache-Control: max-age=0
//...这里省略部分信息
If-Modified-Since: Tue, 27 Jun 2017 15:13:02 GMT

观察一下最后一行,多了一个If-Modified-Since标签,他的时间正是服务器刚刚返回的Last-Modified的值。这个值就这样又被返回给了服务器。

四、这样就很简单啦。在动态语言端(PHP)能够直接使用$_SERVER['HTTP_IF_MODIFIED_SINCE']便可获取时间值,接着就能够作一些简单的对比工做。若是在这个时间以后数据没有变化则直接返回304,告诉浏览器直接使用缓存,而免去数据传输的过程。

并且,最终要的是。这个过程根本无需查找数据库,因此后台程序执行时间很是短,从而大大减小用户等待时间。

这样咱们就作到了动态资源也能够实现静态资源的最后修改时间,从而减小数据传输量,达到优化性能要求。

Etag

Last-Modified缺点

Last-Modified彷佛已经作到了部分性能优化效果。可是,老是有些状况下不是很奏效。好比,一个用户修改了一个文件,后来用户以为修改错误,因而又修改回去。

上面的过程当中,文件内容并无发生变化。可是,文件在系统中的物理最后修改时间却发生了变化。这种状况下,若是浏览器再次请求资源。服务器仍是会发送完整数据。从而并未彻底达到咱们预想的效果。

因而在此之上,咱们还能够添加一个ETag标签,用来进一步确认文件是否修改。

了解ETag

ETag相似于Last-Modified,也是一个header头标签。他的值是一串字符串,用于区分各个文件的版本信息,因为HTTP并无对该值作任何的格式限制,因此能够自定义生成。

ETag的值不一样于Last-Modified,他并不会在文件被修改时候就发生变化,而是在文件内容发生变化的时候才会被改变(具体何时改变,彻底有后台业务逻辑来判断)。对于静态资源,Web服务器仍是会帮咱们处理好这个标签,不用考虑太多。

这里咱们截取了Segmentfault的一张图片的ETag,以下图:

图片描述

下面咱们仍是来讨论一下动态资源模拟静态资源发送ETag标签的过程:

一、这里咱们仍是新建一个PHP文件,其中代码是向浏览器发送一个包含ETag的头文件。

<?php
header("ETag : abcd");

二、使用浏览器请求该PHP文件:

看下服务器返回的header头:

HTTP/1.1 200 OK
Date: Wed, 28 Jun 2017 01:45:40 GMT
Server: Apache/2.4.9 (Win64) PHP/5.5.12
X-Powered-By: PHP/5.5.12
ETag: abcd
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

里面比正常的返回多了一个ETag标签,而且它的值就是咱们刚刚设置的abcd

三、下面咱们刷新浏览器,再次请求改页面:

注意观察下浏览器请求的头header

GET /etags.php HTTP/1.1
Host: localhost
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8
Cookie: Phpstorm-65418376=dceeb07b-c7af-45d6-b8be-4079e9424244; Hm_lvt_65dfcf8f1948f7203dd3fb620de01083=1497600508; admin_id=1; admin_token=072517cddaa9c106fe4662ea70a1345c
If-None-Match: abcd

仔细看下最后一行,有一个If-None-Match头标签。该标签的值正是咱们刚刚接收到的服务器返回的ETag的值,这样相似于Last-Modified,咱们在PHP端可使用$_SERVER['HTTP_IF_NONE_MATCH']直接获取咱们刚刚的值。

四、获取到该值,咱们就能够直接对比文件现有的ETag,来决定是直接使用浏览器缓存仍是再次发送完整数据。

小结

EtagLast-Modified很是类似,都是用来判断一个参数,从而决定是否启用缓存。可是ETag相对于Last-Modified也有他的优点,他能够更加准确的判断文件内容是否被修改,从而在实际操做中实用程度也更高。

有了这两种优化方式,对于节省流量带宽已经起到了很是大的做用。可是老是感受仍是有点儿鸡肋,毕竟每次浏览器仍是要来询问一下服务器,文件是否被改变。

若是,咱们能够肯定,一个文件在半年内不会改变,那么咱们可让浏览器在这半年时间内都不来服务器询问,而直接使用本地缓存。这里就须要使用第三种协商方式Expires.

Expires

Expires这个单词的意思是过时,在这里表示的是过时时间。它的使用方式、格式和Last-Modified同样,都是使用浏览器头,也都是标准的GMT时间。

可是它的功能却彻底不一样,包含了Expires头标签的文件,就说明浏览器对于该文件缓存具备很是大的控制权。例如,一个文件的Expires值是2020年的1月1日,那么就表明,在2020年1月1日以前,浏览器均可以直接使用该文件的本地缓存文件,而没必要去服务器再次请求该文件,哪怕服务器文件发生了变化。

因此,Expires是优化中最理想的状况,由于它根本不会产生请求,因此后端也就无需考虑查询快慢。

下面咱们看下segmentfault的静态文件的Expires

图片描述

对于静态资源,大多数服务器是会开启expires标记功能。若是遇到没有开启的,则可使用配置文件开启。

Apacheexpires支持设置以下:

<IfModule mod_expires.c>
    ExpiresActive on
    ExpiresByType image/gif "access plus 1 month"
    ExpiresByType text/css "now plus 2 day"
    ExpiresDefault "now plus 1 day"
</IfModule>

上面的配置中咱们设置image/gif的格式图片缓存时间为1个月,而css文件缓存时间为2天,其余的默认为1天。

另外,对于经常使用静态资源。若是不在web服务器端设置expires标签,浏览器也能够智能的标记一个过时时间。好比gif图片,浏览器会设置他的过时时间为永不过时。

动态资源中使用Expires

这里咱们仍是拿PHP来举例

一、首先建立一个PHP文件,用于发送Expires头标签。

这里咱们把文件过时时间直接设置为2020年1月1日的0点

<?php
header("Expires:".gmdate("D, d M Y H:i:s",1577808000)." GMT");

二、使用浏览器请求该文件,观察服务器返回的头文件:

HTTP/1.1 200 OK
Date: Wed, 28 Jun 2017 02:24:18 GMT
Server: Apache/2.4.9 (Win64) PHP/5.5.12
X-Powered-By: PHP/5.5.12
Expires: Tue, 31 Dec 2019 16:00:00 GMT
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

不出意外,咱们已经在头文件里面发现Expires标签,而且它的值为Tue, 31 Dec 2019 16:00:00 GMT(这里不是2020年缘由是因为有8个小时时差)。

三、再次使用浏览器访问改页面,发现浏览器的请求数据的路径已经变为了from cache(以下图)。

图片描述

对于chrome浏览器,从network中彷佛并不能看出是否使用了缓存。可是,若是打开chrome://cache/,搜索咱们刚刚的地址,会发现咱们请求的内容也被缓存成功。

图片描述

请求方式与缓存

浏览器有3种请求服务器资源的方式

  1. ctrl+f5:强制刷新
  2. f5:刷新页面
  3. 浏览器地址栏回车,也就是转到功能

这3种请求方式对于资源使用缓存的影响各不不一样,下面一一的解释:

一、ctrl + f5:强制刷新

这种方式是全部加载方式中使用缓存最少的方式。当使用ctrl+f5访问一个地址的时候,浏览器会强制全部的资源从新加载一次。全部的资源将会被从新缓存。

二、f5:刷新页面

f5刷新页面至关于浏览器上面的刷新按钮,是一种比较经常使用的刷新方式。这种方式下浏览器会使用部分必要的缓存,针对于Last-Modified有效,可是expires标签就会失去他的做用。

三、地址栏转到方式

在浏览器地址栏输入即将访问的地址后,按回车或者浏览器转到功能访问网页。这是使用最多的一种状况,也是使用最少请求服务器的方式。也就说浏览器会尽可能使用本地缓存,而避免直接请求服务器数据。注意: Expires标签也只有在这种状况下有效。因此,千万不要使用f5或者ctrl+f5还奇怪expires功能无效。

cache-control

还有一点点小缺陷

了解了上面全部的缓存协商方式后,咱们已经能够高效的优化咱们现有的应用。可是仍是存在一种可能状况,那就是以前的Last-Modifiedexpires都是使用服务器标准时间来标记。

而做为最后的判断者确是浏览器。因此,不免会存在用户电脑时间和服务器时间不一致的状况。

好比咱们设定一个资源在将来10分钟内不会过时,而用户电脑比服务器时间快了1个小时(固然这个太少见)。那么咱们设置的过时时间对于用户来说,当即就过时了。那么咱们的设置至关于白用功了。

因此为了解决这个可能出现的小缺陷,咱们还能够设置一个相对于用户本地时间的缓存过时时间cache-control

做用

cache-control和以前的Last-Modified同样,都是头文件里面的一个标签。只不过他的值是max-age=<second>,这里的<second>是一个数字,单位为秒。

假设咱们设置一个值cahce-control:max-age=3600,那么就表明改缓存有效期是用户本地时间加上3600秒。这样,缓存的截止时间就和服务器时间没有太大关系了,从而避免了由于时间误差带来的不良影响。

对于静态文件,若是服务器好比Apache开启了expires功能,那么也会默认的给头文件添加一个cache-control标签。

PHP设置cache-control

对于动态文件,咱们能够在程序语言中向浏览器直接输出该标签。咱们使用PHP作一个演示:

一、建立一个PHP文件,向浏览器输出一个包含cache-control标签的头:

<?php
header("Cache-Control:max-age=3600");

二、使用浏览器请求该PHP文件,获取服务器返回头header

HTTP/1.1 200 OK
Date: Wed, 28 Jun 2017 12:33:16 GMT
Server: Apache/2.4.9 (Win32) PHP/5.5.12
X-Powered-By: PHP/5.5.12
Cache-Control: max-age=3600
Content-Length: 0
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Content-Type: text/html

观察上面的信息,能够发现其中包含cache-control标签,其值为咱们刚刚设置的max-age=3600,那么就表明相对于我本地时间3600秒以后缓存过时。(完)

严颖,2017.6.28

我的主页:segmentfault

推荐一个咱们团队本身开发的针对开发者的网址导航:笔点导航 - 用心作最简洁的网址导航

能够自定义网址
能够自定义分类
分类能够标记颜色
自定义皮肤
自定义搜索
网址拖拽排序
自定义插件小模块

图片描述

相关文章
相关标签/搜索