让前端飞

静态资源文件的缓存问题

2014-10-01  本文已影响1650人  江枫

前一阵被人问到一个问题:

开发人员修改一文件,版本下发后期望用户可以访问到修改的最新文件,而不是被浏览器缓存过的历史文件,请问Http有机制可以保证用户访问到最新的文件吗?如果没有,在考虑性能的前提下,如何设计一种可行方案呢?

相信不少人第一直觉会想到和浏览器缓存有关的一些缓存头,例如:

  1. expires指定了文档的失效时间,但是前提要求客户端和服务器端的时钟是同步的,不然就不准确了
  2. cache-control头比实际想象的要复杂的多,cache-control:no-cache表明不应使用缓存文件,而应该直接从服务器重新获取,cache-control:max-age=3600表明从服务器将文档传来之时起,可以认为此文档处于新鲜状态的秒数。

我们把问题细化一下,修改的文件存在两种情况:

  1. 该文件的内容是需要动态填充的,这时缓存的策略为不缓存,每次请求都去服务器重新验证

  2. 对于静态文件的修改,举几个例子看看:
    下面这个是github页面上公共图标的缓存情况,cache-control配置了一个很大的失效时间,同时结合last-modified头实施缓存策略。


    github页面上公共图标

下面这个是知乎中个人头像的缓存情况,可以看到采用了cache-control和etag控制缓存


知乎个人头像

现在的问题是:上述图标要是发生了改变,用户浏览器如何才能及时得到更新呢?

因为cache-control配置了一个很大的失效时间间隔,在用户本地存在缓存的情况下,浏览器是不会再次发起请求的

图片和样式文件的更改一般不会给网站带来灾难性的影响,但如果是js文件被修改但是用户浏览器依旧使用的是过期的缓存文件,这种情况相比较而言对网站的影响就要大得多。

如何避免此类问题呢?结合知乎个人头像的例子,不难想到的一种方案就是对修改的脚本文件添加一个修改的标志,类似下面这个样子

<script src="dir/test.js?modify=true"></script>

如果频繁修改呢,下面这种方式似乎给好一点
<script src="dir/test.js?version=2.0"></script>

上面的方案都是基于script标签的,在模块化大行其道的今天,脚本加载器应该是会考虑诸如此类实际问题的,例如在seajs中有下面的配置功能

seajs.config({ vars: { 'version': '2' } });
define(function(require, exports, module) {
var lang = require('./dir/test.js?version={version}');
});

考虑一下现实吧,假设文件A在系统中很重要,因此存在大量文件引用,如果还采用上述的方案,这无疑是烦人的体力劳动,如何解脱呢?

总体的方案是:

在动态请求的文件中给静态文件动态添加类似于版本号的标志,然后对服务器配置url重写功能(例如apache服务器),在java中可以配置过滤器,对特定的文件进行url重写。

下面给出stackoverflow上一个基于php的实现方案,原文在这里

+ 首先,在apache的配置文件.htaccess中开启重写功能,并且添加规则
RewriteEngine on RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

function auto_version($file){ if(strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file)) return $file; $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file); return preg_replace('{\\.([ ^./]+)$}', ".$mtime.\$1", $file); }

<script href="<?php echo auto_version('/js/base.js'); ?> />

上一篇 下一篇

猜你喜欢

热点阅读