HarmonyOS 7 数据协同重构(第3讲):分布式对象与离线冲突合并策略
多设备应用开发中,数据同步是最容易引发数据事故的环节。一台设备离线修改了文档,另一台设备同时修改了同一份文档,网络恢复后,谁覆盖谁?鸿蒙的分布式数据同步底层采用最终一致性模型,KV 单版本默认采用 LWW(最后写入胜出)策略。这意味着,如果你的业务依赖底层自动合并,稍微复杂的并发场景下必定会丢失数据。
要确保“不丢数据”,不能把命运交给系统的默认策略。开发者必须在业务层建立一套版本基线,手动接管冲突的裁决权。
LWW 策略的业务局限性
LWW 的核心判定依据是时间戳。但在多设备环境下,各设备的系统时间未必完全同步,网络延迟也会导致到达顺序与操作顺序不一致。它只适合那些“最后状态即正确状态”的场景,比如主题色的同步、开关的切换。
对于带有增量属性的数据(比如往列表里加了一条记录),LWW 的粗暴覆盖会导致其中一条记录凭空消失。此时必须引入带版本号的实体结构。
typescript
interface SyncRecord {
id: string
payload: Record<string, string>
rev: number // 当前逻辑版本
baseRev: number // 修改基于的版本基线
opId: string // 操作幂等ID
tombstone: boolean // 墓碑标记
}
当同步端收到数据时,比对 baseRev 和本地当前的 rev。如果一致,说明本地没有发生并发修改,直接更新;如果不一致,说明两端从同一个版本出发,各自产生了修改,构成真正的冲突。
字段级合并与版本基线
检测到冲突后,直接让用户去选“保留哪个”体验极差。更好的做法是走字段级合并。遍历双方的 payload,只有同一个字段两边都有值且不相等时,才触发裁决。
typescript
function merge(local: SyncRecord, remote: SyncRecord): SyncRecord {
if (remote.tombstone) return remote // 删除优先
if (local.tombstone) return local
const merged = { ...local }
for (const key in remote.payload) {
const lVal = local.payload[key]
const rVal = remote.payload[key]
if (lVal === undefined) {
merged.payload[key] = rVal // 本地没值直接补
} else if (lVal !== rVal) {
// 双方都有修改,按版本高者胜出
merged.payload[key] = remote.rev > local.rev ? rVal : lVal
}
}
merged.rev = Math.max(local.rev, remote.rev) + 1
merged.baseRev = merged.rev
return merged
}
这样,两端各自修改不同字段的操作都能被完整保留,只有真正修改了同一字段才有一方妥协。
墓碑机制防止数据复活
硬删除在多端同步中是绝对禁忌。A 设备删除了记录,B 设备离线时还存着副本。B 上线同步后,这条记录会被当成新增数据推回云端,导致数据复活。
正确的做法是采用软删除,把 tombstone 置为 true,并递增版本号。系统只会把这个标记同步给其他端,其他端收到后隐藏该数据。等确认所有活跃设备都收到了墓碑,再由后台清理任务彻底物理删除。
幂等键的设计与验证
网络抖动导致的重发,会让同一笔操作被服务端接收多次。如果接口本身不具备幂等性(比如“创建订单”),就会产生重复的业务数据。
每次本地操作生成一个唯一的 opId(如 UUID),同步库收到请求时,先查 opId 是否处理过。处理过的直接返回成功。这样哪怕请求发了十次,真正的业务逻辑只执行一次。并发和同步永远没有银弹,明确版本水位、定义冲突规则,才是保障数据不丢失的唯一途径。
更多推荐

所有评论(0)