查看: 132|回复: 0

鸿蒙分布式KVStore离线并发冲突消解与UserId隔离实战

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
问题背景:离线双端修改为何会冲突

轻规划(AeroPlan)在前序实践中使用 HarmonyOS 分布式键值数据库 Distributed KVStore,借助分布式软总线完成手机与平板在近场或同局域网内的无感同步,省去自建中转服务器。进入多端协同后,典型冲突场景是:用户在手机离线状态把“年度读书习惯”达标天数改为15天,家中平板因路由器临时断网离线改为20天;两端重新联网并重组局域网后,KVStore 启动同步线程合并数据。原文指出,如果只按物理时间戳决定谁覆盖谁,设备时钟漂移或手动改时会造成判断偏差,甚至覆盖本应保留的数据。因此轻规划在端侧采用 UserId 物理隔离与逻辑时钟(Vector Clock)做冲突消解,并以物理时间戳作为 LWW 兜底。

同步管线与UserId物理隔离

原文的同步与冲突检测管线是:通过 dataChange 广播监听接收对端变更数据树,读取变更树上的时钟矩阵,在端侧沙箱内运行消解逻辑,再由轻规划数据管理中心以原子事务提交写回持久化层。多账户隔离不能只靠业务字段过滤,而要把 UserId 拼进 StoreId。原文把 StoreId 设为 `aeroplan_store_${userId}`,让不同用户在本地拥有独立物理数据库路径,防止共用设备或切换账号时发生越权覆盖。

初始化 Distributed KVStore 时,原文的 Options 取舍如下:createIfMissing 为 true,数据库不存在时自动创建;encrypt 为 true,启用物理文件加密;backup 为 true,提供持久化备份,降低意外断电导致的数据损坏风险;autoSync 为 false,禁用底层默认自动同步,避免时钟未对齐造成混乱覆盖,把同步控制权交给应用层策略;kvStoreType 选 SINGLE_VERSION,利用单版本键值局部更新的原子写入性能;securityLevel 设为 S2,使跨设备分发受系统级密级限制。初始化失败时要记录包含 JSON.stringify(error) 的详细日志,避免静默失败导致同步链路异常。

数据模型:给每条分布式负载加版本头

只在 KVStore 中传业务 JSON 不够,必须为分布式负载增加统一元数据。原文的 DistributedPayload 包含 id、dataJson、vectorClock 和 lastUpdatedTimestamp。vectorClock 用设备唯一硬件标识作为键,记录该设备对这条数据的递增修改次数;dataJson 把核心业务属性序列化,实现元数据与业务逻辑解耦;lastUpdatedTimestamp 是更新发起时的本地物理毫秒时间戳,只在向量时钟判定为并发冲突时用于 LWW 裁决。
  1. export interface VectorClock {
  2.   clocks: Record<string, number>;
  3. }
  4. export interface DistributedPayload {
  5.   id: string;
  6.   dataJson: string;
  7.   vectorClock: VectorClock;
  8.   lastUpdatedTimestamp: number;
  9. }
  10. export class DataConflictResolver {
  11.   public static compareClocks(clockA: VectorClock, clockB: VectorClock): number {
  12.     let aHasLarger = false;
  13.     let bHasLarger = false;
  14.     const allKeys = new Set([...Object.keys(clockA.clocks), ...Object.keys(clockB.clocks)]);
  15.     for (const key of allKeys) {
  16.       const valA = clockA.clocks[key] || 0;
  17.       const valB = clockB.clocks[key] || 0;
  18.       if (valA > valB) {
  19.         aHasLarger = true;
  20.       } else if (valA < valB) {
  21.         bHasLarger = true;
  22.       }
  23.     }
  24.     if (aHasLarger && !bHasLarger) {
  25.       return 1;
  26.     }
  27.     if (!aHasLarger && bHasLarger) {
  28.       return -1;
  29.     }
  30.     return 0;
  31.   }
  32. }
复制代码

冲突判定:先看逻辑偏序,再用LWW兜底

原文的判定规则是:对两个版本 A、B,若所有设备 ID 上 A 的计数都大于等于 B,且至少有一个设备 ID 上 A 严格大于 B,则 A 比 B 新;反过来则 B 新。若一部分设备上 A 领先、另一部分设备上 B 领先,说明两端在离线状态下各自修改,产生并发冲突。此时进入 Last-Write-Wins,比较 lastUpdatedTimestamp,取时间戳更大的 dataJson;同时合并双方向量时钟,每个设备轴取 Math.max,避免合并后的新版本再次被误判为过期。原文把该流程称为三向合并消解,核心是偏序判断加并发分支上的 LWW 兜底,并写回唯一调和版本。

规避dataChange自循环广播

收到远端变更后,消解结果往往要调用 kvStore.put() 写回本地。问题在于 dataChange 同时可能捕获本地写入和对端同步;如果监听器不加区分,本端写回会再次触发本端 dataChange,被误认为新数据,继续向对端发送,形成数据广播死循环,导致设备发热、耗电,严重时内存溢出崩溃。

原文给出的避坑机制有两点。第一,精准订阅远端变更:只注册 SubscribeType.SUBSCRIBE_TYPE_REMOTE,不把本地业务写操作纳入监听范围;dataChange 订阅类型本身分为 SUBSCRIBE_TYPE_LOCAL、SUBSCRIBE_TYPE_REMOTE 和 SUBSCRIBE_TYPE_ALL。第二,静默同步写回:写回前用 compareClocks 比较消解结果与本地现有版本,只有结果实质领先时才刷新内存缓存、执行 put 并通知 UI;若一致,则只做内存缓存与 UI 静默刷新,跳过数据库写入,避免同版本无意义广播。原文还用 localCache 保存最新 Payload,减少频繁读盘。

调试、验证与架构权衡

按原文设计,调试时应重点观察三类信号:初始化阶段是否因 StoreId、Options 或上下文问题失败,并保留完整错误对象;远端载荷解析失败时记录 key 并跳过,避免坏数据阻断整批同步;消解后是否因同版本重复 put 触发二次广播。验证离线并发冲突时,可复现手机改15天、平板改20天再重连的路径,检查向量时钟偏序判断、LWW 时间戳选择和合并时钟上界是否符合预期。

架构上,这套方案把底层 autoSync 关闭,换取应用层可控的冲突消解;用 SINGLE_VERSION 保留键值局部更新的原子写入性能,同时接受在应用层维护向量时钟和载荷包装的复杂度;用 UserId 物理隔离保证多账户数据边界,用 encrypt、backup 和 S2 安全等级补足静态存储与跨设备分发风险。它解决的是原文所说的离线多端并发编辑一致性问题,而不是替代所有分布式数据库能力。

小结

在 HarmonyOS 原生 Distributed KVStore 之上,轻规划通过 UserId 拼 StoreId 实现物理隔离,通过 DistributedPayload 携带向量时钟与物理时间戳,通过 compareClocks 判断偏序、LWW 兜底和时钟最大值融合消解并发冲突,再以只订阅远端 dataChange 与静默写回避免广播风暴。这套组合把多端无感同步从“能同步”推进到“离线并发修改后仍能可控合并”,是原文给出的核心工程方法。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-22 09:56 , Processed in 0.021444 second(s), 29 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部