本文主要介绍缓存的各种类型及在我们工作中的使用技巧。
什么是Web缓存
先简单通过几个小例子,来简单描述一下。
场景1
测试妹子或者是qa小哥哥测功能时会说为什么我的浏览器的显示乱七八糟,我的界面怎么跟别人浏览器上不一致?旁边的人会提醒说:清下缓存试试。场景2
开发改了代码,上了环境,发现不生效,这时候首先就是清缓存,清了浏览器缓存发现还是不行,再检查,发现是反向代理缓存。
那么当我们说web缓存的时候,我们说的是什么?什么地方可以缓存,什么时候用什么类型的缓存。如果缓存使用不当的话,又会带来什么样的问题,我们又该如何去避免?
其实,缓存就是把数据或者是我们需要取到的内容,放到能更快访问的地方。缓存对于前后端开发者来说,我们使用缓存都是为了能够更好地提升性能。
其中Web缓存就是为了提升Web页面访问的性能,把能缓存的页面或者数据缓存到能够更快取到的地方。
Web缓存的类型
当一个浏览器发起请求时,会经过上图几个步骤,那么缓存的地方其实就是上图中的浏览器、反向代理、cdn、数据库等等。
所以Web 缓存大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。
数据库缓存
数据库缓存是指,当web应用的关系比较复杂,数据库中的表很多的时候,如果频繁进行数据库查询,很容易导致数据库不堪重荷,网站显示延迟等问题。为了提供查询的性能,将查询后的数据放到内存中进行缓存,下次查询时,可以直接从内存缓存返回,提高响应效率,缓存数据库压力。
常用的数据库缓存比如配合使用memcache、redis这种高性能的分布式内存缓存服务器。
如果使用缓存的话,需要注意 缓存数据和真实数据源数据 的一致性。
服务器缓存
服务器缓存主要分为代理服务器缓存和CDN缓存。
代理服务器缓存
代理服务器是浏览器和源服务器之间的中间服务器,浏览器先向这个中间服务器发起Web请求,经过处理后(比如权限验证,缓存匹配等),再将请求转发到源服务器。代理服务器缓存的运作原理跟浏览器的运作原理差不多,只是规模更大。
CDN缓存
CDN缓存一般是由网站管理员自己部署,为了让他们的网站更容易扩展并获得更好的性能。通常情况下,浏览器先向CDN网关发起Web请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态将请求转发到合适的源服务器上。从浏览器角度来看,整个CDN就是一个源服务器,从这个层面来说,浏览器和服务器之间的缓存机制,在这种架构下同样适用。
浏览器缓存
浏览器缓存是所有web应用中都会使用的,每个浏览器都实现了 HTTP 缓存,我们通过浏览器使用HTTP协议与服务器交互的时候,浏览器就会根据一套与服务器约定的规则进行缓存工作。我们可以通过浏览器提供的开发者工具来查看。
浏览器缓存的类型很多,HTTP 缓存、indexDB、cookie、localstorage 等等。
以Chrome浏览器为例,F12打开浏览器开发者工具,再选择“Application”,可以看到所有的缓存类型,如下图所示,我们可以看到有HTTP文件缓存(Frames下),Local Storage,Session Storage,indexDB,Web SQL,Cookie,CacheStorage,Application Cache等等。
浏览器请求流程
从这张流程图可以看出,浏览器在发送文件请求时,可以根据协议头判断从服务器端请求文件还是从本地缓存读取文件,影响浏览器的文件缓存主要有几个属性:expires、Etag、Last-Modified,这三个属性是由http协议定义的。
Expires
用于设置静态资源的过期时间。它的值一个GMT格式的时间字符串,比如expires:Fri, 27 Jul 2029 13:38:54 GMT
。这个时间代表着这个资源的失效时间,在此时间之前,即命中缓存。这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。于是发展出了Cache-Control。
Cache-Control
Cache-Control是一个相对时间,例如Cache-Control:3600
,代表着资源的有效期是3600秒。由于是相对时间,并且都是与客户端时间比较,所以服务器与客户端时间偏差也不会导致问题。可以用于控制是否缓存、缓存的读取权限、资源的有效期。只不过Cache-Control的选择更多,设置更细致,如果同时设置的话,其优先级高于Expires。
Cache-Control可以由多个字段组合而成。下面简单挑几个来说明:
- public 指示响应数据可以被任何客户端缓存
- private 指示响应数据可以被非共享缓存所缓存。这表明响应的数据只能被单个用户(可能是操作系统用户、浏览器用户)缓存,不能被代理服务器缓存。
- no-cache 指示响应数据不能被任何接受响应的客户端所缓存,强制从服务器进行拉取。可用在请求头和响应头中
- no-store 指示所传送的响应数据除了不能被缓存,也不能存入磁盘。一般用于敏感数据,以免数据被复制。
- must-revalidate 指示所有的缓存都必须重新验证,在这个过程中,浏览器会发送一个If-Modified-Since头。如果服务器程序验证得出当前的响应数据为最新的数据,那么服务器应当返回一个304 Not Modified响应给客户端,否则响应数据将再次被发送到客户端。
- proxy-revalidate 与must-revalidate相似,不同的是用来指示共享缓存。
- max-age:(单位秒) 数据经过max-age设置的秒数后就会失效,相当于HTTP/1.0中的Expires头。如果在一次响应中同时设置了max-age和Expires,那么max-age将具有较高的优先级。(注:ngnix设置expires会被转换为max-age)
Last-Modified/If-Modified-Since
浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间,例如last-modified:Fri, 20 Dec 2019 03:34:57 GMT
。
当浏览器再次请求该资源时,当资源过期(使用Cache-Control标识的max-age),发现资源具有Last-Modified声明,则再次向web服务器请求时带上头 If-Modified-Since,表示请求时间。web服务器收到请求后发现有头If-Modified-Since 则与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。
Etag/If-None-Match
与Last-Modify/If-Modify-Since不同的是,Etag/If-None-Match返回的是一个校验码(ETag: entity tag)。ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。ETag值的变更则说明资源状态已经被修改。服务器根据浏览器上发送的If-None-Match值来判断是否命中缓存。
Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器定义)。nginx中,etag会默认增加,如果需要关闭,需要在配置文件中设置:etag off;
If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match (Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304。
ETag 扩展说明
我们对ETag寄予厚望,希望它对于每一个url生成唯一的值,资源变化时ETag也发生变化。神秘的Etag是如何生成的呢?以Apache为例,ETag生成靠以下几种因子
- 文件的i-node编号,此i-node非彼iNode。是Linux/Unix用来识别文件的编号。是的,识别文件用的不是文件名。使用命令’ls –I’可以看到。
- 文件最后修改时间
- 文件大小
生成Etag的时候,可以使用其中一种或几种因子,使用抗碰撞散列函数来生成。所以,理论上ETag也是会重复的,只是概率小到可以忽略。
既生Last-Modified何生Etag?
你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:
- Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间
- 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存
3.有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形
Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。
用户行为与缓存
浏览器的缓存除了和相关的http协议规则有关外,还与用户行为有关。
用户操作 | Expires/Cache-Control | Last-Modified/Etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进、后退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
Ctrl+F5刷新 | 无效 | 无效 |
如何控制缓存
设置缓存的两种方式:
1、web服务器配置
以ngnix为例,在nginx.conf中设置:
location~ .*\.(gif|jpg|png|htm|html|css|js|flv|ico|swf)(.*){
expires 1d;
}
上述配置表示这些静态文件1天后过期。
如果想配置为完全不缓存,那么可以设置为expires -1;(后面的数字配置为负数),返回的header会被设置为Cache-Control:no-cache。
2.后台代码写入
例如:
response.setHeader("Cache-Control", "no-cache");
3.html 的meta标签
<meta http-equiv="Cache-Control" content="max-age=7200"/>
实际问题分析
1.引入缓存之后,主要有两个问题:
(1)浏览器不知道有资源更新,还是使用缓存中的老文件。
(2)各个文件缓存策略不一致,有关联关系的文件,有的从服务器加载,有的直接取浏览器缓存的,这样有可能会导致界面混乱。
2.解决方式
(1)Etag或Last-modified
Etag是服务端根据文件信息生成的字符串,当服务端文件更新时,Etag也会变化,这样能保证当服务端文件更新时,取到新的文件内容。
但是Etag这种解决方式的问题是,请求还是会发到服务端,由服务端进行判断。
Last-modified与Etag类似。
(2)文件名后缀
构建过程中,把构建生成的文件加上随机后缀,主入口html中的引用文件在构建中替换为增加了文件名后缀的;主入口文件配置为不缓存。
当服务端更新文件时,由于文件名后缀更改,浏览器缓存匹配不上,会直接到服务端获取,服务端没有更新文件时,在浏览器缓存获取。
这种方式效果较好,但是需要引入构建,对于已经使用了前端构建的web应用比较适用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。