静态资源文件配置及缓存问题解决

缓存问题可以算是至今为止遇到最多维持很久的一个问题。这个问题的发生,导致了脚本报错页面不可访问、样式错乱的情况,而且持续时间长。

缓存可以归为两部分,一部分是页面的缓存,一部分是js、css引用的文件缓存。于是具体缓存问题的发生,可以归为两类:

  1. html已经更新了,但是js、css文件取到的还是缓存的文件。
  2. js、css文件已经更新了,但是html缓存还没有更新。

因为两部分缓存的设置大部分不同,所以缓存更新不同步,导致了文件的不对应,从而脚本报错页面不可访问、样式错乱。

以某一现状为例,线上js、css文件的缓存控制方案是这样的:
发布的过程会跑一个本地node的一个命令,遍历存放js、css生产文件,通过node crypto模块来获取文件唯一的版本信息,然后将version对象写入到一个单独的json文件version.json中。

1
2
3
4
5
6
7
8
var md5 = crypto.createHash('md5'),
file = folder + '/production/' + name + '.' + type;

md5.update(grunt.file.read(file));
md5 = md5.digest('hex');
version[type][name] = {
md5: md5
};

js、css文件的引入,是通过一个后端的方法,读取version.json文件配置,将文件名拼上部分md5信息放入到页面中。类似http://st.bbeeii.com/script/production/a.js 这个文件的引入,线上实际引入的链接则是http://st.bbeeii.com/script/production/a-rfgt45jfsg.js ,其中rfgt45jfsg是32位md5信息做了10位截取的。

http://st.bbeeii.com/script/production/a-rfgt45jfsg.js 这个文件请求的发起,会先经过cdn。cdn如果发现有这个文件立即返回。否则cdn向我们的服务器回源,请求/production/a-rfgt45jfsg.js 文件,再返回回去。

自己的服务器这边是没有/production/a-rfgt45jfsg.js 这个实体文件的,这个请求,通过nginx rewrite返回了/production/a.js 。

因为js、css代码文件与后端动态脚本文件是在一个代码库内,通过svn提交到trunk,然后所有的机器svn up更新代码。代码发布svn up的时间会有时间间隔,如果version.json文件先更新,后端脚本读取到这个文件,前台用户发起请求,cdn回源,一气呵成。然后/production/a.js文件实际还没有更新,反而返回回去了。于是cdn就拿到了老的代码,并且是一段比较长的缓存。除非手动更新缓存,缓存期间页面始终都是错误的。

另外,因为用户访问js、css文件并不是定向到cdn服务器(不稳定),有可能请求是到自己的服务器上,从而也会有html文件(缓存较久)还没更新,而js、css文件已经更新的情况。

如果请求的文件/production/a-rfgt45jfsg.js以及上一个版本/production/a-gthdtyh65.js在我们的服务器上实际存在会怎么样呢(多版本共存)?

优化后的发布流程为:

  1. 先发布静态部署文件例如/production/a.js 和版本文件version.json。
  2. 本地任务通过/production/a.js和version.json这两个文件,生成新的/production/a-rfgt45jfsg.js存放到单独的两台机器上(主、备)。
  3. 这两台机器上,跑一个任务,匹配文件前缀,比对last modified时间,保留最新的三到五个版本,其余清除。例如/production/a.js的版本多余限定,将最老的版本清除。

页面中引入的脚本、样式地址,例如:

  1. html缓存,引用的脚本地址也是老的: /production/a-gthdtyh65.js
  2. html已更新,引用的脚本地址也是新的:/production/a-rfgt45jfsg.js

因为/production/a-gthdtyh65.js和/production/a-rfgt45jfsg.js实际已经不是同一个文件(同一文件的不同版本),那么也就不会有缓存的问题了。

优化后的用户访问流程:
用户访问流程图

静态资源的访问,始终经过本地缓存、cdn。
此外,如果版本信息文件处理错误,为避免工具或者人工问题,导致/production/a-gthdtyh65.js文件的访问活着全站性的版本文件没有更新,得到的404,本地服务器在请求获得时将返回一个302 redirect,将文件重定向到/production/a.js,然后返回。

更深的优化项:

  1. 所有js、css部署文件可以全部设置长时间缓存,直接取用户本地缓存,节省带宽。因为存放版本信息和静态资源先发布,生成了对应新版本的实体文件,并且老版本文件也存在着。后端代码发布时,因版本信息更新,文件引入的名称也就更新了。此时html没有更新,引用的老版本文件在;html更新了,引用的新版本文件也在。
  2. 既然前端的发布和后端代码的发布分开,前端代码的访问只通过两台固定的服务器,那也是时候进行代码库分离和“动静分离”了。当然,这还涉及到另外的问题:代码库如何分离以及分离后前后端如何协同开发。

参考来源: