前言
网站在浏览器加载过程中,数据请求效率对网站性能影响很大,过多或者过大的文件请求会十分影响用户的体验,所以熟练使用缓存策略是一种简单高效的性能优化方式。
对于前端来说最常用的几种缓存:HTTP缓存、IndexedDB、Cookie、LocalStorage、SessionStorage。今天就先来深入一下HTTP缓存。
HTTP缓存原因
首先HTTP缓存存在的原因在于与浏览器的请求机制,正常浏览器在发起一个资源请求时,会进行以下几个步骤: 浏览器发起请求→浏览器缓存(内存/磁盘)寻找是否存在缓存标识→根据缓存标识不同分成三种情况:
- 没有缓存则直接发起HTTP请求,成功返回200状态码
- 存在强制缓存未过期,则直接从本地获取资源信息
- 本地缓存为协商缓存,不存在强制缓存,先通过本地资源识别码向服务端发送请求,若协商成功,即缓存数据有效,则返回304状态码,从本地缓存读取数据;若与服务器数据不匹配则再次发起请求,成功后返回200状态码。 最后得到相应资源信息,所以利用这个机制,我们可以HTTP请求头对我们服务器资源设置相应缓存策略,以达到减小服务器请求压力和加快本地网站加载速度的目的。
HTTP缓存分类
通常HTTP缓存分为两种:强制缓存与协商缓存。
两者区别在于强制缓存可以直接使用本地缓存,而协商缓存则需要与服务器进行一个简单的通讯沟通过程,通过服务器返回的请求头信息判断是否使用本地缓存,以达到保证本地资源与服务器资源保持一致的目的。
强制缓存
强制缓存不会向服务器请求资源,直接从内存/磁盘中读取缓存,在谷歌浏览器中请求size选择项中会看到from disk cache或from memory cache就是从本地缓存中读取。区别在于from memory cache是从内存中读取缓存,特点是速度快,占用进程的内存资源,时效性差,随着进程释放而释放,from disk cache从硬盘中读取缓存,需要进行I/O操作,速度低于内存缓存,但时效性长。
强制缓存请求头标识:
-
expires。expires是HTTP1.0控制缓存的字段,通过一个绝对时间(UTC标准)来规定缓存的有效时间。如果客户端发起请求时间小于有效期,则直接使用本地缓存,但是这种控制方式有一个弊端,那就是客户端受本地时间影响,如果客户端本地时间与服务器时间不匹配,则缓存产生时差。
-
cache-control。由于expires的缺点,所以在HTTP1.1版本推出了cache-control字段来替代expires,优先级高于expires,如果cache-control只设置了public或者private没有,设置缓存类型或有效期相关属性,则expires仍然有效。其通过相对的过期时间来控制,避免了客户端与实际时间不匹配的问题。cache-control不区分大小写但是建议使用小写,多个指令用逗号分隔,由于cache-control本身属性比较多,其中no-store优先级最高,这里详细说一下cache-control。cache-control分为缓存请求指令和缓存响应指令:
-
缓存请求指令,即客户端可以在HTTP请求中使用的标准指令包括:
字段名称 字段解释 no-cache 用于告诉代理服务器不直接使用缓存,要向原服务器发起确认请求 no-store 不保存到缓存和临时文件中 max-age=seconds 告诉服务器客户端不希望接收一个超过seconds时间范围的缓存,并且不接受过期缓存,除非max-stale存在。 max-stale[=seconds] 告诉代理服务器可以接收一个过期的缓存资源,若注明seconds时间,则为过期时间在seconds秒内,否则表示任意超出时间都可被接受 min-fresh=seconds 从代理服务器拿一个更新时间小于seconds秒的资源,指后续一段时间不会过期 no-transform 不得对资源进行转换或转变。Content-Encoding、Content-Range、Content-Type等 HTTP 头不能由代理修改。例如,非透明代理或者如Google's Light Mode可能对图像格式进行转换,以便节省缓存空间或者减少缓慢链路上的流量。no-transform指令不允许这样做。 only-if-cached 表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝。如果缓存没有命中则返回504状态码 -
缓存响应指令,即浏览器返回响应头中cache-control属性类型:
字段名称 字段解释 public 表明响应可以被任何对象缓存,如客户端、代理服务器,即使是通常不可缓存的内容,如该响应没有max-age指令或Expires消息头;该响应对应的请求方法是POST。没有设置public和private情况下,则默认是public。 private 表明只能被单个用户缓存,不能作为共享缓存,即不能被代理服务器缓存。私有缓存可以缓存响应内容,如用户浏览器 no-cache 发布缓存前需要向数据源服务器进行验证,即协商缓存 no-store 不使用任何缓存存储 must-revalidate 一旦资源过期,如max-age超时,在成功向资源服务器验证前,不能用于任何缓存请求 no-transform 代理服务器和客户端在缓存时,不可对资源进行转换或转变 proxy-revalidate 与must-revalidate作用相同,但它仅适用于共享(代理)缓存,并被私有缓存忽略 max-age=seconds 表明最大缓存时间为seconds的缓存,与Expires相反,时间是相对请求时间 s-maxage=seconds 覆盖max-age或Expires,但仅适用于共享缓存(代理),私有缓存会忽略这个属性
协商缓存
协商缓存是指,客户端在使用缓存时,必须向服务器发送确认请求,判断资源文件是否是最新的。若缓存可以继续使用,服务器会返回304,通知客户端可以继续使用缓存文件,否则重新发起请求成功后返回200状态码。
常见的协商缓存用法有两种,一是使用 cache-control:no-cache另一种是使用 cache-control:max-age=0,must-revalidate。
服务器协商缓存的验证方式有两种:
-
last-modified与if-modified-since。last-modified一般是和if-modified-since配合一起使用,浏览器第一次访问服务器,服务器会通过last-modified返回文件最后修改时间,客户端收到后缓存并记录这个时间,当客户端再次发起请求时,浏览器会通过if-modified-since通过请求头携带这个时间,让服务器判断资源是否被修改,如果文件修改则返回200重新请求资源文件,如果没有修改,则返回304,告诉客户端使用本地缓存文件。但是使用last-modified有两个缺点,一是如果文件本身内容没变,但是文件最后修改时间变了,如重命名,也会导致缓存重新加载,第二个是last-modified精度为秒,若多次修改时间间隔在秒以下则无法判断(一般这种情况出现几率比较小)。
-
etag。服务器根据资源生成一个唯一标识符,当资源发生改变时,etag就会随之改变。浏览器第一次访问资源文件时,服务器通过响应头将etag传输给客户端,客户端接受缓存并记录etag,当客户端再次发起请求时,浏览器通过if-none-match将标识符传递给服务端,如果服务端与资源文件etag匹配则返回304,否则返回200重新请求。但是etag缺点是过于消耗服务器资源,文件较大或者重复计算时,会给服务器造成压力,所以last-modified与etag一般相辅使用。服务端etag优先级高于last-modified。
前端文件缓存的策略
1. 文件拆分,分包加载
现在大型应用构建工具,如webpack等,都会把js与css应用拆分成多个文件,并添加唯一标识符,按需加载,节省服务器资源与页面加载时间。
2. 预估资源缓存时效
根据不同资源特点,规划缓存时效,强制缓存指定合适max-age。协商缓存指定etag或last-modified。
3. 控制中间代理缓存
安全性高的缓存设置为private,不让代理服务器缓存。如果所有用户响应文件资源相同,则考虑代理缓存。
4. 避免冗余缓存
对资源进行url请求时,尽量保证相同资源同一个URL,避免出现因URL地址不同出现的冗余缓存。