描述在 grunt 中实现灰度缓存策略的思路

解读

国内前端面试常把“灰度缓存”与“构建工具”结合考察,目的是看候选人能否把业务发布节奏(灰度)与静态资源缓存(CDN 缓存、本地缓存)打通。Grunt 作为老牌任务运行器,本身只做构建,但可以通过插件编排 + 自定义任务把“灰度”与“缓存”两个维度串起来。面试官想听到的是:

  1. 如何利用 Grunt 的插件体系产出带指纹的资源
  2. 如何按灰度维度(版本号、用户分组、地域、机房)生成多份资源
  3. 如何在 HTML/SSR 模板里动态注入对应灰度的资源地址
  4. 如何保证回滚与灰度切换时缓存立即失效
  5. 整个流程可灰度、可回滚、可审计,且对运维、CDN、Node 层零侵入。

知识点

  • grunt-filerevgrunt-hash——给静态资源加 MD5 指纹,解决“强缓存”问题
  • grunt-usemin——自动把 HTML 里引用的静态资源替换成带指纹的路径
  • grunt-file-creator / grunt-template——按灰度维度动态生成多份入口 HTML
  • grunt-contrib-copy + grunt-contrib-uglify/cssmin——多目录输出,实现“物理灰度”
  • process.env.BUILD_GRAY——通过环境变量把“灰度标识”注入 Grunt 运行时,实现一次构建多条流水线
  • grunt-contrib-clean——每次构建前清理旧灰度目录,防止“指纹残留”导致 CDN 回源错乱
  • grunt-git-rev——把 git commit id 写入灰度目录名,实现版本可溯源
  • grunt-ssh-deploy / grunt-scp——把灰度资源并发上传到 CDN 源站的不同路径,例如 /gray/1.2.3/_next/
  • grunt-contrib-watch + grunt-contrib-livereload——本地联调时按灰度分组快速刷新
  • 缓存策略头:CDN 侧对 /gray/{version}/** 设置 Cache-Control: max-age=31536000, immutable,对 /gray/current/** 设置 Cache-Control: max-age=60, s-maxage=60——实现灰度路径长期缓存、入口路径短时缓存
  • Node 灰度网关——根据用户 cookie/uid 段决定渲染 current 还是 {version} 目录下的 HTML,Grunt 只负责把两份资源提前编译好

答案

整体思路分三层:构建层、部署层、运行时层,Grunt 重点搞定前两层。

  1. 构建层
    a. 在 Gruntfile 里先用 grunt.option('gray') 读取命令行参数,例如 grunt build --gray=1.2.3,缺省则为 stable
    b. 通过 grunt.config.merge 动态把 dest 目录改成 dist/gray/<gray>/,实现物理隔离
    c. 串行任务:
    clean → copy → uglify/cssmin → filerev → usemin → template
    其中 filerev 会把 app.a8b3.js 改成 app.a8b3.987fed.min.jsusemin 自动替换 HTML 里的引用。
    d. 用 grunt-file-creator 生成一份 gray.json,内容 { "version": "1.2.3", "gitRev": "e4f3a2", "resourceMap": {...} },供 Node 网关动态拼接资源域名
    e. 如果要做用户分组灰度,再跑一遍 grunt build --gray=1.2.3 --group=10%,输出到 dist/gray/1.2.3_10/;CI 里用矩阵并行,10 分钟以内可出 5 组灰度包。

  2. 部署层
    a. 通过 grunt-scpdist/gray/1.2.3/ 上传到 CDN 源站 /gray/1.2.3/ 目录,并做 rsync --delete 保证原子覆盖
    b. 上传完成后调用 CDN 刷新接口,只刷 /gray/1.2.3/**,避免整站缓存失效。
    c. 在灰度控制台把 1.2.3 标记为“待灰”,此时线上流量仍为 stable 目录

  3. 运行时层(Grunt 不直接参与,但要预留规范)
    Node 网关读取 gray.json,按用户 uid 尾号决定渲染 1.2.3 还是 stable 目录下的 HTML;回滚时把灰度标记改为 stable 即可,无需重新构建

通过以上三步,Grunt 侧实现了“一次构建、多灰度包、指纹缓存、秒级回滚”的灰度缓存策略,且全程可脚本化、可回滚、可审计。

拓展思考

  1. 增量指纹:大项目全量哈希慢,可结合 grunt-newer 只给变更文件重新生成指纹,缩短 CI 时间 60%。
  2. 边缘灰度:如果 CDN 支持 EdgeRoutine,可在边缘脚本里读取灰度配置,把 app.987fed.js 302 到 app.987fed_gray.js,实现无发布灰度,Grunt 只需多输出一份 _gray 后缀文件即可。
  3. 共享缓存:公共库(react、lodash)单独做 vendor.dll.js,指纹不变时各灰度版本共用,减少 CDN 流量 30%。
  4. 合规审计:在 Gruntfile 里加 grunt.event.on('exit', () => { axios.post('/log', { gray, user: process.env.BUILD_USER }) }),把灰度构建记录实时推送到内部审计系统,满足国内金融、政务项目上线必留痕的合规要求。