本地优先应用:离线可用与多端同步的架构#
主流 App 的模式是"云端中心":数据在云端,本地只是视图——没网就瘫。本地优先(Local-First)相反:数据在本地,云端(如果有)只是同步通道。笔记、收藏、文档这类"个人资产"应用,本地优先是更优解。
一、两种模式的对比#
| 维度 |
云端中心 |
本地优先 |
| 数据位置 |
云端为主 |
本地为主 |
| 离线 |
不可用 |
完全可用 |
| 速度 |
受网络影响 |
毫秒级 |
| 隐私 |
数据在别人那 |
数据在自己手里 |
| 同步 |
天然一致(单源) |
需要冲突解决 |
| 备份 |
平台负责 |
自己负责 |
适用判断:
二、核心架构#
三、核心问题 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
);
|
关键:
四、核心问题 2:同步#
增量同步流程#
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 |
计数器/文本 |
数学保证最终一致 |
| 保留冲突 |
重要数据 |
存两份 + 用户选择 |
实践:
五、核心问题 3:多端一致性#
关键:同步后两边数据必须一致(收敛性)——不然用户发现"这设备和那设备不一样"就失去信任。
六、Fluxio 的实践#
七、踩坑记录#
- 自增 ID 撞车:两设备同时插入 → 用 UUID(gen_random_uuid)
- 客户端时间戳不可信:改时区/调时间 → 冲突解决用服务端时间戳
- 删除不同步:本地删了,另一设备还留着 → 软删除 + tombstone(墓碑)
- 同步循环:A 推到 B,B 又推回 A → 已同步标记 + 幂等应用(op 带 id,重复应用跳过)
- 大附件同步:图片全量传 → 增量/分片 + 去重(内容哈希)
本地优先架构的核心认知:
- 本地是真身,同步是通道:没网也能用是卖点
- 三件套:UUID 主键 + 操作日志 + 软删除(同步的基建)
- 冲突解决要有策略:LWW/合并/CRDT/保留,按数据性质选
- 收敛性是底线:同步后各端一致,信任才成立
本地优先不是"技术选型",是"数据主权"的立场:你的数据在你手里,云端只是可选的备份与同步。做个人工具,这是最尊重用户的设计。
微信公众号「福清而不淡」
后端架构 · AI 工程 · 云原生的一线实践,扫码关注,不错过更新。
本文已同步发布到公众号,欢迎留言交流。