这个场景解决什么
版本目录并存、用一条软链指向线上版本,是最省事也最通用的发布模式:/opt/app/v20261012 这样的版本目录各自独立,/opt/app/current 指向当前生效的那一个。发布等于切链,回滚等于把链指回去。它的前提有两条:服务的配置读的是 current 这个路径,而不是写死的版本号;新旧版本的目录都完整保留着。
基本信息
| 场景 | 以软链切换发布版本,秒级上线与回滚 |
|---|---|
| 工具组合 | ln 建链、readlink 验证指向、mv -T 做原子替换 |
| 核心前提 | 先准备好新版本目录并完成自检,再切链 |
| 风险等级 | 中:切链本身很快,但应用是否跟随软链需要提前确认 |
典型使用场景
- 常规发布:新版本解压到独立目录,自检通过后切链生效
- 快速回滚:把 current 指回上一个版本目录,无需重新部署
- 灰度验证:先用一个探测用的软链指向新版本,验证后再切主链
- 多环境共用产物:测试与生产使用同一份构建产物,只是指向不同版本目录
- 发布留痕:目录名带日期或提交号,历史版本一目了然,便于审计
切链与验证
切链与验证
ls -1 /opt/app # v20261011 v20261012 ...
ln -sfn /opt/app/v20261012 /opt/app/current # 直接改指向(非原子)
readlink -f /opt/app/current # 验证最终指向
ls -l /opt/app/current # 看链接本身与目标
systemctl reload myapp # 让应用重新解析路径(视应用行为而定)更稳的原子替换
更稳的原子替换
ln -s /opt/app/v20261012 /opt/app/current.new
mv -T /opt/app/current.new /opt/app/current # rename 语义,原子生效
readlink /opt/app/current
# 回滚:把链指回上一个版本
ln -sfn /opt/app/v20261011 /opt/app/current && readlink -f /opt/app/current切换方式对比
| 方式 | 是否原子 | 适用 |
|---|---|---|
| ln -sfn 目标 链接 | 否:先删后建,存在极短空窗 | 普通场景够用,读端能容忍瞬时失效 |
| ln -s 新链 && mv -T 新链 链接 | 是:rename 系统调用原子替换 | 高并发读端,或对发布抖动敏感的服务 |
| 直接改配置文件里的版本路径 | 不适用 | 不推荐:每次发布都要动配置且难回滚 |
切链之后应用是否立刻使用新版本,取决于它什么时候解析路径:只读一次配置的进程需要 reload 或重启;每次访问都重新解析的脚本型应用会自动跟上。发布前先确认这一点,避免出现「切了链但没生效」的误判。
回滚的前提是旧版本目录还在、且旧版本的依赖与配置仍然兼容。清理历史版本时至少保留最近两到三个,否则回滚会变成重新部署。