理解CRDT
本文又名:支撑英雄联盟750万同时在线用户的聊天系统底层的crdt是什么?
CRDT 是什么意思?
CRDT是Conflict-Free Replicated Data Types的缩写,直译的话即“无冲突可复制数据类型”。
CAP定理
分布式系统CAP定理
- Consistency(一致性,所有节点在同一时间的数据完全一致)
- Availability(可用性,读写服务一直可用)
- Partition tolerance(分区容错性,部分节点故障系统仍能提供一致可用的服务)
构建分布式系统时,这三者只可以同时选择两样。
2000年UCB提出假说,2年后由MIT证明。
怎么选?
将问题转化
- 分区容错性P 是实际运营的分布式系统所必需的,无法保证所有的节点都不出现故障或者下线
- 因此,目前分布式系统都在一致性C 和 可用性A 中取舍
保证 | 名称 | 说明 |
---|---|---|
一致性C | CP System | 数据落地即一致,但有时会不可用 |
可用性A | AP System | 最终一致性,每次访问数据可能不一致 |
- CP系统
- 为了保持一致性,系统可能出现暂时不可用的情况,比如节点之间正在同步数据,这时候进来的读请求就要排队等一会
- 分区故障发生时,为了保持一致性,系统只能暂停等待分区故障恢复。
- 适合对一致性要求高,可以在性能(可用性)上做出妥协,比如电商的秒杀功能(有时写,一直读)
- AP系统
- 为了保持可用性,在 强一致性, 弱一致性, 最终一致性的一致性等级中,仅保证最终一致性,所谓最终一致性,是指所有节点上的数据合并后得到的结果是一致的
- 分区故障发生时,为了保证系统可用,放弃一致性。
- 适合对一致性要求不高,但对性能(可用性)要求高的场景,比如日志系统(有时读,一直写),实时聊天,多人协同编辑
- CA系统
- 单机系统
理想的分布式
CP系统的启发
- Raft等强一致性算法,选举leader统一受理写请求
- 写请求过多时,不堪重负
- (集中写,分布读,分布的不够彻底)
理想中的分布式
- 分布式写请求,极大提高吞吐量
- 任何节点受理写请求后,通知给系统中所有其他节点更新信息
- (分布写,分布读,彻底的分布)
该架构的问题
该分布式系统中存有1个变量,count
- 到达顺序问题
- 1号请求A节点count=100,2号请求B节点count=99,最终应该count=99
- 但count=99先到达C节点,count=100后到达C节点,导致C节点的count=100,产生错误
- 幂等问题
- 假设count=100
- 1号请求A节点count+=1,以为更新失败了,重新请求count+=1,最终应该count=101
- 由于+=不满足幂等律,count=102,产生错误
问题解决
如果我们找到一种数据结构,它满足交换、结合、幂等律,那么上述顺序问题,幂等问题将不复存在
- 交换律 a∨b = b∨a
- 结合律 (a∨b)∨c = a∨(b∨c)
- 幂等律 a∨a = a
一个CRDT的简单例子
假设系统中存有count = 100,表示100块钱
- 1号节点存入10块,在它看来,count=110
- 2号节点取出10块,在它看来,count=90
- 3号节点,count=100
这就是最终一致的系统,存在不一致的时刻
1号和2号的更新信息发送给3号节点,3号节点并不知道110和90究竟哪个正确,失败
改变数据结构
不存count最终值,存操作
- 1号节点存入10块,存count+=10
- 2号节点取出10块,存count-=10
结果
- 3号节点收到其他节点的通知时,即可通过count = 100+10-10 = 100,计算出最终正确结果,而且不同节点的到达顺序不影响最终结果,满足交换律和结合律
- 进一步优化,比如说给每个操作加一个时间戳,包含相同时间戳和操作的通知只处理一次,满足幂等律
此时,同时满足交换律,结合律和幂等律,我们得到了CRDT
CRDT的几种结构
G-Counter(Grow-only Counter)
该数据结构实现了+运算
- n个node
- payload是长度为n的数组
- update函数负责+1
- merge函数负责合并两个node的payload
- compare函数负责merge时选择哪个node的P[i]
- query函数负责计算v值,sum(P[i])
减运算怎么办?
PN-Counter (Positive-Negative Counter)
该数据结构实现了+/-运算
字符串运算怎么办?
G-Set (Grow-only Set)
实现了字符串add()操作
- merge函数直接取两个set的并集,只能添加不能删除
- OrbitDB底层基于G-Set,其将常用的数据结构转化成字符串记录在G-Set中,比如说Orbit支持的Key-Value结构本质上也是G-Set实现的
字符串删除怎么办?
2P-Set (Two-Phase Set)
实现了字符串的add()和remove()操作
再加一个Set,记录被删除的字符串,query的时候取差集
删了又想加回来怎么办?
LWW-Element-Set (Last-Write-Wins-Element-Set)
与2P-Set很像,add()和remove()操作加时间戳,字符串在add和remove集合中都存在时,究竟是否存在取决于最晚的时间戳
出现相同时间戳怎么办? 可以设置bias,bias toward add or remove
OR-Set (Observed-Removed Set)
与LWW-Element-Set很像,时间戳替换为唯一标识(tags),query时add tag set 减去 remove tag set,仍然非空,则该数据存在
OrbitDB如何实现在线聊天
OrbitChat |
---|
OrbitDB |
ipfs-log |
ipfs/Pubsub |
- OrbitDB是一个基于ipfs的新型分布式数据库
- OrbitDB实现log, feed, keyvalue, docs, counter等常用数据结构
- ipfs-log实现G-Set
- ipfs的Pubsub目前是实验性功能,实现publish-subscribe发布订阅模式
工业界应用
- SoundCloud open-sourced Roshi, implemented a LWW-element-set CRDT on top of Redis.
- Riak is a distributed NoSQL key-value data store based on CRDTs.
- League of Legends uses the Riak CRDT implementation for its in-game chat system,7.5 million concurrent users and 11,000 messages per second
总结
- 空间换时间
- 一个优美的,有特定适用场景的分布式同步算法
- 并不是银弹
参考
Newest Posts