在做多端协同的购物清单应用时,遇到了一起离奇的"数据丢失"客诉。用户用手机添加了"买牛奶",同时用平板添加了"买面包"。等两台设备联网同步后,手机上的清单里只剩下了面包,牛奶凭空蒸发了。这就是典型的分布式数据同步冲突,系统底层的默认策略把你坑了。
设备发现与隐形权限的门槛
分布式开发的第一道门槛往往不是代码,而是环境。两台设备死活发现不了对方,连底层的软总线都没通。
注意:两台设备必须登录同一个华为账号,且连在同一个 Wi-Fi 下。更关键的是,必须在 module.json5 中显式声明权限,否则系统直接静默拦截你的同步请求。
json
{
"requestPermissions": [
{ "name": "ohos.permission.DISTRIBUTED_DATASYNC" }
]
}
这也是个用户级权限(user_grant),必须在运行期弹窗向用户申请。
时间戳机制带来的覆盖灾难
环境通了,我们用 distributedKVStore 存数据。系统默认的同步策略是 Last-Write-Wins(最后写入者胜出),判断标准是设备的系统时间。
这就导致了惨剧:如果手机时间比平板快了 5 分钟,那么手机上产生的任何数据,在同步时都会无条件覆盖平板上修改的数据。如果两人同时在两端操作,弱网环境下数据就会互相抹除。
要解决这个问题,绝不能依赖底层的时间戳,必须在业务层引入版本号(乐观锁)。
typescript
interface TaskItem {
id: string;
content: string;
version: number; // 手动维护的版本号
}
版本号机制与手动合并
当监听器收到远端数据推送时,先不着急覆盖 UI,而是对比版本号。
typescript
async function handleRemoteSync(remoteData: TaskItem) {
const localData = await getLocalData(remoteData.id);
if (remoteData.version > localData.version) {
// 接受远端更新
saveToLocal(remoteData);
} else if (remoteData.version === localData.version) {
// 警示:版本相同但内容不同,发生并发冲突!
// 业务合并策略:比如把两段文本拼接起来
const merged = {
...remoteData,
content: localData.content + '\n' + remoteData.content,
version: localData.version + 1
};
saveToLocal(merged);
// 将合并结果推回远端
kvStore.put(remoteData.id, merged);
}
}
通过这套逻辑,两端的数据都能完整保留。
单条数据容量与拆分存储
很多新手为了省事,把几百条清单序列化成一个巨大的 JSON 字符串,塞进一个 Key 里。这是极其致命的。KVStore 单条数据建议不要超过 1MB,否则同步极慢,甚至导致底层崩溃。
必须拆分成细粒度的 Key:item_1, item_2。这样不仅同步快,发生冲突时也能精准到某一条记录去解决,而不是整体覆盖。另外,页面退出前,务必调用 kvStore.off('dataChange') 注销监听,避免回调导致后台耗电。总的来说,分布式同步不能全靠系统,业务层的版本控制和冲突合并才是数据一致性的定海神针。