本地优先应用:离线可用与多端同步的架构

主流 App 的模式是"云端中心":数据在云端,本地只是视图——没网就瘫。本地优先(Local-First)相反:数据在本地,云端(如果有)只是同步通道。笔记、收藏、文档这类"个人资产"应用,本地优先是更优解。

一、两种模式的对比

维度 云端中心 本地优先
数据位置 云端为主 本地为主
离线 不可用 完全可用
速度 受网络影响 毫秒级
隐私 数据在别人那 数据在自己手里
同步 天然一致(单源) 需要冲突解决
备份 平台负责 自己负责

适用判断:

✅ ❌ 适 不 合 适 本 合 地 : 优 社 先 交 : ( 笔 需 记 要 、 实 收 时 藏 共 、 享 个 ) 人 、 文 电 档 商 、 ( 照 需 片 要 、 服 待 务 办 端 校 验 ) 、 协 作 文 档 ( 多 人 同 时 编 辑 )

二、核心架构

┌ │ │ │ │ ├ │ │ │ │ └ ─ ─ ─ 本 同 云 地 ├ ├ └ 步 ├ ├ └ 端 层 ─ ─ ─ 层 ─ ─ ─ ( └ ( ( 可 ─ 核 本 本 操 可 变 变 冲 选 心 地 地 作 选 更 更 突 , 同 ) 数 文 日 ) 上 下 解 仅 步 据 件 志 传 载 决 同 服 库 ( ( ( ( ( 步 务 ( 图 所 增 合 规 用 器 S 片 有 量 并 则 ) ( Q / 变 ) ) / 可 L 附 更 C 自 i 件 的 R 建 t ) 有 D / e 序 T 第 ) 记 ) 三 — 录 方 — ) ) 数 据 真 身

三、核心问题 1:本地存储

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
-- 数据表 + 变更日志表
CREATE TABLE items (
  id TEXT PRIMARY KEY,        -- 全局唯一(UUID,不是自增)
  content TEXT,
  updated_at INTEGER,
  deleted INTEGER DEFAULT 0   -- 软删除(同步需要)
);

CREATE TABLE ops (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  item_id TEXT,
  op_type TEXT,               -- create/update/delete
  payload TEXT,
  ts INTEGER,
  synced INTEGER DEFAULT 0
);

关键:

✅ ✅ ✅ 主 软 操 键 删 作 用 除 日 ( 志 U 删 ( U 除 同 I 也 步 D 要 的 ( 同 最 多 步 小 端 ) 单 生 位 成 , 不 也 冲 是 突 撤 ) 销 / 审 计 的 基 础 )

四、核心问题 2:同步

增量同步流程

设 设 备 备 A │ ├ │ B : ─ : 本 拉 地 推 取 操 送 新 作 : 把 o → 未 p 同 s 记 步 日 的 → 志 o 应 → p 用 s 到 标 本 记 发 地 未 给 同 服 → 步 务 器 解 ( 决 带 冲 突 l a s t _ s y n c _ i d )
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 同步循环
def sync():
    # 1. 推送本端未同步变更
    unsynced = db.get_ops(synced=False)
    server.push(unsynced)
    # 2. 拉取对端变更
    new_ops = server.pull(last_sync_id)
    for op in new_ops:
        apply_op(op)          # 应用到本地 + 冲突解决
    # 3. 更新游标
    last_sync_id = new_ops[-1].id

冲突解决策略

策略 场景 实现
最后写入胜(LWW) 普通字段 比时间戳,后写覆盖
合并(Merge) 集合/列表 并集合并
CRDT 计数器/文本 数学保证最终一致
保留冲突 重要数据 存两份 + 用户选择

实践:

✅ ✅ ✅ 普 标 笔 通 签 记 字 / 正 段 集 文 : 合 : L : 保 W M 留 W e 冲 ( r 突 带 g ( 服 e 两 务 版 端 本 时 都 间 留 戳 , , 用 别 户 用 合 客 并 户 ) 端 — 时 — 间 正 — 文 — 是 时 " 钟 个 可 人 能 资 不 产 准 " ) , 别 自 动 丢

五、核心问题 3:多端一致性

设 设 → 备 备 同 A B 步 时 离 在 : 线 线 2 , 7 3 改 了 条 天 变 , 5 更 改 , 了 条 2 ( 2 含 条 0 冲 A 突 条 改 → 过 的 按 规 2 则 解 条 决 ) → 两 边 收 敛

关键:同步后两边数据必须一致(收敛性)——不然用户发现"这设备和那设备不一样"就失去信任。

六、Fluxio 的实践

F 收 l 益 u 收 快 同 云 : 收 离 数 x 藏 照 步 端 藏 线 据 i 同 瞬 也 是 o → → O 步 间 能 文 ( b 完 看 件 信 本 本 s → 成 全 ( 息 地 地 i ( 部 备 流 文 d 规 不 收 份 收 S 件 i 划 依 藏 / 藏 Q a 中 赖 迁 ) L n ( 网 移 : i 可 络 自 t → 选 ) 由 e , ) ( M 加 立 a 密 即 r ) 可 k 用 d , o 毫 w 秒 n 级 ) 文 件 ( 本 地 v a u l t )

七、踩坑记录

  1. 自增 ID 撞车:两设备同时插入 → 用 UUID(gen_random_uuid)
  2. 客户端时间戳不可信:改时区/调时间 → 冲突解决用服务端时间戳
  3. 删除不同步:本地删了,另一设备还留着 → 软删除 + tombstone(墓碑)
  4. 同步循环:A 推到 B,B 又推回 A → 已同步标记 + 幂等应用(op 带 id,重复应用跳过)
  5. 大附件同步:图片全量传 → 增量/分片 + 去重(内容哈希)

总结

本地优先架构的核心认知:

  1. 本地是真身,同步是通道:没网也能用是卖点
  2. 三件套:UUID 主键 + 操作日志 + 软删除(同步的基建)
  3. 冲突解决要有策略:LWW/合并/CRDT/保留,按数据性质选
  4. 收敛性是底线:同步后各端一致,信任才成立

本地优先不是"技术选型",是"数据主权"的立场:你的数据在你手里,云端只是可选的备份与同步。做个人工具,这是最尊重用户的设计。