缓存策略

486 阅读5分钟

目录

  • 1 强缓存和协商缓存
    • 1.1 强缓存
      • expires
      • cache control
    • 1.2协商缓存
      • last-modified/if-modified-since
      • etag/if-none-match
  • 2 缓存位置
    • 2.1 service worker
    • 2.2 memory chache
    • 2.3 disk cache
    • 2.4 push cache
  • 3 扩展
    • 3.1 etag 的优先级为什么比 last-modified 高
    • 3.2 缓存注意点
    • 3.3 webpack hash
    • 3.4 service worker

1 强缓存和协商缓存

  • 强缓存和协商缓存是通过 http 的缓存策略
  • 强缓存有2中 cache-control、expires
  • 强缓存不会发送真实请求给服务端,状态码是 200
  • 协商缓存也有2种(last-modified/if-modified-since)、(etag/if-none-match)
  • 协商缓存是通过客户端和服务共同协商的,状态码是 304

1.1强缓存

expires

绝对时间,将服务端时间和浏览器时间做对比,如果服务端时间和客服端时间有差异,就会出现问题

cache-control

相对时间,可以更精准的控制缓存,有以下一些值

  • no-cache 需要先和服务器确认返回的响应的编号是否发生了变化,如果没变化就用缓存
  • no-store 禁止使用缓存
  • max-age 相对时间秒数
  • private 只允许终端客户端进行缓存,中间层不允许
  • public 允许客户端、cdn等中间服务层进行缓存

1.2 协商缓存

last-modified/if-modified-since

  • last-modified 文件最后修改日期

etag/if-none-match

  • etag 根据文件内容生成一个唯一值
  • etag 都优先级比 last-modified 高

2 缓存位置

2.1 Service Worker

  • service worker 是一个注册在指定源和路径下的事件驱动的 worker,它采用JavaScript 控制关联页面或者网站,拦截并修改访问请求,细粒度地缓存资源,相当于在客户端和服务端做了一个中间件来有效控制请求的缓存
  • service worker 运行在 worker 上下文, 因此不能访问 dom
  • 设计完全异步, 不能用 xhr 和 localstorage
  • service worker 是独立于主线程的独立线程,用来做服务器推送,缓存等功能
  • service worker 首先要注册,注册成功后就可以缓存此域名下所有的 fetch 事件
  • 必须使用 https,因为 service worker 中涉及请求拦截,我们必须使用 https 来保障安全

2.2 Memory Cache

  • 内存缓存,储存到内存上,读取速度快,容量小,持续性短,有时间限制,会随着进程释放而释放

2.3 Disk Cache

  • 储存在硬盘上,容量大,读取速度慢,读取需要进行I/O操作

2.4 Push Cache

http2中的内容,当上面当缓存都没命中时,才会被使用,缓存时间短,存在session中,会话结束而释放

缓存的执行顺序: Service Work -> Memory Cache -> Disk Cache -> Push Cache

3扩展

3.1 etag 的优先级为什么比 last-modified 高

  • 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候,我们并不希望客户端认为这个文件被修改了,而重新 get
  • 某些文件修改非常频繁,比如在秒以下的时间内进行修改(比方说 1s 内修改了 N 次),If-Modified-Since能检查到的粒度时 s 级的,这种修改无法判断(或者说 UNIX 记录 MTIME只能精确到秒)
  • 某些服务器不能精确得到的文件的最后修改时间

3.2 缓存注意点

  • 分布式系统里多台机器间文件的Last-Modified必须保持一致,以免负载均衡到不同机器导致比对失败;
  • 分布式系统尽量关闭掉ETag(每台机器生成的ETag都会不一样);

3.3 webpack hash

对于客户端,静态资源首次被加载后浏览器都会进行缓存,当资源发生变化我们如何去通知浏览器呢?资源文件命名hash化就是来解决这个问题

hash

  • 只要项目中的一个文件改变了,项目构建了,项目文件中所有的 hash 都会变

chunkhash

  • 众所周知,根据不同的入口文件进行依赖文件解析、构建对应的chunk,每一个代码块 chunk 对应的一个hash,当相应的文件发生改变,只有当前的 chunk 的 hash 会发生改变,chunkhash 的影响范围比 hash 要小一些

contenthash

  • 根据文件内容产生都hash值

需要特别注意的一点,file-loader的hash字段,这个loader自己定义的占位符,和webpack的内置hash字段并不一致。这里的hash是使用md4等hash算法,对文件内容进行hash。所以只要文件内容不变,hash还是会保持一致。

第一种每构建一次,即使文件每变,hash值都变了,缓存就失效了,一般不采用。在项目中,我们一般会把项目中都会用 MiniCssExtractPlugin 把 css 抽离出来,如果我们使用 chunkhash,修改了css后,js 的 hash 也变化了。所以css 一般用contenthash, js用chunkhash

3.4 service worker

  • 通过配置sw-precache-webpack-plugin生成我们的service-worker的脚本
  • SWPrecacheWebpackPlugin是一个webpack插件,用于使用service worker来缓存外部项目依赖项。 它将使用sw-precache生成service worker文件并将其添加到您的构建目录。为了在service worker中生成预缓存的名单, 这个插件必须应用在assets已经被webpack打包之后
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin')
const plugins = [
 new SWPrecacheWebpackPlugin({
    dontCacheBustUrlsMatching: /\.\w{8}\./,
    filename: 'service-worker.js',
    logger(message) {
        ...
    },
    minify: true,
    navigateFallback: publicUrl + '/index.html',
    navigateFallbackWhitelist: [/^(?!\/__).*/],
    staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
  }),
  ...
]
  • 注册service worker
navigator.serviceWorker
    .register(swUrl)
    .then(registration => {
      registration.onupdatefound = () => {
        const installingWorker = registration.installing;
        installingWorker.onstatechange = () => {
          if (installingWorker.state === 'installed') {
              ...
          }
        };
      };
    })
    .catch(error => {
      ...
    });

参考