2012 年 8 月 1 日,Knight Capital 推送一次软件更新,八台服务器里有一台没拿到新代码。沉睡的逻辑被唤醒,45 分钟内这家公司亏掉约 4.4 亿美元。复盘结论残酷而简单:没有第二位工程师复核这次发布,也没有一把能按总敞口停掉的熔断开关。45 分钟后,才有人手动把它关掉。
这就是「全量发布、无法回滚」在极端尺度下的样子。多数流量 App 当然不会在 45 分钟里亏掉 4 亿美元。但同样的结构性错误,已经嵌进它们交付核心产品逻辑的方式里:当你的关键链路跑在加载远端文档的 WebView 中,每一次服务端改动都是一次面向 100% 用户的全量发布——没有灰度、没有分阶段、没有一把尊重你 App 信任边界的熔断开关。
一次远端改动,就是一次全量发布
在 Web 上,一次坏发布是可以活下来的:你可以灰度、可以回滚、可以翻 flag。移动行业吸取了教训,也基本普及了这套能力——IDC 的 DevOps 调研显示,约 三分之二 的组织正在扩展、使用或试点渐进式交付,其中 45% 用 feature flags 来实现。这么做的人恢复得很快:LaunchDarkly 2024 年调研中,86% 的团队在一天内从故障恢复,42% 在一小时以内。
基于 WebView 的链路,把这份成熟度原封不动地扔掉了。那份远端 HTML 在每次会话都被重新拉取,所以你只要在服务器端改一下,每一台设备上的每一个用户瞬间就看到新逻辑。没有按百分比的发布,没有金丝雀,没有内置的熔断开关。如果改动是错的,你唯一的修复方式是再推一次服务端改动——而那一次同样瞬间打到 100%。爆炸半径,从结构上就是全体用户。
版本碎片化是隐形税
第二个问题是漂移。原生 App 的二进制包在发布那一刻就冻结了,并且会在野外存活好几个月:
- Android 16 发布七个月后,在活跃设备里只占 7.5%,长尾仍停留在 Android 11、12、13、14。
- 一款典型移动产品,在大版本发布六个月后,仍有 20–30% 的活跃用户在上一版本,5–10% 在超过六个月旧的版本上。
- 应用商店审核还要再拖 1–7 天,原生修复才能触达用户。
于是,你服务端侧的 Web 逻辑一直在动,原生壳却原地不动。你在服务端「修好」的结账流程,可能在那四分之一、二进制还守着旧契约的用户身上直接崩掉——而你没法强制他们更新。Web 团队和 App 团队,正在把同一功能的「不同版本」发给同一个用户,却没人认领这道错配。
控制到底在哪里崩掉
| 维度 | 核心逻辑跑在 WebView | 核心逻辑跑在宿主自有 runtime |
|---|---|---|
| 发布 | 瞬间 100%,无灰度 | 按百分比分阶段,天生金丝雀 |
| 回滚 | 再推一次服务端,仍打 100% | 按 App 实例分钟级熔断 |
| 版本一致性 | 冻结的原生壳 + 会变的 Web 逻辑相互漂移 | 签名包版本,按 App 钉死 |
| 坏改动的爆炸半径 | 全体用户,瞬间 | 有界的某个片段 |
| 故障恢复时长 | 等下一次发布 | 分钟级 |
宿主自有 runtime 把什么还给你
宿主自有 runtime 把小程序当作带版本、已签名的包,而不是会变的远端页面:
- 分阶段发布。 新组件版本先给 1%,再 10%,再 100%——金丝雀是设计出来的,不是靠英雄主义。
- 熔断开关。 坏版本按 App 实例在分钟级禁用,而不是祈祷下一次服务端推送能成。
- 版本钉死。 每个 App 版本映射到已知良好的组件版本,那四分之一守在旧二进制上的用户,仍然拿到匹配其契约的逻辑。
- 审计链路。 每一个发布的版本都签名留痕——你能在瞬间回答「14:00 用户看到的是什么」。
你保留了 Web 的更新速度,却没有交出整个行业早已标准化的发布工程能力。
一句话结论
WebView 卖给你的,是「改服务器就行」。它实际卖掉的,是一套 2011 年的发布模型:全量发布、无灰度、无熔断、还有一个你掌控不了的版本矩阵。Knight Capital 那 4.4 亿美元,只是同构失败的一个极端样本——一次改动瞬间铺到所有人,而没有任何东西能快速拦住它。
一个带开放、可治理目录的通用 runtime,让你既保住速度,又把控制权拿回来:分阶段发布、即时回滚、版本钉死、签名审计链路。你的用户,不再充当那只金丝雀。