前端部署缓存策略实践

本文介绍基于前后端分离状态下前端部署缓存策略的实践。

背景

由于前端的蓬勃发展,前端框架reactvueangular等在日常开发中已经非常普及,放大了前端的业务能力,也催生了前后端分离开发的模式。实际开发部署时只需要通过webpack等打包工具打包后生成静态资源,部署到nginxCaddy等现有的静态服务器中,方便快捷同时与后端分离后彼此隔离,只通过RESTful api进行通信。这时对静态服务器的配置便显得至关重要。

浏览器缓存

强制缓存

强缓存所谓的"强",在于强制让浏览器按照一定时间范围内来存储来自服务器的资源,有点强制的味道~,强缓存是利用Expires或者Cache-Control,不发送请求,直接从缓存中取,请求状态码会返回200from cache)。

Expires(已逐步淘汰)

ExpiresHTTP/1.0中提及的,让服务器为文件资源设置一个过期时间,在多长时间内可以将这些内容视为最新的,允许客户端在这个时间之前不去检查。

  • 指定到期时间:

    指定缓存到期GMT的绝对时间,如果Expires到期需要重新请求。

    这个时间是服务器的时间,所以这里就会出现一个问题,服务器时间和本地时间不一致,就会造成缓存失效时间不准确。

    nginx 复制代码
    Expires:Sat, 09 Jun 2020 08:13:56 GMT

Cache-Control(主要)

相比Expires,两者有什么区别呢? Cache-Control你可以理解成为高级版Expires,为了弥补Expires的缺陷在Http1.1协议引入的,且强大之外优先级也更高,也就是当ExpiresCache-Control同时存在时,Cache-Control会覆盖Expires的配置,即Cache-ControlHttp 1.1 ) > ExpiresHttp 1.0 )。

Cache-ControlExpires比具备更多的属性,其中包括如下:

  • no-cache :可以在本地缓存,可以在代理服务器缓存,需要先验证才可使用缓存。
  • no-store :禁止浏览器缓存,只能通过服务器获取。
  • max-age :设置资源的过期时间(效果与Expires一样)。

示例:

nginx 复制代码
// 设置缓存时间为1年
Cache-Control: max-age=31536000
Expires:Sat, 09 Jun 2020 08:13:56 GMT //同时设置两个,Expires会失效

这意味着浏览器可以缓存一年的时间,无需请求服务器,同时如果同时声明ExpiresCache-ControlExpires将失效。

用户对浏览器的操作

Cache-Control no-cachemax-age=0的区别你按浏览器刷新与强制刷新的区分。

  • Ctrl + F5 (强制刷新):request header多了cache-control: no-cache(重新获取请求)。
  • F5 (刷新)/ctrl+R 刷新:request header多了cache-control: max-age=0(需要先验证才可使用缓存,Expires无效)。

协商缓存

协商缓存,就没有强缓存那么霸道,协商缓存需要客户端和服务端两端进行交互,通过服务器告知浏览器缓存是否可用,并增加缓存标识,"有事好好商量",两者都会互相协商。 协商缓存,其实就是服务器与浏览器交互过程,一般有两个回合,而协商主要有以下几种方式:

Last-ModifiedHttp 1.0

  • 第一回合:当浏览器第一次请求服务器资源时,服务器通过Last-Modified来设置响应头的缓存标识,把资源最后修改的时间作为值写入,再将资源返回给浏览器。
  • 第二回合:第二次请求时,浏览器会带上If-Modified-Since请求头去访问服务器,服务器将If-Modified-Since中携带的时间与资源修改的时间对比,当时间不一致时,意味更新了,服务器会返回新资源并更新Last-Modified,当时间一致时,意味着资源没有更新,服务器会返回304状态码,浏览器将从缓存中读取资源。

示例:

nginx 复制代码
//response header 第一回合
Last-Modified: Wed, 21 Oct 2019 07:28:00 GMT

//request header 第二回合
If-Modified-Since: Wed, 21 Oct 2019 07:29:00 GMT

EtagHttp 1.1

MDN中提到ETag之间的比较,使用的是强比较算法,即只有在每一个字节都相同的情况下,才可以认为两个文件是相同的,而这个hash值,是由对文件的索引节、大小和最后修改时间进行Hash后得到的,而且要注意的是分布式系统不适用,同时需要注意的是Etag的组装不同类型的服务器可能不同比如nginxEtag可能是长的这样ETag: "5f3498d1-b0063"

  • 第一回合:也是跟上文一样,浏览器去请求服务器资源,不过这次不是通过Last-Modified了,而是用Etag来设置响应头缓存标识。Etag是由服务端生成的,然后浏览器会将Etag与资源缓存。
  • 第二回合: 浏览器会将Etag放入If-None-Match请求头中去访问服务器,服务器收到后,会对比两端的标识,当两者不一致时,意味着资源更新,会从服务器的响应读取资源并更新Etag,浏览器将从缓存中读取资源,当两者一致时,意味着资源没有更新,服务器会返回304状态码,浏览器将从缓存中读取资源。

示例:

nginx 复制代码
//response header 第一回合
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

//request header 第二回合
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

对比完Last-ModifiedEtag,我们可以很显然看到,协商缓存每次请求都会与服务器发生"关系",第一回合都是拿数据和标识,而第二回合就是浏览器"咨询"服务器是否资源已经更新的过程。

同时,如果以上两种方式同时使用,Etag优先级会更高,即Etag(Http 1.1) > Last-Modified(Http 1.0)。

缓存状态码

状态码200 OKfrom cache

这是浏览器没有跟服务器确认,直接用了浏览器缓存,性能最好的,没有网络请求,那么什么情况会出现这种情况?一般在Expires或者Cache-Control中的max-age头部有效时会发生。

状态码304 Not Modified

是浏览器和服务器"交流"了,确定使用缓存后,再用缓存,也就是第二节讲的通过EtagLast-Modified的第二回合中对比,对比两者一致,则意味资源不更新,则服务器返回304状态码。

状态码200

以上两种缓存全都失败,也就是未缓存或者缓存未过期,需要浏览器去获取最新的资源,效率最低 一句话:缓存是否过期用:Cache-Controlmax-age), Expires,缓存是否有效用:Last-ModifiedEtag

静态文件

前面介绍了浏览器缓存的一些方式,而随着前端工程化,前端可以做到通过打包工具(webpack等)来进行前端代码打包,生成一个可以直接部署到静态网站的源码。下面来看下通过打包后前端文件情况(以我的实际项目为例):

  • 由以上的截图可以看到,每次发布新的版本打包出来的文件入口的index.html和前端配置文件config.js或者path.js的文件名是不变的,而动态生成的jspng文件会自动添加不同的hash值是动态的。

  • 结合上文讲到的浏览器缓存的内容可以知道如果不单独配置这些固定名称的文件的缓存策略的话由于不同服务器不同浏览器的实现不同极有可能会被缓存而导致发布新版本后页面失效的问题。

解决方案

上文已经提到前端打包生成的动态文件会根据内容自动生成对应hash和文件名,每次发布新版本这些文件的更新会命中协商缓存或者强缓存失效的规则,能形成破坏缓存的效果,达到发布新版本内容更新的效果,而入口文件index.htmlconfig.jspath.js等可能会因为浏览器默认的缓存策略不能破坏缓存,达不到发布新版本内容即更新的效果(尤其是index.html这个整个服务的入口如果不更新则会造成对应的引入文件均不可用而使页面崩溃)。

针对这种情况,需要在服务器端针对上面提到的index.htmlconfig.jspath.js等文件单独配置缓存策略,由截图中绿色框中的部分可以看出这些文件都不大,所有这里的缓存策略可以是配置强制缓存并且强制不让这些文件缓存Cache-control: no-store,让浏览器每次都重新从服务器拉取最新的文件。

nginx为例,nginx.conf配置如下:

nginx 复制代码
# server下增加如下配置
location ~* (.html|config.js|path.js)$ {
    # 通过gx_http_headers_module提供的add_header方法配置强缓存并任何情况下不可缓存
    add_header Cache-Control no-store;
}

来匹配我们上文中提到的三种静态文件,这里附上nginxlocation匹配规则

  • 增加以上配置后对nginx进行重启(nginx -s reload)。
  • 第一次增加配置后建议用户进行强制刷新或者清除浏览器缓存后再使用。

验证方法

增加以上配置后可以通过两个途径进行验证:

  • 桌面端的浏览器(以Chrome为例):
    • 浏览器访问之前部署的服务uri路径。
    • F12 打开开发者工具,切换到network选项,刷新或者强制刷新查看如下图:
  • linux下或者windows PS命令行:
    • 执行curl http://ip:port来发送对应的请求。
    • 控制台会显示如下图:

总结

前端工程化的现在给我们发布服务很大的便利,浏览器缓存是优化网站性能的利器同时也会带来一些问题,制定好缓存策略至关重要,现在前端通过打包生成的文件能很好地破坏原有缓存,但也有例外,本实践通过对静态资源的缓存策略来保证发布新版本后用户及时获得最新更新页面。

参考资料

相关推荐
没想好d2 小时前
通用管理后台组件库-14-图表和富文本组件
前端
Mh2 小时前
react 设计哲学 | 严格模式
前端·react.js·preact
怜悯2 小时前
uniapp 如何实现google登录-安卓端
前端·javascript
TT_哲哲2 小时前
小程序解析字符串拼接多图 点击放大展示
前端·javascript
一颗奇趣蛋2 小时前
Cursor 多项目搜索指南
前端
Jolyne_2 小时前
Taro小程序接入微信客服过程记录
前端
勇往直前plus2 小时前
前端三基石:从后端视角理解 HTML、CSS 与 JavaScript
前端·css·html
用户69371750013842 小时前
Google 推 AppFunctions:手机上的 AI 终于能自己干活了
android·前端·人工智能
用户69371750013842 小时前
AI让编码变简单,真正拉开差距的是UI设计和产品思考
android·前端·人工智能