2021-12-08 浏览器常用缓存策略及vue-cli单页面SPA应用缓存优化

问题描述

vue 项目打包上线之后,每一次都会有浏览器缓存问题,需要手动的清除缓存。这样用户体验非常不好,所以我们在打包部署的时候需要尽量避免浏览器的缓存。

需要解决的问题

1、程序每次升级后,用户都不会因为缓存问题而执行的仍然是老的程序。
2、若程序没升级,用户对静态资源的请求则能用到缓存。

关于浏览器缓存策略,可以分为这三种:

  • (1)不使用缓存

no-store
永远都不在客户端存储资源,永远都去原始服务器去获取资源。

  • (2)强制使用缓存

private(ngnix默认): 只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存, 只有在第一次请求的时候才访问服务器, 若有max-age, 则缓存期间不访问服务器获取资源,过期后才访问。

public: 可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。

  • (3)协商使用缓存

no-cache
可以在客户端存储资源,不管本地缓存是否过期,每次都必须去服务端做新鲜度校验,来决定从服务端获取新的资源(200)还是使用客户端缓存(304)。也就是所谓的协商缓存。

must-revalidate
可以在客户端存储资源,只有本地缓存过期才去服务端做新鲜度校验。

1、不使用缓存

有时,我们希望浏览器永远都不要使用缓存,全部到服务器拉取数据,此时即为不使用缓存,我们可以在服务端通过Cache-Control为 no-store实现。


服务器端针对上面文件设置了no-store,可以看到在请求的时候,无论怎么刷新,都是返回200,不会显示304,也不会显示“memory cache”或“disk cache”,说明真的都是从服务器重新拉取数据。

比如我们想设置html文件不缓存,可以在域名的解析配置中如下设置,当文件后缀为html或htm时add_header Cache-Control “no-store”

server {
listen 80;
server_name yourdomain.com;
location / {
    try_files $uri $uri/ /index.html;
    root /yourdir/;
    index index.html index.htm;

    if ($request_filename ~* .*\.(?:htm|html)$)
    {
        add_header Cache-Control "no-store";  //对html文件设置永远不缓存
    }  
  }
}

这种方式缺点就是每次都要去服务端拉取文件,即使文件没有更新,很明显这样增加了不必要的带宽消耗。

如果文件没有更新,我们就使用缓存,只有更新了才去拉取最新文件,这样多好,这就是协商缓存。

2、协商缓存

协商缓存就是浏览器携带文件缓存标识(如Last-Modified或ETag),向服务器发送请求,由服务器根据文件缓存标识来决定是否使用缓存,如果文件没有更新,则告诉浏览器使用本地缓存,如果文件更新了,则直接返回新文件内容。
可以在服务端通过设置Cache-Control为 no-cache或者max-age=0来实现

可以看出,相比不使用缓存,协商缓存是会大大减少带宽消耗的。

  • 协商缓存生效,返回304 和 Not Modified
  • 协商缓存无效,返回200和请求文件
image.png

我们在浏览器调试页面,可以看到有304的,即是使用了协商缓存

服务器返回的header中会有Last-Modified和ETag标识,而浏览器请求header中会包含If-Modified-Since和If-None-Match

LAST-MODIFIED和IF-MODIFIED-SINCE

在 http 1.0 版本中,第一次请求资源时服务器通过 Last-Modified 来设置响应头的缓存标识,并且把资源最后修改的时间作为值填入,然后将资源返回给浏览器。在第二次请求时,浏览器会首先带上 If-Modified-Since 请求头去访问服务器,服务器会将 If-Modified-Since 中携带的时间与资源修改的时间匹配,如果时间不一致,服务器会返回新的资源,并且将 Last-Modified 值更新,作为响应头返回给浏览器。如果时间一致,表示资源没有更新,服务器返回 304 状态码,浏览器拿到响应状态码后从本地缓存数据库中读取缓存资源。

这种方式有2个弊端,第一个就是当服务器中的资源增加了一个字符,后来又把这个字符删掉,本身资源文件并没有发生变化,但修改时间发生了变化。当下次请求过来时,服务器也会把这个本来没有变化的资源重新返回给浏览器;第二个就是修改时间的单位为秒,所以存在1s的间隙,即使更新了,也会认为没有更新。

ETAG和IF-NONE-MATCH

在 http 1.1 版本中,服务器通过 Etag 来设置响应头缓存标识。Etag 的值由服务端生成,可以认为是文件内容的hash值。在第一次请求时,服务器会将资源和 Etag 一并返回给浏览器,浏览器将两者缓存到本地缓存数据库。在第二次请求时,浏览器会将 Etag 信息放到 If-None-Match 请求头去访问服务器,服务器收到请求后,会将服务器中的文件标识与浏览器发来的标识进行对比,如果不相同,服务器返回更新的资源和新的 Etag ,如果相同,服务器返回 304 状态码,浏览器读取缓存

两者对比

  • 首先在精确度上,Etag要优于Last-Modified。Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。

  • 第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。

  • 第三在优先级上,服务器校验优先考虑Etag

3、强制缓存

有时我们希望文件强制使用缓存,比如通过vue-cli产生的js和css,文件名上带有hash值,所以如果文件名没有变的时候,我们希望文件永久缓存,这样可以减少网络请求。

强制缓存整体流程比较简单,就是在第一次访问服务器取到数据之后,在过期时间之内不会再去重复请求。实现这个流程的核心就是如何知道当前时间是否超过了过期时间。

强制缓存的过期时间通过第一次访问服务器时返回的响应头获取。在 http 1.0 和 http 1.1 版本中通过不同的响应头字段实现。

在 http 1.0 版本中,强制缓存通过 Expires 响应头来实现。 expires 表示未来资源会过期的时间。也就是说,当发起请求的时间超过了 expires 设定的时间,即表示资源缓存时间到期,会发送请求到服务器重新获取资源。而如果发起请求的时间在 expires 限定的时间之内,浏览器会直接读取本地缓存数据库中的信息(from memory or from disk),两种方式根据浏览器的策略随机获取。

在 http 1.1 版本中,可以设置Cache-Control中的 max-age=xxx ,来表示缓存的资源将在 xxx 秒后过期。一般来说,为了兼容,两个版本的强制缓存都会被实现。

为什么有了Expires,后来又增加了max-age呢,这是因为Expires是一个绝对时间,有可能客户端的时间和服务器不一致,导致缓存不能按照预期进行,而max-age则是个相对时间,比如3600s,自浏览器请求后3600s之内,都使用本地缓存,和客户端的时间没关系。

补充:

缓存的优先级策略

  • 文件缓存位置:
    Service Worker 浏览器背后的独立线程缓存
    Memory Cache 浏览器内存缓存
    Disk Cache 系统硬盘缓存
    Push Cache 推送缓存

具体可以看这里:
https://blog.csdn.net/weixin_43972437/article/details/105513486

打开网页,地址栏输入地址:
查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。

普通刷新 (F5):
因为页面tab并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。

强制刷新 (Ctrl + F5):
浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache)。服务器直接返回 200 和最新内容。

比如:访问图片-> 200 -> 退出浏览器
再进来-> 200(from disk cache) -> 刷新 -> 200(from memory cache)

  • 缓存失效策略分类:
    缓存过期时间 : Cache-control 的优先级高于 Expires
    文件是否改动标识: Etag 的优先级高于 Last-Modified

SPA应用缓存解决方案

  • 方案一:直接在index.html中加入了这几行代码:
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Cache" content="no-cache">

缺点:升级时缓存问题倒解决了,但直接导致了用户每次访问你的程序时都要重新请求服务器,所有的静态资源都无法用缓存了,浪费流量,网络压力变大。且多数服务器不支持。

  • 方案二:Nginx 配合 vue.config.js 进行配置

由于打包后的js、css和图片,一般名称都带有hash值,名称中的hash变了,自然会拉取新文件,所以我们可以将这类文件设置为强制缓存,只要文件名不变,就一直缓存,或者设置缓存比较久的时间100天或者一年。

而html文件则不能设为强制缓存,一般html名称是没法带hash值的,所以html如果设置了强制缓存,则永远也没法更新,html不更新,其引用的js、css等名称也不会更新,则整个服务都没有更新,只能让用户清除缓存了。所以针对html文件,我们可以设置协商缓存或者直接不使用缓存。

nginx配置:

location /udaam-ui {
       root  /usr/local/ui-workspace;
       index index.html index.htm;
       try_files $uri $uri/ /udaam-ui/index.html;
       if ($request_filename ~* .*\.(js|css|woff|png|jpg|jpeg)$){
            expires    100d;  #js、css、图片缓存100天
            #add_header Cache-Control "max-age = 8640000"; #或者设置max-age
        }
        if ($request_filename ~* .*\.(?:htm|html)$){
            add_header Cache-Control "no-cache, no-store";  #html不缓存
        }
}

root:设置静态根目录为 data
index:设置目录的默认文件为 index.html 、index.htm
try_files:设置文件查找规则为 uriuri/ /index.html。即3个规则,先从 uri 查找,再从uri/ 目录中查找,最后查找 /index.html。

vue.config.js设置:
修改output的filename和chunkFilename

 configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      return {
        output: {
            // 输出重构  打包编译后的 文件名称  【模块名称.版本号.时间戳】
          filename: `js/[name].[chunkhash].js`,
          chunkFilename: `js/[id].[chunkhash].js`
        }
      }
    }
  },
  • filename 指列在entry 中,打包后输出的文件的名称。
  • chunkFilename 指未列在entry 中,却又需要被打包出来的文件的名称。

修改打包后的css

  css: {
    extract: { // 打包后css文件名称添加时间戳
      filename: `css/[name].[chunkhash].css`,
      chunkFilename: `css/chunk.[id].[chunkhash].css`
    }
  }

原文地址:https://blog.csdn.net/qq_42268364/article/details/119386473
参考文章:https://www.jianshu.com/p/5068788204f9

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容