从架构上,我会把它分为三个部分。使用编辑器的视图层,实时协作的同步层,以及后端的数据同步部分。

这其中最麻烦的是数据同步,我选择的使用 yjs 来解决 CRDT 处理数据同步和冲突处理。

Yjs根本不使用“索引”。

1. 相对位置 (Relative Positioning) 在Yjs的视角里,文档不是一个字符串,而是一个双向链表

  • 当我插入"B"时,我的操作不是“在索引1插入”,而是:“把’B’插入到’A’和’C’之间”。
  • 当您插入"D"时,您的操作也不是“在索引1插入”,而是:“把’D’插入到’A’和’C’之间”。

2. 确定性排序 (Deterministic Tie-breaking)

  • 现在,我们俩的客户端都收到了两个操作:【在’A’和’C’之间插’B’】和【在’A’和’C’之间插’D’】。
  • Yjs必须决定一个最终的、所有人都一致的顺序
  • 它的规则很简单:Yjs会给每个协作者分配一个唯一的ClientID(比如一个超大的随机数)。
  • 规则: 当两个操作要插入到同一个相对位置时,ClientID更大(或更小,取决于算法)的那个,排在后面。
  • 假设我的ClientID是 500,您的ClientID是 800。
  • 我的客户端和您的客户端都会执行这个逻辑:“‘B’和’D’都想插在’A’和’C’之间。OK,‘D’的ClientID (800) 大于 ‘B’的ClientID (500),所以 ‘D’ 排在 ‘B’ 后面。”
  • 最终结果:我们两个人的屏幕上,文档都会自动收敛"ABDC"

总结: 通过相对位置定位,Yjs避免了索引失效的问题;通过ClientID作为“ tie-breaker(决胜局)”,Yjs在数学上保证了所有人最终会得到完全一致的结果。