热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

https:www.sohu.coma153567485_505818一提起缓存, Web开发者们总是在想数据库缓存、页面静态化、使用 Redis内存缓存。这些方法都有一个共性,就

https://www.sohu.com/a/153567485_505818

 

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

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

认识浏览器缓存

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

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

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

技术分享图片

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

查看缓存

知道了浏览器从缓存中读取文件,那么浏览器缓存文件存储在哪里?以 chrome为例,直接在浏览器地址栏输入: chrome://cache/即可打开近期的所有缓存文件链接,当然你可以直接点击打开缓存内容。

至于背后的文件,一般存在于: C:UsersyanyingAppDataLocalGoogleChromeUserDataDefaultCache路径中,其中 yanying是你的 windows用户名称。

缓存协商

从上面的图片可以看出。一部分请求使用了缓存,而有一部分缓存并没有使用缓存。浏览器如果想判断何时该做什么操作,就必须要有一个判定标准。这里就需要用到缓存协商。简单来说就是 Web浏览器和服务器之间协定一个法则,什么情况下请求资源,什么情况下不请求。

缓存协商方式和 COOKIE、 User-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举个例子:

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



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


2、使用浏览器请求该文件,我们得到了如下的服务器返回头:


  1. HTTP/1.1200OK


  2. Date:Tue,27Jun201715:13:02GMT


  3. Server:Apache/2.4.9(Win32)PHP/5.5.12


  4. X-Powered-By:PHP/5.5.12


  5. Last-Modified:Tue,27Jun201715:13:02GMT


  6. Content-Length:0


  7. Keep-Alive:timeout=5,max=97


  8. Connection:Keep-Alive


  9. Content-Type:text/html


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

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


  1. GET /php/last.php HTTP/1.1


  2. Host:localhost


  3. Connection:keep-alive


  4. Cache-Control:max-age=0


  5. //...这里省略部分信息


  6. If-Modified-Since:Tue,27Jun201715:13:02GMT


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

4、这样就很简单啦。在动态语言端( 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标签的过程:

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



  1. header("ETag : abcd");


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

看下服务器返回的 header头:


  1. HTTP/1.1200OK


  2. Date:Wed,28Jun201701:45:40GMT


  3. Server:Apache/2.4.9(Win64)PHP/5.5.12


  4. X-Powered-By:PHP/5.5.12


  5. ETag:abcd


  6. Content-Length:0


  7. Keep-Alive:timeout=5,max=100


  8. Connection:Keep-Alive


  9. Content-Type:text/html


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

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

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


  1. GET /etags.php HTTP/1.1


  2. Host:localhost


  3. Connection:keep-alive


  4. Cache-Control:max-age=0


  5. Upgrade-Insecure-Requests:1


  6. User-Agent:Mozilla/5.0(WindowsNT 10.0;Win64;x64)AppleWebKit/537.36(KHTML,like Gecko)Chrome/59.0.3071.86Safari/537.36


  7. Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8


  8. Accept-Encoding: gzip, deflate, br


  9. Accept-Language: zh-CN,zh;q=0.8


  10. COOKIE: Phpstorm-65418376=dceeb07b-c7af-45d6-b8be-4079e9424244; Hm_lvt_65dfcf8f1948f7203dd3fb620de01083=1497600508; admin_id=1; admin_token=072517cddaa9c106fe4662ea70a1345c


  11. If-None-Match: abcd


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

4、获取到该值,我们就可以直接对比文件现有的 ETag,来决定是直接使用浏览器缓存还是再次发送完整数据。

小结

Etag和 Last-Modified非常相似,都是用来判断一个参数,从而决定是否启用缓存。但是 ETag相对于 Last-Modified也有他的优势,他可以更加准确的判断文件内容是否被修改,从而在实际操作中实用程度也更高。

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

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

Expires

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

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

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

下面我们看下 segmentfault的静态文件的 Expires:

技术分享图片

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

Apache的 expires支持设置如下:



  1. ExpiresActive on


  2. ExpiresByType image/gif "access plus 1 month"


  3. ExpiresByType text/css "now plus 2 day"


  4. ExpiresDefault "now plus 1 day"



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

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

动态资源中使用Expires

这里我们还是拿 PHP来举例

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

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



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


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


  1. HTTP/1.1200OK


  2. Date:Wed,28Jun201702:24:18GMT


  3. Server:Apache/2.4.9(Win64)PHP/5.5.12


  4. X-Powered-By:PHP/5.5.12


  5. Expires:Tue,31Dec201916:00:00GMT


  6. Content-Length:0


  7. Keep-Alive:timeout=5,max=100


  8. Connection:Keep-Alive


  9. Content-Type:text/html


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

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

技术分享图片

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

技术分享图片

请求方式与缓存

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


  1. ctrl+f5:强制刷新


  2. f5:刷新页面


  3. 浏览器地址栏回车,也就是转到功能


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

1、ctrl + f5:强制刷新

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

2、f5:刷新页面

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

3、地址栏转到方式

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

cache-control 还有一点点小缺陷

了解了上面所有的缓存协商方式后,我们已经可以高效的优化我们现有的应用。但是还是存在一种可能情况,那就是之前的 Last-Modified和 expires都是使用服务器标准时间来标记。

而作为最后的判断者确是浏览器。所以,难免会存在用户电脑时间和服务器时间不一致的情况。

比如我们设定一个资源在未来10分钟内不会过期,而用户电脑比服务器时间快了1个小时(当然这个太少见)。那么我们设置的过期时间对于用户来讲,立即就过期了。那么我们的设置相当于白用功了。

所以为了解决这个可能出现的小缺陷,我们还可以设置一个相对于用户本地时间的缓存过期时间 cache-control。

作用

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

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

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

PHP设置cache-control

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

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



  1. header("Cache-Control:max-age=3600");


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


  1. HTTP/1.1200OK


  2. Date:Wed,28Jun201712:33:16GMT


  3. Server:Apache/2.4.9(Win32)PHP/5.5.12


  4. X-Powered-By:PHP/5.5.12


  5. Cache-Control:max-age=3600


  6. Content-Length:0


  7. Keep-Alive:timeout=5,max=98


  8. Connection:Keep-Alive


  9. Content-Type:text/html


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


推荐阅读
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 单页面应用 VS 多页面应用的区别和适用场景
    本文主要介绍了单页面应用(SPA)和多页面应用(MPA)的区别和适用场景。单页面应用只有一个主页面,所有内容都包含在主页面中,页面切换快但需要做相关的调优;多页面应用有多个独立的页面,每个页面都要加载相关资源,页面切换慢但适用于对SEO要求较高的应用。文章还提到了两者在资源加载、过渡动画、路由模式和数据传递方面的差异。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • 本文介绍了OkHttp3的基本使用和特性,包括支持HTTP/2、连接池、GZIP压缩、缓存等功能。同时还提到了OkHttp3的适用平台和源码阅读计划。文章还介绍了OkHttp3的请求/响应API的设计和使用方式,包括阻塞式的同步请求和带回调的异步请求。 ... [详细]
  • LVS实现负载均衡的原理LVS负载均衡负载均衡集群是LoadBalance集群。是一种将网络上的访问流量分布于各个节点,以降低服务器压力,更好的向客户端 ... [详细]
  • .NetCoreWebApi生成Swagger接口文档的使用方法
    本文介绍了使用.NetCoreWebApi生成Swagger接口文档的方法,并详细说明了Swagger的定义和功能。通过使用Swagger,可以实现接口和服务的可视化,方便测试人员进行接口测试。同时,还提供了Github链接和具体的步骤,包括创建WebApi工程、引入swagger的包、配置XML文档文件和跨域处理。通过本文,读者可以了解到如何使用Swagger生成接口文档,并加深对Swagger的理解。 ... [详细]
author-avatar
万象新动HR
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有