Expo自托管OTA更新:从链路原理到可观测性落地实践
2026/8/30 3:54:26 网站建设 项目流程

做 Expo 应用开发的人,大概率绕不开 OTA 更新这件事。Xprem 按项目标题来看,是给 Expo 应用做自托管 OTA 更新和可观测性的方案:不把更新分发完全押在 EAS 云服务上,而是自己搭建服务端,同时把更新链路里的检查、下载、应用、回滚这些环节用指标和日志管起来。

这篇文章适合三类人:正在评估要不要把 Expo 更新链路迁回自己服务器的团队;有数据合规、私有化交付或离线部署需求,必须自建更新通道的开发者;以及对 Expo OTA 机制感兴趣,想弄明白从发布一个 JS Bundle 到线上设备应用更新,中间到底发生什么的人。

下面不按“下载即用”的安装教程来写,而是按一个团队真正落地自托管 OTA 时会遇到的链路顺序拆:先理解环节,再部署服务端,再接入客户端,最后处理灰度、回滚、监控和排查。

1. 先搞清楚自托管 OTA 方案要覆盖哪些环节

1.1 Expo 的 OTA 链路里,原本依赖了什么

Expo 应用的 OTA 更新,本质上不是“整包热更”,而是更新 JS Bundle 和静态资源。应用启动时,expo-updates会向一个更新服务地址发起请求,服务端返回一个 manifest,客户端根据 manifest 里的信息决定下载哪个 bundle、哪些 assets,下载完成后在合适的时机切换加载。

这个机制默认由 EAS Update 托管。EAS Update 替你做了几件脏活:

  • 存储上传的更新包和资源文件
  • 按 channel、runtimeVersion、平台、设备信息筛选返回版本
  • 处理更新包的签名校验
  • 提供 CLI 或 API 让 CI 上传新版本
  • 记录请求日志和版本分布数据

一旦决定自托管,这些能力都要有自己的替代品。Xprem 这类项目想解决的就是这件事:把“更新服务”和“可观测”两个模块都收回到自己的基础设施里。

1.2 自托管之后,参与者从两方变成三方

使用 EAS Update 时,参与方是:开发者的构建机、EAS 云服务、设备上的客户端。自托管之后,中间那一环换成自己部署的服务。

参与方看起来只是替换了一个服务地址,实际上要多管几样东西:

  • 服务进程本身要能稳定运行,挂掉会影响所有新设备、甚至旧设备启动时的检查
  • 上传的更新包要落到持久化存储,不能重启就丢
  • 数据库或至少一套元数据机制,要记住每个更新包属于哪个应用、哪个 channel、哪个 runtimeVersion
  • 客户端请求量增长后,服务端要有日志、限流和监控能力,否则问题出现时无从下手

这里最关键的一点是:客户端在启动时会频繁请求更新服务,不是只在发布时访问一次。所以自托管方案不只是一个上传下载工具,它本质上是一个面向生产环境的接口服务。Xprem 的方向里同时出现 OTA updates 和 observability,原因就在这里:更新链路一旦自建,可观测性就不是加分项,而是必需品。

2. 部署前先确认这些前置条件

2.1 服务端需要准备的运行环境

从常见部署逻辑来看,自托管 OTA 服务通常需要三块基础设施:

组件作用常见形态
应用服务提供 manifest 接口、上传接口、管理接口Docker 容器、Node/Python 等进程
数据库存应用、channel、更新包版本、请求元数据PostgreSQL、MySQL、SQLite
对象存储存 JS Bundle、assets 文件S3、MinIO、本地磁盘目录

如果你只是在家里服务器上跑通功能,用一个进程加一个 SQLite 加本地磁盘就够了。如果要长时间放生产环境,我更建议一上来就让数据库和对象存储跟应用服务分离,这样后续做多副本部署、备份恢复都会省事。

部署前还要确认几个点:

  • 服务端版本对 Node 或 Docker 的版本要求,先看官方 README 里的最低版本,不要直接用最新的运行时去猜
  • 外网访问是否能打通,客户端设备能不能直接访问你的服务域名,端口、防火墙、反向代理都要考虑
  • 是否需要 HTTPS 证书。Android 默认不允许明文 HTTP 请求,iOS 也有 ATS 限制,这决定了你测试时能不能直接用 IP 加 HTTP 访问

2.2 客户端 Expo 版本和配置状态

服务端准备之前的另一半工作,是确认客户端的接入基础。

Xprem 这类方案通常要求项目已经安装并配置了expo-updates库。你可以先在你的 Expo 项目里检查:

npx expo install expo-updates

安装之后,查看app.json里是否已经有updates字段。常见的配置项包括:

{ "expo": { "updates": { "enabled": true, "url": "https://ota.example.com", "checkAutomatically": "ON_LOAD", "fallbackToCacheTimeout": 0 }, "runtimeVersion": "1.0.0" } }

url指向自托管服务的根地址,runtimeVersion决定了客户端在什么条件下接受更新,checkAutomatically决定何时发起检查。这几个配置是自托管接入的核心,后面排查问题也基本都从它们开始。

还有一个容易被忽略的点:修改updates.url后必须重新构建原生应用并生成新的安装包。只改 JS 代码不会生效,因为 OTA 配置是编译进原生层的。所以第一批测试设备安装的应该是重新构建的 Development Build 或归档包。

3. 最小可用链路怎么跑通

3.1 先启动服务端,再验证接口

不管用 Docker Compose 还是裸进程启动,第一步都不是急着对接客户端,而是确认服务端自己活着。

启动完成后,先用浏览器或 curl 访问服务根地址或健康检查接口:

curl https://ota.example.com/status

如果返回正常的 JSON,比如包含服务名和版本号,说明进程至少起来了。接着再看上传接口能不能接收更新包。多数方案会提供 CLI 或 HTTP 上传接口,比如:

# 示例,实际命令以项目文档为准 xprem update:upload --app my-app --channel production --runtime-version 1.0.0 --bundle ./dist

我的建议是:先上传一个最小 bundle,确认服务端能存下来,数据库里能看到记录,对象存储里有对应文件。走到这一步,服务端才算真正可用。

3.2 客户端指向自托管地址,重新构建

服务端就绪后,把app.json里的updates.url改成自托管域名,重新构建客户端安装包。

第一次接入时不要开复杂策略,先把checkAutomatically保持默认或设为ON_LOADfallbackToCacheTimeout也可以先用默认值。这样应用启动时会自动向新地址发起检查,方便观察链路是否通。

构建完成后装到测试设备上,打开应用,然后去看服务端访问日志:

  • 有没有收到来自该设备的 manifest 请求
  • 如果收到了,返回的是空 manifest 还是包含具体更新包的 manifest
  • 客户端有没有继续请求下载 bundle 和 assets

这里特别容易踩的坑是:配置改完后没有重新构建,导致设备上的应用还在请求旧的 EAS 地址。如果服务端日志里一直看不到请求,先确认安装包是新的,再确认手机网络能访问你的域名。

3.3 发布一个更新,验证三种结果

链路通了之后,做一个真正的发布测试。把应用内某个文案改掉,重新导出 bundle,上传到自托管服务,然后拿着已经安装旧包的设备去触发检查。

验证时重点看三种结果:

  1. 正常更新:设备请求到新 manifest,下载新 bundle,重启后新内容生效。观察客户端日志里有Finished downloading new update这类记录。
  2. 无更新:重新上传一个内容完全相同的 bundle,客户端请求后返回“无需更新”,不会重复下载。这能验证服务端去重和版本比较逻辑。
  3. 版本不匹配:bundle 的 runtimeVersion 和客户端不匹配时,客户端要么不下载,要么下载后不应用。这能帮你确认服务端的过滤逻辑是否按 runtimeVersion 做了隔离。

这三种结果任何一个不对,都不要急着进入批量阶段。先把链路调对:最小可用就是“单应用、单 channel、单版本”能稳定推一个新版本上去,并且能在客户端确认生效。

4. 多环境、多应用和版本策略怎么设计

4.1 channel 和 runtimeVersion 的关系

单应用跑通后,下一步就是多环境。channel 是逻辑上的更新通道,用于区分 development、staging、production 等环境;runtimeVersion 用于区分二进制原生代码的兼容性。

它们的分工可以这么理解:

  • channel 管“这批设备该从哪个通道拿更新”
  • runtimeVersion 管“这个更新包和当前设备上的原生代码是否兼容”

生产实践中,channel 和 runtimeVersion 是两个独立维度,要分开管理,不能混成一个字段。常见做法是:

channelruntimeVersion用途
development1.0.0开发同学手动触发更新
staging1.0.0测试验证
production1.0.0正式用户

同一套 channel 配置可以对应多个 runtimeVersion,因为你的线上 App 可能同时存在多个原生版本,每个原生版本都需要有对应的更新通道。

4.2 多应用隔离:命名空间、应用标识和存储目录

如果一个自托管服务要服务多个 App,必须在一开始就设计好隔离。上传更新时至少要区分:

  • 应用 ID(例如 Expo 项目里的 slug 或自定义应用标识)
  • 平台(android / ios)
  • channel
  • runtimeVersion
  • 构建产物号(buildNumber / versionCode)

数据库里建议用“应用 ID + channel + runtimeVersion + 平台”作为查询维度。对象存储里也可以用同样的结构组织目录,例如:

updates/my-app/production/1.0.0/android/<update-id>/bundle updates/my-app/production/1.0.0/android/<update-id>/assets/

这样设计的好处是排查问题时路径清晰,备份恢复时也能按应用粒度处理。不要在根目录平铺所有更新包,文件多了之后找问题会非常痛苦。

4.3 强制更新、灰度发布和回滚

生产环境只做到“能更新”远远不够,还要能控制更新节奏。

  • 灰度发布:按设备比例或用户 ID 白名单返回新 manifest,其余设备继续返回旧版本。这个能力通常需要服务端在做 manifest 过滤时支持随机抽样或按请求头字段过滤。
  • 强制更新:有些变更不兼容旧 bundle,必须让用户升级到新版本后才能继续使用。这需要在 manifest 里携带强制更新标记,客户端根据标记决定是否阻塞使用。
  • 回滚:发布后有严重问题,最快速的处理不是写代码修复,而是把服务端“最新可用版本”指回上一个正常版本。所以服务端不能只存最新一个包,要保留可回滚的历史版本。

我见过很多团队到这一步才开始想回滚,结果发现自己的自托管服务根本没有“把某版本设为当前版本”的接口。建议在最开始就确认清楚:Xprem 或你选用的方案里,回滚是通过管理接口操作,还是只能重新上传旧 bundle。前一种方式在紧急情况下的操作成本低很多。

5. 可观测性:日志、指标和告警

5.1 更新链路里最值得盯的指标

自托管 OTA 服务上线后,你需要能回答这几个问题:今天有多少设备检查了更新?成功拿到新版本的有多少?下载失败的有多少?线上几个 runtimeVersion 的活跃分布情况如何?

对应下来,核心指标大概是这样:

指标含义判断标准
检查请求量manifest 接口的调用次数与活跃设备量匹配
更新下发量返回新 manifest 的次数大于 0,且和发布窗口对应
下载完成量bundle 和 assets 下载成功次数接近更新下发量
应用成功率客户端应用新 bundle 后上报成功接近下载完成量,否则回滚率高
活跃版本分布当前设备使用的 runtimeVersion 占比新版本递增,老版本递减
回滚率发布后被迫回滚的次数生产环境应接近 0

没有这些指标,你无法判断一次发布到底是成功还是只是“服务端没报错”。这也就是 Xprem 把 observability 和 OTA updates 放在一起的原因:更新能力只是分发链路,可观测性才是你对这次分发有没有把握的证据。

5.2 告警阈值怎么设,避免“能更新但没人知道”

告警不要等出了问题再配。上线第一周就可以配一条最基础的规则:服务端应用进程不可用,直接告警。这一条比什么都重要,因为 OTA 服务挂了,后果不是马上崩,而是用户得不到新版本、新问题无法快速修复,等发现时已经过去很久。

第二类告警是请求量异常下降。如果你知道自己的活跃设备有几千台,而某天检查请求量突然跌到几十,大概率不是用户减少了,而是服务端出问题或客户端配置失效。这类指标比 CPU 告警更能反映问题本质。

第三类是下载失败率升高。正常网络环境下下载失败率应该很低,如果某次发布后失败率明显上升,优先检查 bundle 体积是不是异常变大,或者对象存储是否限流。

阈值怎么设?我建议先用一周正常数据做基线,再根据基线上下浮动 30% 到 50% 来设告警。不要拍脑袋设一个固定数字,因为不同 App 的用户量、请求频率差别太大。

5.3 更新不生效时的排查顺序

自托管 OTA 出现“发了新版本但用户还是旧内容”的问题时,按下面顺序排查,不要一上来就怀疑服务端代码:

  1. 先看客户端配置:设备上安装的包是不是重新构建过,updates.url是否指向自托管域名。
  2. 再看服务端日志:有没有收到该设备的 manifest 请求。没有收到,说明客户端根本没连上,查网络、域名、证书。
  3. 再查 manifest 内容:收到的请求有没有匹配到正确 channel 和 runtimeVersion,返回的 update ID 是不是最新的。
  4. 再看下载日志:客户端有没有下载新 bundle,下载是否中断。中断查对象存储权限、限流、文件大小。
  5. 最后看客户端日志expo-updates在应用层会有明确的日志,例如更新被拒绝时通常会写明原因。

这个顺序特别重要。很多人在第 1 步没确认的情况下就去改服务端逻辑,改完发现不是代码问题,白白浪费时间。

6. 自托管 OTA 的常见坑和边界

6.1 runtimeVersion 不一致是最大的坑

自托管 OTA 的失败案例里,出现频率最高的就是 runtimeVersion 没对齐。

Expo 的更新机制要求客户端声明的 runtimeVersion 和更新包声明的 runtimeVersion 一致,才会应用这个更新。如果你改了原生代码但忘了更新 runtimeVersion,发布的新 bundle 会被旧客户端拒绝;反过来,如果你改了 runtimeVersion 但客户端没有被重新构建,也一样拉不到新包。

验证方法:上传时把 bundle 的 runtimeVersion 和 channel 都打印到日志里,客户端请求时也把请求参数打出来,两边一对比就清楚。

6.2 服务器带宽、存储和请求频率的边界

自托管不等于无限资源。一个 Expo 应用的基础更新包通常有几 MB 到几十 MB 不等,资产文件多的时候可能上百 MB。如果你的用户量不小,每次发布都会被大量设备同时请求下载。

在这里要注意三个边界:

  • 存储:建议开启对象存储版本清理,历史版本保留最近 N 个即可,不要无限堆积。
  • 带宽:可以先用估算方式测一下,比如 100 台设备同时下载 20MB 的包,高峰期瞬时吞吐会到 2GB 级别,普通服务器带宽撑不住。遇到这种情况就要考虑 CDN 或尽量减小 bundle 体积。
  • 请求频率:客户端默认会在启动时检查更新,如果当日活跃用户很多,manifest 接口要能扛住短时高并发,建议在上游加一层缓存,或至少确认服务端的连接池配置合理。

6.3 HTTPS、签名和访问控制

自托管服务直接暴露公网时,安全设计不能省。

第一,HTTPS 必须配。Android 的网络安全配置默认阻止明文流量,iOS 的 ATS 也有限制,不配 HTTPS 的话客户端请求一上来就被系统拦掉,排查时会非常困惑。

第二,更新包签名要保留。Expo 的更新机制里,更新包需要签名校验,自托管方案通常会提供生成私钥和公钥的流程。客户端和上传工具要配对使用,密钥丢失会导致无法发布更新。

第三,上传接口和管理接口不要公开无鉴权。至少用 token 保护上传接口,管理页面加登录校验。否则别人知道你的服务地址后,可以任意上传版本,这在大用户量的应用里会导致严重事故。

7. Xprem 适合谁,不适合谁

7.1 值得用的情况

从 Xprem 的定位看,以下几种情况值得认真评估:

  • 你的业务对数据所在位置有硬性要求,更新包和访问日志不能放在第三方云上
  • 你已经有一套内部的发布基础设施,希望把 Expo OTA 也纳入统一管理
  • 用户规模比较大,EAS Update 免费层或按量计费的成本开始变得不可接受
  • 你需要在更新链路里做更细的日志、版本追踪、灰度控制,而托管服务的灵活性不够

这些场景下,自托管 OTA 的核心价值不是“省一个云服务订阅”,而是把发布链路的控制权拿回来。

7.2 建议再等一等的情况

反过来,如果你只是个人项目或者团队规模很小,更新频率也不高,直接用 EAS Update 会更省心。自托管意味着你同时要维护服务端、数据库、存储和监控链路,任何一个环节出问题都会直接影响线上更新能力。

另外,如果团队里没有人熟悉 Expo 的更新机制,也不要一上来就自托管。先跑通 EAS Update,理解 channel、runtimeVersion、manifest 这些概念之后,再迁移到自托管方案,排查问题时会顺手很多。

7.3 如果只是学习,怎么低成本验证

低成本验证的路径可以分成三步:

  1. 在一台小机器上部署服务端,用一条本地网络场景验证上传和下载链路
  2. 用测试设备连接,发布一个更新并确认生效
  3. 用模拟请求跑一遍 manifest 接口,观察返回结果在不同 channel 和 runtimeVersion 下的差异

重点是先把最小链路跑通,而不是一开始就追求高并发、灰度、CDN 这些能力。等你对整套流程有把握了,再按生产要求补日志、告警、备份和回滚策略。

自托管 OTA 这件事,难的不是搭建那一下,而是把服务和客户端之间的版本关系、环境关系、异常路径都理顺。Xprem 把更新和可观测放在一起,方向上是对的:先能发布,再知道发得安不安全、成没成功,这是自托管方案真正能落地的基础。如果后续部署过程中卡在某一步,先回到版本、配置、日志这三个层面排查,大多数问题都能定位到具体环节。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询