从架构上,我会把它分为三个部分。使用编辑器的视图层,实时协作的同步层,以及后端的数据同步部分。
这其中最麻烦的是数据同步,我选择的使用 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在数学上保证了所有人最终会得到完全一致的结果。