CRDT / 向量时钟的适用性判断
一句话速记
向量时钟(Vector Clock):每个节点维护所有节点的逻辑时钟向量,用于判断事件先后顺序和检测并发冲突——“我写的时候对方的版本是多少”;CRDT(Conflict-free Replicated Data Type):特殊设计的数据结构,所有合并操作(merge)满足交换律、结合律、幂等律,无论什么顺序合并,最终结果都相同——彻底避免冲突。工程中 CRDT 适合协同编辑、分布式计数器、在线状态等场景;向量时钟适合检测冲突,冲突解决还需业务逻辑。
关键细节
1)向量时钟
每个节点 i 维护一个向量 V[i] = [v1, v2, v3, ..., vn]
vj 表示节点 i 见到的节点 j 的最新时钟值
节点 A 发送消息:附上自己的向量时钟 V[A]
接收方 B:V[B] = component-wise max(V[B], V[A]),然后 V[B][B]++
并发判断:
A 发生在 B 之前:V[A] ≤ V[B](各分量都 ≤)
A 发生在 B 之后:V[A] ≥ V[B](各分量都 ≥)
A 和 B 并发:V[A] 和 V[B] 既不 ≤ 也不 ≥(某些分量大、某些分量小)
示例:
节点 A 发送 msg1,V[A]=[1,0,0]
节点 B 收到并处理,V[B]=[1,1,0]
节点 C 也收到 msg1(同时收到),V[C]=[1,0,1]
→ V[B] 和 V[C] 并发(B[1]=1>0 但 B[2]=0<1)→ 检测到冲突,需要解决
适用场景:
✅ 分布式 KV 存储(DynamoDB 用向量时钟检测写冲突)
✅ 版本控制系统(Git 的 merge 底层思想类似)
✅ 需要检测因果关系的分布式系统
❌ 不适用:
节点数量很多(向量大小 = 节点数,开销大)
只需要全序(向量时钟是偏序)
需要自动解决冲突(向量时钟只检测,不解决)
2)CRDT(Conflict-free Replicated Data Type)
核心思想:
设计数据结构,使得 merge 操作满足:
交换律:merge(A, B) = merge(B, A)(顺序无关)
结合律:merge(A, merge(B, C)) = merge(merge(A, B), C)(分组无关)
幂等律:merge(A, A) = A(重复合并无害)
→ 多副本最终收敛到同一状态(无论同步顺序如何)
常见 CRDT 类型:
G-Counter(只增计数器):
每个节点维护自己的计数器 counter[node_id]++
合并:counter = max(counter[i], other[i]) for each i
读取:sum(counter)
→ 分布式 DAU、点赞数、播放次数
PN-Counter(可增减计数器):
P(增)+ N(减)两个 G-Counter
合并各自 max,值 = sum(P) - sum(N)
→ 库存(可以减少但不能为负数需额外校验)
LWW-Register(Last Write Wins):
每个写操作带时间戳,合并时取最新时间戳的值
→ 最简单,但可能丢失并发写
→ 适合:最后一次更新胜出的场景(用户状态)
OR-Set(Observed-Remove Set,可观察删除集合):
每个元素添加时带唯一标签
删除:标记该标签为删除
合并:存在 add 标签且未被删除 → 元素存在
→ 协同编辑的标签/成员列表
RGA(Replicated Growable Array):
支持插入和删除的序列(文本编辑)
每个插入操作有唯一 ID,用于保证最终顺序
→ Google Docs、Notion 等协同编辑的底层
3)适用场景对比
技术 适用场景 代表产品
────────────────────────────────────────────────────────────────────
向量时钟 检测并发冲突(再由业务/用户解决) DynamoDB, Riak, Git
CRDT(G/PN) 分布式计数(DAU、点赞、库存) Redis(Streams/HLL),Riak
CRDT(LWW) 最后写赢的简单状态(在线状态、配置) Redis(一般 SET 操作)
CRDT(OR-Set)协作共享标签、成员列表 Figma, Notion
CRDT(RGA) 实时协同文本编辑 Google Docs, VS Code Live Share
操作变换(OT)协同编辑(CRDT 的前辈方案,更复杂) Google Wave(已废弃)
4)工程中的选型建议
绝大多数互联网业务场景:
用"乐观锁 + 版本号"足够(简单、够用)
需要多节点并发写入、容忍网络分区时:
计数类:CRDT G/PN Counter
状态类:LWW Register(带时间戳的最后写赢)
需要高精度冲突检测(不想丢数据):
向量时钟(检测冲突)+ 展示给用户选择(类似 Git conflict)
实时协作编辑产品:
使用 CRDT(Yjs, Automerge 等成熟库)
不要自己实现 RGA(极其复杂)
延伸追问
- Q:CRDT 的 LWW 策略有什么问题? → 依赖时钟同步(NTP)。如果节点时钟不同步,可能导致”更旧的写操作”时间戳更大,覆盖”更新的写操作”。解决方案:用逻辑时钟(HLC,混合逻辑时钟)替代物理时钟,或接受 LWW 的语义(业务上最后一次 click 胜出是合理的)。
- Q:Redis 是 CRDT 吗? → Redis 本身不是 CRDT(单节点主从复制,不是无中心多主)。但 Redis Enterprise 有 Active-Active(CRDT-based)模式,多地域多主写,用 CRDT 保证冲突自动合并。开源 Redis 的 INCRBY 操作在并发下通过单线程保证原子性,不需要 CRDT。
- Q:向量时钟和逻辑时钟(Lamport Clock)的区别? → Lamport Clock(标量逻辑时钟):只能判断”A 一定发生在 B 之前”,无法判断并发(不能判断”A 和 B 是并发的”)。向量时钟(偏序):可以精确判断”A 先于 B”、“B 先于 A”、“A 和 B 并发”。向量时钟是 Lamport Clock 的多维扩展。
我的记法
- 向量时钟 = 每节点维护向量,判断先后/并发,不解决冲突
- CRDT = 特殊设计数据结构,合并满足交换+结合+幂等,自动无冲突
- G-Counter = 分布式计数;LWW = 最后写赢;OR-Set = 协作列表;RGA = 协作文本
- 日常业务:乐观锁够用;协作产品 / 多主写:考虑 CRDT
- 一句话:「向量时钟检测冲突,CRDT 从设计上消灭冲突」
状态
- 已背速记
- 能解释 G-Counter 的合并规则
- 能判断什么场景用 CRDT vs 向量时钟