Administrator
发布于 2026-09-03 / 5,132 阅读
0
0

鸿蒙分布式数据管理:KV 存储、DDO 与 DataShare 的工程取舍

鸿蒙百篇系列第 62 篇 · 专家

第 9 篇我们讲过分布式能力的大图景。这一篇落到"分布式数据管理"这条主线:鸿蒙到底给了几套同步 API,它们之间的关系是什么,什么时候用哪个。很多团队上来就一句"我们要做跨设备同步",然后直接用了同步方案,等上线才发现选错。下面我把鸿蒙提供的几套同步机制整理出来,按"谁做什么"的维度拆开。

一、先理解三层数据模型

鸿蒙的分布式数据分三层,它的设计跟文件系统无关,跟传统 RDB 无关:

层级

数据对象

API

一致性

字段

分布式数据对象 DDO(Distributed Data Object)

@ohos.data.distributedDataObject

最终一致

键值

分布式 KV 存储

@ohos.data.distributedKVStore

最终一致 + 设备协作

数据库

分布式关系数据库

@ohos.data.relationalStore + @ohos.data.cloud

可同步、可端云协同

DDO 适合"对象级别的状态共享",KV 适合"配置、Token、计数器",关系数据库适合"业务表"。选错层的代价是后面迁移数据非常麻烦。

二、DDO:跨设备同步一个内存对象

DDO 是"为单条对象服务的同步通道"。它做的事情非常克制:

  • 把一个 ArkTS 类的实例同步到同一组网下的其他设备

  • 只同步有 @SyncField / @SyncIgnore 标记的字段

  • 增量同步、字段级合并

import distributedDataObject from '@ohos.data.distributedDataObject';

class TodoItem extends distributedDataObject.Object {
  @SyncField({ changeDetector: 'auto' }) public title: string = '';
  @SyncField({ changeDetector: 'auto' }) public completed: boolean = false;
  @SyncIgnore() public localOnly: string = '';
}

@Entry
@Component
struct TodoPage {
  private todo: TodoItem = new TodoItem();

  aboutToAppear(): void {
    this.todo.title = '提交鸿蒙适配';
    this.todo.completed = false;

    // 创建同步通道,sessionId 用于标识同一个会话
    this.todo.setSessionId('todo_global_session');

    // 监听其他设备对本对象的修改
    this.todo.on('change', (sessionId, fields) => {
      console.info(`字段变更: ${fields.join(',')}`);
    });
  }

  build() {
    Column() {
      Text(this.todo.title)
      Toggle({ isOn: this.todo.completed })
        .onChange((v) => { this.todo.completed = v; })
    }
  }
}

DDO 用起来简单,但它有三个限制,业务方容易踩:

  1. 不能存太复杂的数据结构。DDO 处理 plain object 很好,但塞进去一个包含循环引用的对象会失败

  2. sessionId 决定同步域。两台设备用同一个 sessionId 才同步,默认按同账号+同应用来归类

  3. 冲突策略单一。只支持"最后写入胜出"或"自定义字段合并",不支持 CRDT 那种版本向量

三、分布式 KV 存储:团队协作的"事件总线"

KV 存储比 DDO 更底层、更灵活。鸿蒙把它叫做 DistributedKVStore,核心特点:

  • 数据是 KV 对,按 storeId 分组

  • 设备之间增量同步 key/value(不是全量)

  • 可订阅"远端写入事件",知道是哪台设备改的

初始化代码(API 12 一致):

import distributedKVStore from '@ohos.data.distributedKVStore';

const kvManager = distributedKVStore.createKVManager({
  bundleName: 'com.example.todo',
  context: getContext(this)
});

const options: distributedKVStore.Options = {
  createIfMissing: true,
  encrypt: false,
  backup: false,
  autoSync: true,
  kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
  securityLevel: distributedKVStore.SecurityLevel.S2
};

const kv = await kvManager.getKVStore('todo_store', options);

// 写入
await kv.put('task:001', '提交报告');

// 订阅远端写入
kv.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
  for (const change of data.insertEntries) {
    console.info(`设备 ${change.deviceId} 写了 ${change.key} = ${change.value.getValue()}`);
  }
});

DEVICE_COLLABORATION 是最常用的类型,专为多设备协作场景设计。autoSync: true 会在写入时自动同步到已信任设备。

四、DataShare:跨应用数据共享

DataShare 跟前面两个完全不同的方向,它解决的是"多个应用之间共享同一份数据"。它的形态是 DataShareExtensionAbility,跟 UIAbility 一类但作用是提供数据访问接口:

// 数据提供方:定义共享的 URI
import { DataShareExtensionAbility, ColumnsAndValues } from '@ohos.data.dataShare';

export default class TodoDataShare extends DataShareExtensionAbility {
  async query(uri: string): Promise<Array<any>> {
    if (uri === 'datashare://com.example.todo/tasks') {
      // 从数据库读,支持跨进程访问
      return this.queryTasks();
    }
    return [];
  }

  async insert(uri: string, values: ColumnsAndValues): Promise<number> {
    if (uri === 'datashare://com.example.todo/tasks') {
      return this.insertTask(values);
    }
    return -1;
  }

  async update(uri: string, predicates, values: ColumnsAndValues): Promise<number> {
    return this.updateTask(predicates, values);
  }

  async delete(uri: string, predicates): Promise<number> {
    return this.deleteTask(predicates);
  }
}

访问方用 dataShare.createDataShareHelper(uri) 创建帮助类,然后在 UI 侧直接当个数据源用。这是多端、多应用协作的统一接入口。

五、五种同步方案对比

工程上经常被问"我到底用哪个",下面这张表整理了下决策路径:

方案

适合场景

数据大小

冲突处理

部署复杂度

@StorageLink + Preferences

单设备轻配置

< 200KB

不涉及

极简

分布式 Preferences

多设备共享小配置

< 200KB

后写胜出

简单

DDO

多设备共享一个业务对象

对象字段级

字段合并

简单

分布式 KV

多设备共享轻量状态

每条 < 4MB

自定义

中等

DataShare

多应用共享数据

表级别

SQL 谓词

复杂但可控

分布式 RDB + 云

多设备多应用 + 端云协同

表级别

完整事务

高

我自己经验里的几个判断:

  • 对象属性 + 实时可见:用 DDO

  • 配置 + 多设备生效 + 不挑剔冲突:用分布式 Preferences

  • 状态总线 + 跨应用订阅:用分布式 KV

  • 跨应用数据查询 + SQL 风格:用 DataShare

  • 业务表 + 端云 + 严格一致性:用分布式 RDB

六、实战:用户配置中心

我用一个小例子把 KV + DDO 用起来,看看它们怎么搭配。需求:

  • 一个用户的"是否开启暗黑模式"配置同步到他所有设备

  • 一个 List 的"是否展开"是按 List 共享的,不和别的 List 混

import Preferences from '@ohos.data.preferences';
import distributedPreferences from '@ohos.data.distributedPreferences';
import distributedKVStore from '@ohos.data.distributedKVStore';

class ConfigCenter {
  // 1. 跨设备共享的偏好
  private distPref: distributedPreferences.Preferences | null = null;

  // 2. 按 listId 共享的多设备 KV
  private kv: distributedKVStore.KVStore | null = null;

  async init() {
    // 分布式 Preferences:用户级、key-value,自动同步到同账号的所有设备
    this.distPref = await distributedPreferences.getPreferences(getContext(this), 'user_config');

    // 分布式 KV:按 listId 隔离
    const mgr = distributedKVStore.createKVManager({
      bundleName: 'com.example.app'
    });
    this.kv = await mgr.getKVStore('shared_state', {
      createIfMissing: true,
      autoSync: true,
      kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
      securityLevel: distributedKVStore.SecurityLevel.S2
    });
  }

  async setDarkMode(on: boolean) {
    await this.distPref?.put('dark_mode', on);
    await this.distPref?.flush();
  }

  async isDarkMode(): Promise<boolean> {
    return (await this.distPref?.get('dark_mode', false)) as boolean;
  }

  async setListExpanded(listId: string, expanded: boolean) {
    await this.kv?.put(`expand:${listId}`, expanded ? '1' : '0');
  }

  isListExpanded(listId: string): Promise<boolean> {
    return this.kv?.get(`expand:${listId}`).then(v => v === '1') || Promise.resolve(false);
  }
}

七、几个工程要点

  1. autoSync 别乱开。开了之后每次写入都触发同步,如果你的写入是高频请求(滚动日志、埋点),成本吃不消。要么手动 sync(syncMode, delayMs) 控制节奏

  2. 大对象不要走 DDO。DDO 整对象同步虽然增量,框架会做一次 hash 对比,对象很大时 hash 计算本身贵

  3. DataShare 必须显式声明权限。module.json5 里的 dataShare 字段没配好,跨进程调用会直接失败

  4. 冲突不要赌运气。即使用了"最后写入胜出",UI 上也要带个"合并"按钮,让用户主动解决,不要让他们看到内容来回变

  5. 测试一定要用真机。模拟器的分布式能力是大大缩水甚至不工作的,拿模拟器验证同步逻辑几乎没用

八、和前几篇的衔接

  • 第 9 篇讲了分布式大图景。本篇是把"分布式数据"那一支挖到底

  • 第 56 篇讲了 IPC 与 DataShareExtension。这一篇把它和 KV、DDO 拼起来看,就清楚各自的边界

  • 第 35 篇"动态化"里提的卡片数据下发,本质上是端云协同 + DataShare 配合

九、小结

分布式数据管理在鸿蒙里是一套分层方案:DDO 同步对象、KV 同步键值、DataShare 跨进程共享数据、RDB + 云跨端云协同。选错层不只是性能问题,还会影响后续能不能扩展。下次被问"我要做跨设备同步",别急着动手,先问一句"你到底是同步一个对象、一组 KV、还是一张表"。

下一篇转向"安全沙箱",讨论应用间为什么天然隔开、目录边界怎么定、权限链怎么校验。


评论