迫于常常服务器性能渣渣,常常需要增加各种缓存,来减轻服务器压力,对一些常见的缓存机制进行一些梳理。
HTTP 缓存机制
HTTP/1.1 标准中固定了 HTTP 缓存的两种方式: expiration 机制 和 validation 机制。前者是通过减少服务器和浏览器的循环次数,后者通过只响应头部信息较少网络带宽。(有些参考资料,将前者称为强缓存,后者称为协商缓存)
强缓存优先级高于协商缓存
强缓存
expiration 机制通过 Expires 和 Cache-Control 两个字段来标明失效规则。
Expires
Expires 的值为服务器返回的到期时间,即下一次请求时,请求时间小于服务器返回的到期时间,直接使用缓存数据。但是由于客户端和服务器可能有时间差,会导致较大的缓存命中误差,所以 HTTP/1.1 使用了 Cache-Control 替代。
Cache-Control
Cache-Control 是非常重要的规则。取值如下:
- private: 默认取值,表示客户端可以缓存
- public: 客户端和代理服务器都可以缓存
- max-age: 缓存内容会在 xxx 秒后失效
- no-cache: 缓存前确认必须先确认其有效性
- no-store: 所有内容都不会缓存
注意,Cache-Control 是一个通用的首部字段。所以,取值对客户端和服务器端含义会有所不同。
应用 HTTP/1.1 版本的缓存服务器同时存在 Expires 首部字段的情况时,会优先处理 max-age 指令,而忽略 Expires 首部字段。
强缓存请求数据流程如下: 客户端向缓存服务器(缓存数据库)请求数据,如果有缓存数据且未失效,则返回数据。如果没有缓存数据或缓存数据失效,则向服务器请求数据,服务器返回数据和缓存规则,客户端收到并将数据和缓存规则存入缓存。
协商缓存
协商缓存的方式是客户端发送请求验证缓存标识所对应的数据是否失效,如果服务器判断数据有效,则返回一个 304 状态码(即报文首部和状态行),而不用返回报文主体。协议中规定了两种缓存标识。
Last-Modified/If-Modified-Since
客户端第一次请求服务器时,服务器在响应报文的首部通过 Last-Modified 字段告知浏览器资源的最后修改时间。
再次请求时,客户端在请求报文的首部添加 If-Modified-Since 字段,该字段的值是上次请求时,服务器返回的 Last-Modified 的值。
服务器收到请求发现有 If-Modified-Since 则与被请求资源的最后修改时间进行比对,若资源的最后修改时间大于 If-Modified-Since 所提供的值,说明资源被改动过,则响应资源内容,返回状态码 200。否则,说明资源没有被改动,则返回 304,告知浏览器可以使用所保存的 cache。
Etag/If-None-Match 优先级高于 Last-Modified/If-Modified-Since
客户端第一次请求服务器时,服务器在响应报文的首部通过 Etag 字段告知浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。
同理,再次请求时,缓存数据的唯一标识被添加到请求报文的 If-None-Match 字段中。
服务器收到请求发现有 If-None-Match 则与被请求资源的唯一标识进行比对,不同,说明资源被修改过,返回 200。相同,则说明资源未被修改,返回 304。
HTTP 缓存过程总结
- 浏览器第一次加载资源,服务器返回 200,并将数据和缓存规则存入缓存。
- 再次请求资源时,如果当前时间和上一次返回 200 的时间差不超过 Cache-Control 设置的 max-age,则没有过期,命中缓存(如果浏览器不支持 HTTP/1.1,则用 expires 判断是否过期)。如果时间过期,则向服务器发送 If-None-Match 和 If-Modified-Since 请求。
- 服务器收到请求,首先根据 Etag 的值判断被请求的文件有没有被修改,Etag 值一致,则命中协商缓存,返回304;如果不一致,返回新的资源的缓存规则和状态码,并返回 200。
- 如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求的文件的最后修改时间做对比,一致则命中协商缓存,返回 304;否则返回新的 Last-Modified 和数据,返回 200。
HTTP缓存生效
- 对于一下操作,如地址栏回车、页面链接跳转、新开窗口、前进和回退,两种机制( expires/validation )都是有效的。
- F5 刷新操作,Expires/Cache-Control 是无效的,Last-Modified/Etag 是有效的,也就是说强制协商缓存。
- Ctrl + F5 强制刷新,两者都是无效的。
HTML5 应用缓存
HTML5 引入了应用程序缓存,这意味着 web 应用可进行缓存,并可在没有因特网连接时进行访问。
应用程序缓存为应用带来三个优势:
- 离线浏览 - 用户可在应用离线时使用它们
- 速度 - 已缓存资源加载得更快
- 减少服务器负载 - 浏览器将只从服务器下载更新过或更改过的资源。
在需要离线使用的页面中添加 manifest 属性,用于指定缓存清单文件的路径。
数据存储
cookie
Cookie 的工作机制是用户识别及状态管理。服务器对任意的 HTTP 请求发送 Set-Cookie 首部字段作为响应的一部分。
Set-Cookie 字段的属性
- name 名称 一个唯一确定的 cookie 的名称,名称必须被 URL 编码
- value 值 存储在 cookie 中的字符串的值。值必须被 URL 编码
- domain 域名 确定 cookie 有效的域,所有向该域发送的请求中都包含这个 cookie 信息
- path 路径 对于制定域中的那个路径,才应该向服务器发送 cookie
- expire 失效时间
- secure 安全标识 指定后,cookie 只能在使用 SSL 连接时(即HTTPS通信时)才发送到服务器
- HttpOnly 它使得附加在 HttpOnly 属性后的 Cookie 内容无法被 JavaScript 读取,可以防止 XSS 攻击
Web Storage
Web Storage 是 HTML5 的一部分,目的是克服由 cookie 带来的一些限制。
- 提供一种在 cookie 之外存储会话数据的途径
- 提供一种存储大量可以跨会话存在的数据的机制
localstorage
存储在 localStorage 中的数据保留到通过 JavaScript 删除或者是用户清除浏览器缓存。同时,localStorage 支持同源策略。也就是说,要访问同一个 localStorage 对象,页面必须来自同一个域。
sessionstorage
sessionStorage 对象存储特定于某个会话的数据,该数据只保持到浏览器关闭。存储在 sessionStorage 中的数据可以跨越页面刷新而存在。
IndexedDB
IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
IndexedDB 具有以下特点。
- 键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
- 异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
- 支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
- 同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
- 储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
- 支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。