本节摘要:移动端现场的三条约束改写了 protobuf 的使用姿势:弱网与离线要求同步协议围绕"未知字段保留"设计、App 版本碎片化把枚举降级义务放大成常态、序列化库的包体与能耗进入选型视野。本节设计一套离线同步协议并逐项处理三条约束,给出移动端的选型清单。读完你应当能设计出在 3% 存活版本上不崩的同步协议。
第二现场。微服务现场的主角是"治理",移动端现场的主角是"韧性"——第 5 章的兼容机制在这里从工程纪律升级成生存条件。
移动端不能假设网络可用,数据同步协议必须围绕"长时间滞后后追平"设计。协议骨架:
message SyncRequest { string device_id = 1; int64 since_version = 2; // 上次同步到游标(版本号或时间戳) bytes page_token = 3; // 断点续传游标(分页续拉) } message SyncResponse { int64 latest_version = 1; repeated ChangeOp changes = 2; // 变更操作流 string next_page_token = 3; // 空表示拉完 }
三个 protobuf 特定的设计点。其一,changes 的增量语义:整包快照(每轮发全量)实现简单但流量线性爆炸;变更流(只发差异)要靠 since_version 游标——而游标字段用 int64 存版本号时,服务端的版本单调性必须与第 2 章的类型选型对齐(值域只增不减,int64 一步到位)。其二,分页续传:弱网下大响应中途断掉是常态,page_token 的 bytes 类型装不透明的续传凭据,服务端不用理解只回传——这是 6.3 节"信封哲学"的微型应用。其三,设备间合并的未知字段义务:离线期间多设备各自产生变更,合并发生在客户端时——合并代码必须走运行时 Clone/Merge(第 5.3 节保真纪律),否则用户设备上的新字段(App 新版本写入的)被旧代码的合并路径剥掉,数据静默丢失且无法追责。
微服务群的服务升级以天计,移动端 App 的版本存活以月计——发布两年后仍有 3% 的用户跑在古早版本上。第 5.2 节的枚举降级义务在移动端是常态而非例外:
服务端义务:所有响应里的枚举值默认"最老在线版本不认识"——给每条 enum 值标注"引入版本",发布检查器(第 6.1 节 option 的又一个消费场景)比对"最老存活 App 版本认识哪些值",越界即拦截发布。这是第 4.3 节"契约元数据摊到 CI"模式的移动端版。
客户端义务:switch 枚举的 default 分支是产品逻辑而不是错误处理——遇到不认识的枚举值(服务端新版本发的)按"通用展示态"渲染(未知态显示占位图标而非崩溃或丢弃数据)。第 1.2 节 0 值盲区在这里也有移动端变体:UNSPECIFIED 枚举值渲染为中性态,别让它悄悄等于第一个业务态。
未知字段的存储链路:App 缓存同步数据到本地(后续章节:SQLite 或文件),缓存层若做了 proto→自有模型的转换,未知字段到这里为止——缓存层必须原样存 protobuf 字节(或经 Clone 保真),自有模型转换只发生在渲染层。多版本 App 共用同一缓存(升级覆盖安装)时,这是新旧数据无声混居的唯一安全通道。
服务端选库看吞吐,移动端先看三样:包体增量(运行时加生成代码给 APK/IPA 增加的体积——跨语言实现差异可达数倍,反射重的实现(如某些 JVM 系方案)在移动端是禁区)、启动开销(生成代码的注册与初始化发生在 App 冷启动路径上还是懒加载)、每消息能耗(移动 CPU 上单条编解码的耗时直接乘电池——第 7.1 节的基准方法原样适用,只是口径从吞吐换成单位消息能耗)。
选型判据表:
| 维度 | 服务端优先级 | 移动端优先级 |
|---|---|---|
| 编解码吞吐 | 第一 | 第三(够用即可) |
| 包体增量 | 无所谓 | 第一 |
| 冷启动开销 | 无所谓 | 第二 |
| 反射支持(第6章功能) | 重要 | 尽量避免(体积与能耗双税) |
| 平台覆盖 | 按服务语言 | iOS/Android 双端(含跨端框架) |
移动端要第 6 章的反射与动态消息功能时优先自问:是不是服务端做掉更合适——把动态性留在服务端、把静态生成的精简代码发到端上,是包体与灵活性的常见折中。
背景:笔记应用,离线可编辑、多设备同步,后端 Go、客户端 Android(Java/Kotlin 混合)与 iOS(Swift),两年存续期。操作:同步协议按本节骨架实现,变更流带 since_version 游标与 bytes 续传凭据;合并策略——服务端权威合并(客户端只上报本地操作,复杂合并不上端)绕开多端合并的保真雷区,缓存层原样存 protobuf 字节,渲染层才转视图模型;枚举纪律——操作类型枚举的每个值标注引入版本,发布门禁比对最老存活 App;版本演练——每次发版前用"两年前的老 App + 最新后端"过一遍冒烟集(同步、未知枚举、未知字段全链路)。结果:两年里协议演进 14 次(含两次字段类型级迁移走双写),线上零起"老版本数据丢失"事故;一起"新字段消失"的客诉定位到某低端机缓存层私自重建消息(违反保真纪律),修复后该机型下发版清缓存。解读:移动端韧性的全部来源可以压缩成一句话——让老版本永远能安全地"不理解"新东西:枚举有中性态、字段有未知容器、缓存保原始字节;三条全部是第 3、5 章机制在端上的投影。变式:若应用是强实时协作(多人同时编辑、冲突高频),服务端权威合并的延迟不可接受,端上合并不可避免——此时合并层必须用官方运行时的 DeepClone/Merge 并配 5.3 节的往返保真 CI 用例,把"端上合并"的保真纪律机器化。
移动端收工。下一个现场把频率推到极限:游戏同步的百毫秒广播与状态存档的长周期二进制治理。