### 第3章 系统分层设计总图:客户端层、同步中间层、服务端层、离线存储层的职责与边界 --- #### 3.1 分层总览与设计目标 离线优先架构的核心难点在于**网络不确定性与数据一致性**之间的平衡。本章定义的四层模型将系统拆解为**数据采集与展示(客户端层)**、**冲突协调与路由(同步中间层)**、**权威数据治理(服务端层)** 以及**离线持久化(离线存储层)**。每层边界清晰,职责不重叠,通过契约接口通信,降低耦合度。
设计目标:即使网络完全断开,客户端层+离线存储层依然能支撑用户完整操作;网络恢复后,同步中间层自动执行冲突检测与合并,最终与服务端层达成最终一致。
--- #### 3.2 四层职责定义 | 层级 | 核心职责 | 边界约束 | 典型技术选型 | |------|---------|----------|-------------| | **客户端层** | UI渲染、用户事件处理、本地读写、离线优先交互 | 不直接操作远端数据库;不执行业务复杂校验 | Flutter/SwiftUI/Compose + 本地数据库 | | **同步中间层** | 增量变更捕获、双向同步管道、冲突检测与调解 | 不持有持久化状态;不直接生成业务数据 | CRDT/OT引擎、WebSocket长连接、幂等队列 | | **服务端层** | 权威数据存储、认证授权、全局业务规则、多端合并 | 不感知客户端离线状态;不处理UI逻辑 | PostgreSQL/Spanner + gRPC/REST | | **离线存储层** | 本地数据缓存、变更日志、离线队列、同步元数据 | 不驱动UI行为;不与网络层直接交互 | SQLite/WAL模式 + 日志结构化合并树 | --- #### 3.3 数据流与信任链路 数据在四层间遵循**单向流动 + 环状回写**模式: 1. **写路径(客户端 → 离线存储 → 同步中间层 → 服务端层)** - 用户操作 → 客户端写入离线存储(立即生效,乐观UI) - 离线存储记录本地变更日志(`pending_changes` 表) - 同步中间层轮询或监听变更,组装为增量包(`delta payload`) - 网络可用时推送到服务端层,服务端验证后写入权威库 2. **读路径(服务端层 → 同步中间层 → 离线存储 → 客户端层)** - 同步中间层订阅服务端变更事件 - 拉取增量更新,计算合并结果,写入客户端离线存储 - 客户端层通过本地数据库观察者模式刷新UI 3. **信任链路** - 客户端层对离线存储层完全信任(本地写无需认证) - 同步中间层与服务端层之间必须建立**双向认证TLS + 请求签名** - 服务端层拒绝未签名的客户数据,仅接收同步中间层的合并结果
风险点:若同步中间层未对变更 payload 做幂等校验,网络重试可能导致服务端层重复写入。解决方案:同步中间层必须携带全局唯一 `sync_id`,服务端层建立去重索引。
--- #### 3.4 系统分层总图(内联SVG) 离线优先四层架构总图 客户端层 UI渲染 / 事件处理 本地读写 / 离线优先 不直接访问服务端 离线存储层 本地数据库 / 变更日志 离线队列 / 同步元数据 SQLite / 文件存储 同步中间层 增量捕获 / 冲突检测 双向管道 / 幂等重试 WebSocket / 事件总线 服务端层 权威数据存储 认证授权 / 业务规则 全局合并 / 去重 本地读写 增量推送 TLS + 签名 变更通知 ⓘ 分层设计关键指标 • 客户端层 + 离线存储层 在断网时完全自治,不产生功能降级 • 同步中间层与服务端层之间必须实现至少一次交付(at-least-once delivery) --- #### 3.5 选型权衡与落地步骤 ##### 3.5.1 关键选型决策表 | 决策点 | 选项A | 选项B | 推荐场景 | |--------|-------|-------|---------| | 离线存储引擎 | SQLite + WAL | LevelDB/RocksDB | 关系型查询多选A,纯键值选B | | 同步协议 | 基于 gRPC 双向流 | REST + 长轮询 | 高并发低延迟选gRPC,运维简单选REST | | 冲突解决策略 | CRDT(无冲突数据类型) | 最后写入者胜出(LWW) | 协作编辑选CRDT,简单数据选LWW | | 服务端权威数据库 | PostgreSQL | CockroachDB | 全局强一致选PG,跨区域多活选Cockroach | ##### 3.5.2 落地步骤(6阶段) 1. **定义层间契约接口**:明确 `SyncPayload`、`ConflictRecord`、`AckResponse` 的 protobuf 定义。 2. **搭建离线存储基础**:设计 `changes` 表(`sync_id`, `entity_type`, `entity_id`, `operation`, `data`, `timestamp`, `status`)。 3. **实现客户端层乐观UI**:写入离线存储后立即更新UI,不等待网络确认。 4. **构建同步中间层双向管道**:实现 `websocket_handler` 和 `retry_queue`。 5. **部署服务端层整合点**:编写 `merge_handler` 接收增量包,执行冲突检测。 6. **集成测试与调优**:模拟断网/弱网/高并发场景,验证最终一致性。 ##### 3.5.3 验收标准 - **离线操作**:在完全断网环境下,用户可完成创建/编辑/删除全部核心实体,且本地UI无感知降级。 - **同步完整性**:重新联网后,离线期间所有操作均成功同步至服务端,无数据丢失。 - **冲突解决**:同时编辑同一字段时,系统按预设策略(CRDT/LWW)自动合并,不出现数据损坏。 - **性能边界**:单次同步延迟(从客户端写入到服务端确认)≤ 500ms(正常网络下)。
实践建议:优先将“离线存储层”的变更日志设计为追加写(append-only),避免随机写导致SQLite锁竞争。同步中间层使用
  • 批量推送减少连接数
  • 指数退避避免网络风暴
--- #### 3.6 风险点与边界法则 | 风险分类 | 具体场景 | 预防措施 | |----------|---------|---------| | **存储边界** | 离线数据库膨胀导致手机存储溢出 | 设置最大行数/容量,淘汰超期变更 | | **信任边界** | 客户端伪造 `sync_id` 重放攻击 | 同步中间层验证 `sync_id` + 签名 | | **时间边界** | 客户端时钟偏移导致变更顺序错乱 | 服务端层使用单调递增的 `server_timestamp` | | **网络边界** | 同步中间层与服务端断连后,本地增量堆积 | 离线队列设置最大深度,超出后降级为只读 | > **核心原则:每一层只信任自己下一层的输出。** 客户端层认为离线存储层的数据就是“当前状态”;同步中间层只信任服务端层返回的合并结果;服务端层拒绝任何非同步中间层来源的写入。