浅谈 Nginx 与浏览器缓存与优化
前言
程序员: 我写的程序会有Bug? 清除一下缓存再试试。
测试: 我清除完缓存如果还有Bug, 我40米的大刀只允许你跑3.1415926米。
用户: 什么情况, 怎么每次浏览页面都要等这么长时间, 这届程序员不大行啊!
...
以上的情景可能每天都在发生, 身为开发者对缓存这个概念一定也不会陌生, 大家在工作和生活中多多少少会碰到缓存产生的问题或者缓存相关的优化方案, 今天我们就深入浅出的聊聊 缓存 的那些事。
什么是浏览器缓存
浏览器缓存就是把一些资源或数据放到浏览器的内存或磁盘中, 当用户再次请求对应资源时, 可以更快速的获取以此提高用户体验。
一般是将一些用户频繁访问的界面或者很少发生变化的内容用户缓存。
但无论什么形式的缓存基本上都可以用一句话概括: 以节省计算机资源提升发起者获取数据的速度。
使用缓存的俩种交互
-
协商缓存 -
用户发送请求后, 由服务器判定是否从缓存中获取资源 -
强缓存 -
用户发送请求后, 直接从缓存中获取, 不与服务器进行交互
浏览器缓存的默认行为
当服务器和客户端双方都没有对缓存方式进行协定, 那么浏览器默认缓存策略如下:
文件 | 缓存时长 |
---|---|
html | 不缓存, 每次向服务器发送请求确认 |
js | 强缓存 |
css\img\fonts | 强缓存 |
可以看得出来, 如果静态资源命中强缓存且资源发生变化时, 用户无法获取到最新资源, 这种问题如何解决呢 ?
石器时代之--<刀耕火种>
解决这种问题时, 习惯在引用的静态资源后面拼接一个 时间戳或者版本号, 每次发版的时候与上一次不同, 以此告诉浏览器资源有变化需重新请求。
大前端时代之--<webpack-实现xxx的第101种方法>
第101种方法纯属瞎扯, 只是以此来形容大前端时代的繁荣以及各种层出不穷的强大的构建工具, 下面介绍 webpack 的实现方式。
Webpack Hash
webpack 打包时会更新对应的 hash 文件路径, 当入口文件内引用静态资源路径发送变化时, 协商缓存就会失效进而重新请求资源。
entry:{
main: path.join(__dirname,'./main.js'),
vendor: ['react', 'antd']
},
output:{
path:path.join(__dirname,'./dist'),
publicPath: '/dist/',
filname: 'bundle.[chunkhash].js'
}
Webpack Hash 三种计算方式
-
hash
: 构建生成的 hash 都是一样的, 意味着只要项目中有更改整个项目的 hash 都会变化 -
chunkhash
: 根据不同的入口对所依赖的文件进行解析, 生成对应的 chunkhash 值 -
contenthash
: 有文件内容产生的 hash 值, 内容不同产生的 contenthash 值也不一样
contenthash
的应用场景:
-
比如当 css 和 js 共用的 chunkhash 因 js 改变而发生变化时, 页面也需要重新加载 css 文件, 解决的这种场景的问题 -
可以使用 extra-text-webpack-plugin
, 保证 css 文件所处的模块就算文件内容改变, 只要css文件内容不变, 那么就不会重复构建
总结: 也就是说如果项目中使用了 webpack
的这种构建工具, 配合浏览器的默认缓存策略, 一般也会有不错的用户体验。
ps: 老款旧版浏览器表现可能会有差异
缓存设置
浏览器、Nginx、服务器都可以对缓存进行设置, 但目前主流是在 Nginx, 因为相对比较灵活、统一
HTTP 1.0 缓存指令
Expires: [time|epoch|max|off;
缺点: 以服务器时间为准生成的绝对过期时间, 当服务器与客户端时区不一致时, 会导致缓存效果出现偏差。
HTTP 1.1 缓存指令
Cache-control: no-cache
Cache-control: no-store
Cache-Control: max-age=<seconds>
...
Cache-control
: 可以看做是 Expires 的完善, 以客户端的时间为准。
缓存方式:
-
public: 默认值, 响应会被缓存, 并且在多用户间共享 -
private: 响应只作为私有缓存, 不能在用户间共享 -
no-cache: -
指定不缓存响应, 表明资源不进行缓存 -
但不代表浏览器不缓存, 而是在缓存前要向服务器确认资源是否被更改 -
no-store: 绝对禁止缓存, 每一次都需要重新获取资源
到期时间:
-
max-age: -
设置缓存的有效时间, 单位为秒 -
max-age > 0时直接从浏览器缓存中提取 -
max-age < 0时向 server 发送请求进行协商 -
优先级高于 Expires -
s-maxage: -
只用于共享缓存, 比如 CDN缓存 -
优先级高于 Expires/max-age
重新验证和加载:
-
must-revalidate: 如果页面过期, 则去服务器进行获取
注意:
-
private: max-age 为正数时, 后退不会访问服务器 -
no-cache: max-age 为正数时, 后退会访问服务器
缓存协商
服务器会对请求头部的信息进行匹配和检测的过程
Response Header:
-
Last-modified: 资源最后一次的修改时间 -
Etag: 资源内容的标识
Request Header:
-
If-Modifired-Since: -
其值是上次请求服务响应的 Last-modified -
服务器会根据 If-Modifired-Since 的值与当前最新的 Last-modified 进行比较 -
局限: 由于时间只能精确到秒, 意味着 无法有效缓存一秒内的多次改动 -
If-None-Match: -
其值是上次请求服务响应的 Etag -
服务器会根据 If-None-Match 的值与当前最新的 Etag 进行比较 -
优先级高于 If-Modifired-Since -
相同处: 对应比较字段匹配则返回 200 或 304 -
不同处: 时间与内容俩个维度
注意:
-
分布式系统尽量关闭 Etag, 因为每台机器生成的 Etag 都不一样 -
分布式系统里多台机器文件的 Last-Modified 必须一致, 以免负载均衡不同导致对比失败
用户行为影响浏览器缓存行为
用户操作 | Expires/Cache-Control | Last-Modified/Etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进、后退 | 有效 | 有效 |
F5刷新 | 无效 | 有效 |
Ctrl+F5刷新 | 无效 | 无效 |