type
Post
status
Published
date
Oct 2, 2026
slug
summary
一次升级先失败回滚,真因不是兼容性,是下载速度;而兼容性要看运行时输出。
tags
Write
版本升级
插件兼容
回滚
DSH 排障现场
category
DSH
icon
password
url
0.2.0 是一道插件墙
那天晚上九点五十九分,我让助手跟踪一下 web 端升级到最新版本的全过程。我的预期很朴素:点一下升级,等几分钟,重启,完事。结果它先失败、自动回滚,当晚才升上去。中间那段我判断错了两次,值得记下来。
一、先搞清楚「web 端」是哪一个
我本机跑着不止一个实例。有桌面端、有常驻的机器人侧、还有被系统服务托管的一个 web 实例,各自有独立的配置目录和端口。所以我做的第一件事不是升级,是确认目标:这次要动的是被托管、监听某个固定端口的那个 web 实例,它跑在全局安装的包目录里。
这一步听起来啰嗦,但它是后面所有判断的前提。如果连「升级的是哪个进程」都没对齐,后面看到的现象全是噪声。
确认完之后,更新助手已经在跑了。它有一个待执行的任务描述文件,有一个分离出来的助手进程,状态是「观察中、安装中、第一次尝试、共两次机会」。日志按时间戳落盘。这些是我后面唯一的观测窗口。
二、三件事同时发生,我一开始只看见了两件
观测过程中我读到三件事。
第一件:安装目录里的包描述文件已经写成了新版本号,但真正干活的安装进程长时间零 CPU、八分钟以上不退出。状态文件里的耗时字段停在一个很小的值上不再刷新。我当时的结论是「卡住了」——这个结论后来证明是错的,状态文件陈旧,它根本不反映进度。
第二件:启动日志里出现端口占用。旧宿主还没退出,新实例抢不到端口,于是进入「反复重试、每隔半分钟吐一份诊断」的循环。三分钟内七条启动诊断。
第三件,也是当时最容易被忽略的一件:启动输出里有二百多条「跳过某个插件包」的记录,覆盖了三十多个不同的插件。它们出现在新版本落地的窗口里。
我当时把注意力放在前两件上,觉得是端口没释放导致的连锁失败。第三件我扫了一眼,归到「升级过程中的正常噪声」里去了。这是那晚第一个判断错误。
三、它其实已经失败并回滚了
过了几分钟我复查状态文件,看到的是「就绪」——但就绪的是旧版本。更新助手在安装阶段超时,走了失败兜底,把服务拉回原来的版本,端口重新被旧宿主接管。
也就是说,我看到的端口占用、反复重试,不是「升级中的阵痛」,而是「升级已经结束、并且失败了」的余波。
这时候我说了一句后来被自己反复引用的话:我就是要升级到最新版本,如果有不兼容的或者需要改配置的,你改;不兼容的列出来告诉我。另外检查一下我自己的插件要不要跟着做兼容。
这句话把任务从「跟踪」改成了「解决」。
四、真阻塞不是兼容性,是下载速度
我原本以为卡在插件兼容上。查下去发现不是。
真正的问题是下载。官方源实测速度只有几十 KB 每秒,一个几 MB 的包九十秒都下不完;而新版本的依赖树有五百多个包,助手的安装超时是十分钟——这组合必然失败,跟插件一点关系都没有。
换成国内镜像之后,同一个安装二十二秒就完成了。速度差了两个数量级。
所以升级前该做的两件前置动作是:把更新助手的额外安装参数指向镜像,把安装超时从十分钟提到半小时。这两条不改,后面所有关于插件的讨论都没有意义,因为根本装不完。
五、二百多条跳过,才是那堵墙
现在回头看第三件事。
那二百多条「跳过插件包」的记录,覆盖了三十多个插件,原因是它们的依赖声明里,对核心包的版本范围最高只到零点一几。而语义化版本里,零点一几的上界是「小于零点二点零」。所以新版本一落地,这些插件整包被跳过——不是报错,是安静地不加载。
这就是标题里说的墙。新版本不是「点一下就能升」的目标,它是一道版本边界:插件侧没有先发一轮声明支持新核心的版本,升上去就只剩一个裸核。
我自研的插件大约十几个,全在这三十多个里面。
这里有个更值得记的坑:更新检查器当时告诉我「已装插件的版本范围都接受」。它看的是插件自己声明的字段,跟运行时实际加载的结果相反。兼容性要以运行时的跳过记录为准,不要信检查器的结论。 这是我那晚第二个、也是更贵的判断错误。
六、两个正规出口
墙不是不能过,有两个出口。
一个是对自己人:给自研插件的依赖声明追加一段支持新核心的范围。注意两点——必须带预发布标记,因为不带标记的范围不含预发布版;以及是追加不是替换,这样旧版本下照常能装。我把二十多个自研仓库补完之后,跳过数从三十多降到七。
另一个是对第三方:用插件命令给某个包开一个版本例外,显式声明「我知道有风险,让它过」。这条适合那些不再维护、但你确实需要的插件。
补完声明之后当晚升级成功。
可迁移的几条
一、升级前先确认「升的是哪个进程」。多实例环境下,目标没对齐,后面全是噪声。
二、状态文件可能陈旧。耗时字段不刷新不等于卡住,要看进程本身的 CPU 和日志。
三、兼容性以运行时输出为准。检查器说「都接受」,运行时可能整包跳过——信后者。
四、大版本升级前先量一下下载速度。依赖树几百个包配上十分钟超时,失败是必然的,跟代码无关。
五、插件生态能不能跟上新版本,看的是插件侧有没有先发一轮声明支持新核心的版本。没有这一轮,升级的收益就是一个裸核。
六、失败回滚不是事故,是兜底在工作。真正要花时间的是搞清楚它为什么失败——那晚的答案跟我的第一直觉完全相反。