CRDT(无冲突复制数据类型)详解
CRDT 全称:
Conflict-free Replicated Data Type
它是一类专门用于分布式系统数据同步的数据结构。
核心目标:
让多个副本(Replica)在没有中心协调、甚至同时修改数据的情况下,最终能够自动合并,并保证所有副本最终一致。
它主要解决:
-
多地编辑数据同步
-
分布式缓存同步
-
离线应用数据合并
-
多人协作文档
-
分布式数据库复制
-
Edge Computing 数据同步
1. 为什么需要 CRDT?
先看一个现实问题。
假设有一个文档:
title = "hello"
有两个用户:
Replica A Replica B
hello hellor
| |
| |
修改为 helloA 修改为 helloB
结果:
A:
helloA
B:
helloB
现在两个副本同步。
问题:
最终应该是什么?
可能:
helloA
也可能:
helloB
也可能:
helloAB
传统做法:
方案1:加锁
例如:
用户A获取锁
修改
释放锁
用户B修改
问题:
-
延迟增加
-
网络分区时不可用
-
分布式锁复杂
方案2:中心服务器仲裁
例如:
Client A
|
|
Server
|
|
Client B
服务器决定:
谁覆盖谁
问题:
-
中心节点压力
-
离线无法工作
CRDT 的思想:
不需要决定谁赢,而是设计一种数据结构,让所有修改天然可以合并。
2. CRDT 的核心思想
普通数据:
value
例如:
count = 10
CRDT:
value + metadata
例如:
{
value:10,
version:{
A:5,
B:3
}
}
它保存:
-
数据
-
修改历史信息
-
因果关系
这样:
多个副本合并时:
A的数据
+
B的数据
↓
自动计算结果
3. CRDT 的两个核心性质
CRDT 能保证最终一致,依赖数学性质。
1. 交换律(Commutative)
顺序无关:
A + B
=
B + A
例如:
增加:
+1
+2
无论:
+1 后 +2
还是:
+2 后 +1
结果:
3
2. 结合律(Associative)
组合顺序无关:
(A+B)+C
=
A+(B+C)
3. 幂等律(Idempotent)
重复执行不会影响:
A+A=A
例如:
同步消息:
update(x)
发送两次:
update(x)
update(x)
结果一样。
这三个性质保证:
即使:
-
网络乱序
-
消息重复
-
延迟不同
最终仍然一致。
4. CRDT 分类
CRDT 主要分两类:
CRDT
|
-----------------
| |
State-based Operation-based
状态型 操作型
5. State-based CRDT(状态型)
也叫:
CvRDT(Convergent Replicated Data Type)
思想:
副本之间交换完整状态,然后合并。
例如:
Replica A
{
count:5
}
Replica B
{
count:8
}
同步:
merge(A,B)
得到:
count=?
关键:
merge 必须满足:
交换律
结合律
幂等律
6. 最简单例子:G-Counter
G-Counter:
Grow-only Counter,只增加计数器
场景:
点赞数量。
三个节点:
A
B
C
每个节点维护:
{
A:0,
B:0,
C:0
}
用户在 A 点赞:
A:
{
A:1,
B:0,
C:0
}
用户在 B 点赞:
B:
{
A:0,
B:1,
C:0
}
同步:
合并:
取每个节点最大值
{
A:max(1,0),
B:max(0,1),
C:0
}
=
{
A:1,
B:1,
C:0
}
结果:
count=2
为什么不会冲突?
因为:
max()
满足:
max(a,b)=max(b,a)
7. PN-Counter(可增减计数器)
G-Counter 只能增加。
现实:
点赞:
+1
取消点赞:
-1
怎么办?
PN-Counter:
拆成两个:
P Counter
增加
N Counter
减少
例如:
用户点赞:
P:
A=10
取消:
N:
A=2
最终:
value=P-N
=10-2
=8
8. Set 类型 CRDT
集合:
users
两个副本:
A:
{
Tom
}
B:
{
Jerry
}
同步:
union
结果:
{
Tom,
Jerry
}
但是删除怎么办?
例如:
A:
删除 Tom
B:
添加 Tom
冲突:
到底有没有 Tom?
所以出现:
OR-Set
Observed Remove Set
它记录:
元素
+
唯一标识
例如:
添加:
Tom#001
删除:
删除 Tom#001
如果:
B:
Tom#002
那么:
仍然存在。
9. 最经典应用:协同编辑
例如:
Google Docs。
两个用户:
User A:
hello
User B:
hello
同时:
A:
插入:
hello A
B:
插入:
hello B
最终:
hello AB
或者:
hello BA
关键:
所有人看到一致结果。
这类 CRDT:
例如:
-
Yjs
-
Automerge
采用:
Sequence CRDT
专门解决:
文本序列编辑。
10. Sequence CRDT 怎么实现文本?
普通字符串:
hello
如果插入:
hello
^
插入 X
问题:
字符位置会变化。
CRDT 不保存:
index=5
而保存:
字符ID
例如:
h
id=001
e
id=002
l
id=003
插入:
x
after id=003
于是:
h
e
l
x
l
o
即使:
两个用户同时插入:
after id=003
也可以根据:
-
时间戳
-
节点ID
排序。
11. CRDT 和 Paxos/Raft 的区别
很多人会混淆。
Raft
目标:
强一致
流程:
Client
↓
Leader
↓
Followers
特点:
-
有 Leader
-
需要多数派
-
写入需要协调
例如:
数据库。
CRDT
目标:
最终一致
流程:
Replica A
↔
Replica B
↔
Replica C
特点:
-
无中心
-
可离线
-
自动合并
例如:
协作文档。
对比:
| Raft | CRDT | |
|---|---|---|
| 一致性 | 强一致 | 最终一致 |
| 中心节点 | 需要 | 不需要 |
| 网络分区 | 牺牲可用性 | 保持可用 |
| 冲突处理 | 拒绝冲突 | 自动合并 |
| 适合 | 数据库 | 协作系统 |
12. CRDT 的优点
1. 无需锁
多个节点:
同时写
也可以。
2. 支持离线
例如:
手机:
飞机模式
编辑文档
回来:
同步
自动合并
3. 高可用
网络断开:
Replica A
继续工作
Replica B
继续工作
13. CRDT 的缺点
CRDT 不是万能。
1. 数据膨胀
因为需要保存:
版本
ID
操作历史
例如:
文本:
hello
实际:
字符
+
UUID
+
时间戳
+
关系信息
空间明显增加。
2. 复杂度高
简单:
value=10
变成:
value
+
metadata
+
merge algorithm
3. 不适合强事务
例如:
银行转账:
A账户 -100
B账户 +100
不能接受:
最终一致
需要:
Raft/Paxos/事务。
14. 实际应用案例
在线文档
-
Google Docs
-
Notion
-
Figma
数据同步
-
Redis CRDT
-
Riak DT
分布式数据库
- AntidoteDB
前端协作框架
-
Yjs
-
Automerge
15. 一句话总结
CRDT 是一种通过设计数据结构,使分布式副本之间的修改天然可合并的数据类型,它利用数学上的交换律、结合律、幂等律,让多个节点无需加锁和中心协调,也能最终达到一致。
简单理解:
传统分布式:
多个地方修改数据
|
↓
必须有人裁决冲突
CRDT:
多个地方修改数据
|
↓
数据结构自己知道如何合并
|
↓
最终一致
如果你前面关注的是缓存、一致性 Hash、分布式系统设计,那么 CRDT 可以进一步理解为:
一致性 Hash 解决「数据放哪里」;CRDT 解决「多个地方同时改同一份数据,如何自动合并」。
二者是分布式系统中两个不同维度的问题。