尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Expo自托管OTA更新:从链路原理到可观测性落地实践

Expo自托管OTA更新:从链路原理到可观测性落地实践 做 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上传到自托管服务然后拿着已经安装旧包的设备去触发检查。验证时重点看三种结果正常更新设备请求到新 manifest下载新 bundle重启后新内容生效。观察客户端日志里有Finished downloading new update这类记录。无更新重新上传一个内容完全相同的 bundle客户端请求后返回“无需更新”不会重复下载。这能验证服务端去重和版本比较逻辑。版本不匹配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 / ioschannelruntimeVersion构建产物号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 出现“发了新版本但用户还是旧内容”的问题时按下面顺序排查不要一上来就怀疑服务端代码先看客户端配置设备上安装的包是不是重新构建过updates.url是否指向自托管域名。再看服务端日志有没有收到该设备的 manifest 请求。没有收到说明客户端根本没连上查网络、域名、证书。再查 manifest 内容收到的请求有没有匹配到正确 channel 和 runtimeVersion返回的 update ID 是不是最新的。再看下载日志客户端有没有下载新 bundle下载是否中断。中断查对象存储权限、限流、文件大小。最后看客户端日志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 如果只是学习怎么低成本验证低成本验证的路径可以分成三步在一台小机器上部署服务端用一条本地网络场景验证上传和下载链路用测试设备连接发布一个更新并确认生效用模拟请求跑一遍 manifest 接口观察返回结果在不同 channel 和 runtimeVersion 下的差异重点是先把最小链路跑通而不是一开始就追求高并发、灰度、CDN 这些能力。等你对整套流程有把握了再按生产要求补日志、告警、备份和回滚策略。自托管 OTA 这件事难的不是搭建那一下而是把服务和客户端之间的版本关系、环境关系、异常路径都理顺。Xprem 把更新和可观测放在一起方向上是对的先能发布再知道发得安不安全、成没成功这是自托管方案真正能落地的基础。如果后续部署过程中卡在某一步先回到版本、配置、日志这三个层面排查大多数问题都能定位到具体环节。
返回列表