把 Electron 更新做快:我在 OTA 上的几次推倒重来

把 Electron 更新做快:我在 OTA 上的几次推倒重来

💡 原文中文,约8600字,阅读约需21分钟。
📝

内容提要

LobeHub 对 Electron 更新机制进行优化:将原本手写 renderer 层 OTA 改为把 app.asar 拆分为 shell 与可独立 OTA 的 core,并引入内容寻址、zstd 差分、Range 下载和硬链接。Canary 发版时间从约 64 分钟缩短至 8 分钟,renderer 改动可热重载。下一步计划在 Stable 中用 Sparkle 替换 electron-updater,实现更小更快的差分更新。

🔎

延伸解读

从手搓到重构:OTA 方案的演进逻辑

文章按时间线梳理了 LobeHub 的 OTA 优化过程:从早期 Folo 手搓 renderer 热更新,到 LobeHub 逐步引入 gzip 压缩、V2 包格式、app.asar 拆分 shell 与 core,再到内容寻址、zstd 差分和 Range 下载。每次改动都针对具体瓶颈,如 blockmap 生成慢、小版本下载量大、散文件安装慢等。这种迭代方式说明 OTA 优化没有一劳永逸的方案,需要根据实际数据不断调整。

为什么放弃 electron-updater 的全量更新

文章指出 electron-updater 在 macOS 上依赖 Squirrel.Mac,全量更新需下载完整 zip,退出后由 ShipIt 替换整个 .app,耗时约 10 秒。其 blockmap 增量机制在压缩后的安装包上分块,导致 app.asar 改动后大量块指纹变化,小版本升级仍需下载数兆。相比之下,Sparkle 在解压后的 .app 上做二进制差分,补丁大小更贴近真实改动,安装也更快。这解释了作者转向 Sparkle 的核心动机。

renderer OTA 的复杂度陷阱

文章详细描述了 renderer 层 OTA 的兼容性判断难题:在 LobeHub 的 workspace 中,共享包可能同时被 main 和 renderer 引用,修改一个常量就可能改变 main 产物,导致无法安全走 OTA。为此团队写了近千行脚本(mainHash.mjs 等)来推导兼容性,但可读性和可维护性差。最终作者认为方向错误,改为将 asar 作为引导器,只判断底层(Electron 版本、native 代码)是否变化,从而简化决策。

散文件与 asar 的取舍:数据驱动的决策

文章提到,最初担心散文件安装慢,但实测发现 OTA 版本约 1700 个文件、111 MB,组装在后台进行,macOS 切换仅 300–340 ms,Windows 首次 OTA 端到端 4.19 秒。硬链接使版本间共享 inode,多保留一个版本实际多占 0 KB。若改为 asar,则无法共享 inode,磁盘占用接近翻倍。因此保留散文件,仅将内置 core 打包为 asar。这体现了基于实际测量而非直觉的优化思路。

❓

Q&A

LobeHub 为什么放弃手写 renderer 层 OTA,转向拆分 app.asar?

手写 renderer OTA 需要判断 main 和 preload 是否变动,在 LobeHub 的 workspace 依赖树中判断极其复杂,相关脚本近千行,可读性和可维护性差。因此改为将 app.asar 拆成 shell 和可独立 OTA 的 core,简化判断逻辑。

LobeHub 的 core OTA 是如何实现增量更新的?

对 core 内文件做内容寻址,按 sha256 上传对象,并生成 zstd 差分补丁;客户端根据 manifest 计算缺失文件,优先下载补丁,缺失过多或失败则回退整包。第二版将 core 打包为 asar,OTA 产物合并为 pack 文件,客户端通过 HTTP Range 请求获取所需片段。

LobeHub 的 OTA 更新在 Canary 渠道上速度提升了多少?

全量构建中位数约 64 分钟,最快 43 分钟;OTA 后只动 renderer 的发版中位数 4.3 分钟,core OTA 在 runner 空闲时端到端约 8 分钟。

LobeHub 如何判断一次更新是 reload 还是 relaunch?

CI 对比新旧文件树,若改动文件全在 dist/renderer/ 下则标记为 reload,否则标记为 relaunch。reload 只需切换 renderer 目录并刷新窗口,relaunch 需要退出 App 再重启。

LobeHub 的 shell 启动时如何选择加载哪个 core 版本?

shell 依次查找待生效的新版本、当前版本、上一个版本、内置 core.asar。每个候选需通过 ABI 匹配、manifest 签名有效、渠道一致、版本比内置新等检查。一个版本连续 3 次启动失败会被拉黑,自动退到下一个候选。

LobeHub 下一步计划如何优化 Stable 渠道的更新?

计划在 Stable 中用 Sparkle 替换 electron-updater,实现更小更快的差分更新。Sparkle 在解压后的 .app 上按文件做 diff,补丁大小跟随真实改动,安装更快,且增强版支持跨多个版本打补丁链。

🏷️

标签

➡️

继续阅读