缓存策略

2,807 阅读6分钟

学习整理了web缓存的一些策略,如有不正确的地方,欢迎指正。

浏览器端的缓存规则

缓存行为主要由缓存策略决定,而缓存策略由内容拥有者设置。这些策略主要通过特定的HTTP头部来清晰地表达。

当一个用户发起一个静态资源请求的时候,浏览器会通过以下几步来获取资源:

  1. 本地缓存阶段:先在本地查找该资源,如果有发现该资源,而且该资源还没有过期,就使用这一个资源,完全不会发送http请求到服务器;
  2. 协商缓存阶段:如果在本地缓存找到对应的资源,但是不知道该资源是否过期或者已经过期,则发一个http请求到服务器,然后服务器判断这个请求,如果请求的资源在服务器上没有改动过,则返回304,让浏览器使用本地找到的那个资源;
  3. 缓存失败阶段:当服务器发现请求的资源已经修改过,或者这是一个新的请求(在本来没有找到资源),服务器则返回该资源的数据,并且返回200, 当然这个是指找到资源的情况下,如果服务器上没有这个资源,则返回404。

本地缓存

可以通过设置请求头来进行配置

Expires

 res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString())

Cache Control

 res.setHeader('Cache-Control', 'max-age=30')

注意:Cache-Control:这个是http 1.1中为了弥补 Expires 缺陷新加入的。如果设了max-age,max-age就会覆盖expires

完整代码

http.createServer(function(req, res) {
  let { pathname } = url.parse(req.url, true)
  let filepath = path.join(__dirname, pathname)
  fs.stat(filepath, (err, stat) => {
    if (err) {
      sendError(req, res)
    } else {
      send(req, res, filepath)
    }
  })
}).listen(8080)

function sendError(req, res) {
  res.statusCode = 404
  res.end('Not Found')
}

function send(req, res, filepath) {
  res.setHeader('Content-Type', mime.getType(filepath))
  res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toUTCString())
  res.setHeader('Cache-Control', 'max-age=30')
  fs.createReadStream(filepath).pipe(res)
}

协商缓存

Last-Modified & if-modified-since

  1. Last-Modified与If-Modified-Since是一对报文头,属于http 1.0。
  2. last-modified是WEB服务器认为对象的最后修改时间,比如文件的最后修改时间,动态页面的最后产生时间。
http.createServer(function(req, res) {
  let { pathname } = url.parse(req.url, true)
  let filepath = path.join(__dirname, pathname)
  fs.stat(filepath, (err, stat) => {
    if (err) {
      res.statusCode = 404
      res.end('Not Found')
    } else {
      let ifModifiedSince = req.headers['if-modified-since']
      let LastModified = stat.ctime.toGMTString()
      if (ifModifiedSince === LastModified) {
        res.statusCode = 304
        res.end()
      } else {
        send(req, res, filepath, stat)
      }
    }
  })
}).listen(8080)

function send(req, res, filepath, stat) {
  res.setHeader('Content-Type', mime.getType())
  res.setHeader('Last-Modified', stat.ctime.toGMTString())
  fs.createReadStream(filepath).pipe(res)
}

ETag & If-None-Match

  1. ETag与If-None-Match是一对报文,属于http 1.1。
  2. ETag可以用来解决这种问题。ETag是一个文件的唯一标志符。就像一个哈希或者指纹,每个文件都有一个单独的标志,只要这个文件发生了改变,这个标志就会发生变化。
  3. ETag机制类似于乐观锁机制,如果请求报文的ETag与服务器的不一致,则表示该资源已经被修改过来,需要发最新的内容给浏览器。
  4. 同时使用这两个报文头,在完全匹配If-Modified-Since和If-None-Match即检查完修改时间和Etag之后,如都与服务器的相符,服务器返回304,否则,发送最新内容给浏览器。
http.createServer(function(req, res) {
  let { pathname } = url.parse(req.url, true)
  let filepath = path.join(__dirname, pathname)
  fs.stat(filepath, (err, stat) => {
    if (err) {
      sendError(req, res)
    } else {
      let ifNoneMatch = req.headers['if-none-match']
      let out = fs.createReadStream(filepath)
      let md5 = crypto.createHash('md5')
      out.on('data', function(data) {
        md5.update(data)
      })
      out.on('end', function() {
        let etag = md5.digest('hex')
        if (ifNoneMatch === etag) {
          res.statusCode = 304
          res.end()
        } else {
          send(req, res, filepath, etag)
        }
      })
    }
  })
}).listen(8080)

function sendError(req, res) {
  res.statusCode('404')
  res.end('Not Found')
}

function send(req, res, filepath, etag) {
  res.setHeader('Content-Type', mime.getType())
  res.setHeader('ETag', etag)
  fs.createReadStream(filepath).pipe(res)
}

Etag/lastModified过程如下:

  1. 客户端请求一个页面(A)。
  2. 服务器返回页面A,并在给A加上一个Last-Modified/ETag。
  3. 客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。
  4. 客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。
  5. 服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。

服务器端缓存

CND缓存

CDN缓存,也叫网关缓存、反向代理缓存。浏览器先向CDN网关发起WEB请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态地请求转发到合适的源服务器上。

CDN缓存策略

  1. CDN边缘节点缓存策略因服务商不同而不同,但一般都会遵循http标准协议,通过http响应头中的Cache-control: max-age的字段来设置CDN边缘节点数据缓存时间。
  2. 当客户端向CDN节点请求数据时,CDN节点会判断缓存数据是否过期,若缓存数据并没有过期,则直接将缓存数据返回给客户端;否则,CDN节点就会向源站发出回源请求(back to the source request),从源站拉取最新数据,更新本地缓存,并将最新数据返回给客户端。
  3. CDN服务商一般会提供基于文件后缀、目录多个维度来指定CDN缓存时间,为用户提供更精细化的缓存管理。
  4. CDN缓存时间会对“回源率”产生直接的影响。若CDN缓存时间较短,CDN边缘节点上的数据会经常失效,导致频繁回源,增加了源站的负载,同时也增大的访问延时;若CDN缓存时间太长,会带来数据更新时间慢的问题。开发者需要增对特定的业务,来做特定的数据缓存时间管理。
  5. CDN缓存刷新CDN边缘节点对开发者是透明的,相比于浏览器Ctrl+F5的强制刷新来使浏览器本地缓存失效,开发者可以通过CDN服务商提供的“刷新缓存”接口来达到清理CDN边缘节点缓存的目的。这样开发者在更新数据后,可以使用“刷新缓存”功能来强制CDN节点上的数据缓存过期,保证客户端在访问时,拉取到最新的数据。

CDN优势

  1. CDN节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;
  2. 大部分请求在CDN边缘节点完成,CDN起到了分流作用,减轻了源站的负载。

HTML5缓存思路

  1. 用户可离线访问你的应用,这对于无法随时保持联网状态的移动终端用户来说尤其重要
  2. 用户访问本地的缓存文件,通常意味着更快的访问速度
  3. 仅仅加载被修改过的资源,避免同一资源对服务器多次的请求,大大降低了对服务器的访问压力