描述在 grunt 中实现零停机前端部署的关键步骤

解读

面试官想知道你是否能把“前端构建”与“线上发布”打通,用 Grunt 把“本地打包 → 上传 → 流量切换 → 回滚”做成一条零中断的流水线。国内主流场景是 Nginx + 多版本静态目录,因此回答必须围绕“多版本并存 + 原子切换”展开,并体现你对灰度、回滚、缓存、CDN 同步等细节的认知。

知识点

  1. 多版本目录策略:/data/web/static/v1.2.3/…
  2. Nginx upstream 或 try_files 原子切换:软链 or mv 指向最新版本
  3. 文件指纹:Grunt 插件 grunt-filerev 给所有静态资源加 hash,解决缓存击穿
  4. 非覆盖式上传:rsync --link-dest 或 ossutil cp --update,保证旧文件仍在
  5. 健康检查与自动回滚:上传后通过 grunt-http 调用接口验证 200,失败则回指旧软链
  6. 并发与锁:用 grunt-lockfile 防止多人同时发布
  7. CDN 刷新:grunt-aliyun-cdn 或 grunt-qcloud-cdn 批量提交刷新 API,确保边缘节点同步
  8. 灰度/分组发布:结合 Nginx map 模块按 Cookie、IP 段分流,Grunt 任务里动态生成灰度配置并热加载
  9. 回滚速度:保留最近 5 个版本,回滚只需重新指向旧目录,<1 秒完成
  10. 日志与通知:grunt-slack 或 grunt-dingtalk 在发布成功/失败时实时推送,方便值班

答案

我通常把零停机流程拆成 7 个 Grunt 子任务,全部串在 grunt release 一条命令里:

  1. 前置检查
    grunt.lock 文件若存在直接退出,防止并发;接着跑 eslintjest,只有 0 错误才继续。

  2. 多环境配置注入
    grunt-replace__API__ 替换成线上域名,并写入版本号 v<%= pkg.version %>-<%= grunt.template.today('yyyymmddHHMM') %>,保证每次构建唯一。

  3. 构建与指纹
    顺序执行 cleanwebpack:prodfilerevusemin,产出目录 dist/v1.2.3-202406211530/;所有 js/css 带 hash,图片压缩后小于 50 KB。

  4. 上传(非覆盖)
    通过 grunt-exec 调用 rsync -avz --link-dest=../current ./dist/v1.2.3-202406211530/ remote:/data/web/static/v1.2.3-202406211530/;旧文件不删除,上传耗时 20 秒。

  5. 健康检查
    上传后 grunt-http 请求 https://static.xxx.com/v1.2.3-202406211530/js/app.1234.js,返回 200 且内容与本地 MD5 一致才继续;否则自动删除远端新目录并钉钉报警。

  6. 原子切换
    SSH 到服务器执行 ln -sfn /data/web/static/v1.2.3-202406211530/ /data/web/static/current,Nginx 的 root 指到 current,切换瞬间完成,用户无感知。

  7. CDN 刷新与通知
    调用 grunt-aliyun-cdn:flush 批量刷新 https://static.xxx.com/*;成功后钉钉机器人推送“线上已更新至 v1.2.3,回滚命令:ln -sfn v1.2.2 current”。整个流程 3 分钟内结束,回滚 1 秒内完成,真正做到零停机

拓展思考

  1. 如果项目日 PV 过亿,可把“蓝绿目录”升级为“三级目录”:预发→金丝雀→全量,Grunt 任务里用 map 文件控制流量比例,10%→30%→100% 自动阶梯推进。
  2. 对 SSR 或微前端场景,静态资源零停机只是第一步,还需把模板文件(html)纳入版本控制,用 Nginx SSI 拼接,确保灰度模板灰度静态版本一致。
  3. 为了审计,可在 Gruntfile 里把本次版本号、MD5、操作人、Git commit 写入 /data/web/static/versions.json,前端实时读取,方便运营一键回滚。