同步
两个存在并发编辑的文档,只需交换两次消息即可完成同步。
下面是两个文档同步的示例:
const = new ();
const = new ();
const : = .("list");
.(0, "A");
.(1, "B");
.(2, "C");
// B 导入来自 A 的操作
const : = .({ : "update" });
// 将数据通过网络发送给 B
.();
(.()).({
: ["A", "B", "C"],
});
const : = .("list");
.(1, 1);
// `doc.export({ mode: "update", from: version })` 会打包自某个版本以来的全部操作
// `version` 即另一文档的版本向量
const = .({
: "update",
: .(),
});
.();
(.()).({
: ["A", "C"],
});
(.()).(.());实时协作
得益于 CRDT 的特性,只要各节点最终接收到相同的更新,顺序、重复或延迟都不会影响一致性。
同步策略
-
首次同步(节点初次连线):
- 新节点可以与其他节点交换版本向量,判断缺失的更新
- 使用
doc.export({ mode: "update", from: versionVector })获取对方已知版本之后的增量,也可以像示例那样直接发送整个历史doc.export({ mode: "update" }) - 上述示例展示了基础的首次同步流程
-
实时同步(持续更新):
- 订阅本地更新
- 将更新直接广播给其他节点
- 首次同步完成后无需再次比较版本
- 只要所有更新都能送达,就能保持一致性
示例
以下示例展示了当一位带有离线修改的用户重新上线时,两节点如何建立实时同步:
- 双方交换版本信息
- 互相补齐缺失的更新:
doc2获取doc1中自己缺少的更新doc1获取doc2中自己缺少的更新
- 建立实时同步,保持连接
const = new ();
.("text").(0, "Hello");
// Peer2 加入网络
const = new ();
// ... doc2 可能先导入本地快照
// 1. 交换版本信息
const = .();
const = .();
// 2. 向现有节点请求缺失更新
const = .({
: "update",
: ,
});
.();
const = .({
: "update",
: ,
});
.();
// 3. 建立实时同步
.(() => {
// websocket.send(update);
});
.(() => {
// websocket.send(update);
});
// 现在两个节点已同步,可继续协作理解 import() 的返回值
在 Loro 的 JavaScript/WASM 绑定中,import() 会返回一个对象,告知导入结果。该对象(下文称 ImportStatusJS)结构如下:
interface ImportStatusJS {
: PeerVersionRange;
?: PeerVersionRange; // 可选字段:存在待处理操作时才返回
}
interface PeerVersionRange {
[: string]: {
: number; // 起始计数(包含)
: number; // 结束计数(不包含)
};
}字段说明
-
success(必填,PeerVersionRange)- 定义:描述哪些操作(变更)已成功导入并应用。
- 结构:键为
PeerID,值为{ start, end },表示该 peer 从start到end - 1的操作已处理。 - 用途:用于确认本次导入包含了哪些更新。
- 示例:
console.log(importResult.success); // 可能输出: // { // "clientA_peerId": { start: 0, end: 50 }, // "server_peerId": { start: 120, end: 150 } // } // 表示成功导入了 clientA 的 0-49 号操作,server 的 120-149 号操作。
-
pending(可选,PeerVersionRange)- 定义:当导入的数据依赖尚未到达的操作时,会记录这些“暂挂”操作的范围。
- 结构:与
success相同,表示因缺失依赖而暂未应用的操作区间。 - 用途:提示应用需要进一步获取缺失的先决操作,以保持一致性。
- 示例:
if (importResult.pending) { console.log(importResult.pending); // 可能输出: // { // "clientA_peerId": { start: 50, end: 60 }, // "clientB_peerId": { start: 10, end: 25 } // } // 表示 clientA 的 50-59、clientB 的 10-24 号操作因缺少依赖被暂挂。 }
如何利用这些信息
- 查看
success了解已成功应用的更新。 - 若
pending存在且非空,说明仍有操作等待依赖;应用应继续请求或同步缺少的更新,以完成整合。