本文对强一致存储系统进行思辨:Paxos/Raft带来了什么?又失去了什么?我们真的需要强一致性存储系统吗?我们业务对一致性要求没到强一致的地步,真的需要Paxos/Raft驱动设计存储系统吗?
我想用PolarDB-X 话题讨论|分布式数据库,挂掉两台机器会发生什么 这篇引出本文。在阿里PolarDB公众号中,作者罗列了主备架构的各种痛点,得出了一些结论:
• 所谓一致性和可用性“兼顾”的主备方案,实际上是“兼不顾”。现代分布式数据库,解决了传统数据库主备结构的容灾问题。
• 选择Paxos/Raft协议,每份数据会存在三个副本,并且能够保证在一个副本挂掉的情况下,不影响可用性,并且不会出现任何一致性问题(脑裂、丢数据、丢更新等)。在这个时代,但凡数据有一定的重要性,都不应该选择主备架构的产品。
• 最后(其实这才是作者重点)通过概率算出随意分布的三副本挂两台丢数据的风险,引出CopySet算法。
我首先尝试“反驳”作者过于绝对的结论,并进一步思考,我们业务对一致性要求没到强一致的地步,真的需要Paxos/Raft驱动设计存储系统吗?甚至抛出一个面试问题:当存储系统使用了Raft/Paxos协议,是否意味着该系统就是强一致系统?
我先给出一个结论:系统使用了Paxos或者Raft,并不意味着该系统就是强一致性系统,也不意味着客户端"免费"地获得了强一致性。Paxos或者Raft只是副本间的复制协议,它规约了一种(获得一致性)游戏规则帮助架构师理清思路,解决可能会遇到的容灾、脑裂、数据丢失等问题,而不是仅为系统提供了强一致性保证(事实上要获得强一致,开发者需要付出更多的努力呢)。
用户读写master,一般在控制面有Router对Master/Slave进行选举,当Router发现Master有问题(可能是网络、进程core、宕机)即进行切换,并更新路由信息。
单主模型最大的问题是丢失更新,在Master写入数据后,返回给客户端后,Slave切换后仍可能丢失更新。现象为:写入的数据永远丢失了。单主模型切换为了尽量保证一致性,切换前各种探测,通常在3~5s不可用。
进一步可能的选项是:写时同步复制,即同时等slave写完后再返回客户端,衍生的问题是,slave宕机时带来的可用性问题。再进一步的优化是?我冒出一个想法:模仿neondb@Architecture decisions in Neon[1],将WAL分离到独立集群,必要时,补齐备机wal,无忌切换宕机。有意思的是,应该如何确保wal集群的可用性?
多主模型好处显而易见,没有单主模型带来的切换不可用时间。例如字节Abase2 主要通过多主技术实现系统高可用目标。在多主模式下,分片的任一副本都可以接受和处理读写请求,以确保分片只要有任一副本存活,即可对外提供服务。
多主的问题是需要众多技术和补丁,2008年Dynamo论文[2]展示了极致高可用的系统是如何用众多补丁设计架构。例如Abase2使用NWR模型实现可调一致性,使用Anti-Entropy检测对比分片差异,注意这是每个副本在内存缓存replicaLog向量[seqno1,seqno2...seqnoN],并发向其他副本拉齐缺失的日志,以逐步推进seqno前进。这里缺失log的原因是NWR模型中N<W,即部分副本写入成果就算成功(所以需要补数据)导致。与 DynamoDB、Cassandra 等通过扫描引擎层构建 merkle tree 来完成一致性检测相比,Abase2似乎只关注写入时wal的一致性。尽管做到了这些,Abase2还是会产生非幂等操作冲突的问题(why?),进而需要引入基于HLC的操作的CRDTs技术解决冲突。
多主系统架构是复杂的,Dynamo系统运维如此复杂已被Amazon抛弃。不知字节Abase2还好吗。
网络上关于Raft协议的讨论真的多如牛毛,我也不打算在此讨论Raft协议细节,而是想要回答:选择了Raft协议,并不是立即拥有了强一致性系统。从未有人讨论过如何将Raft降级一致性(从而获得更好的可用性):
如下图,当我们使用一个Raft Leader再配合Learner节点(不投票节点)时,立即具备了与主备模式相同写代价的架构,我称之为What ever,don’t give a xxx模型。数据爱丢就丢无所谓。同理上层需要做一个learner切换升成Raft leader(有损)的控制面逻辑。
使用witness节点(只投票不计算不存储),可以让Raft base系统成本降低到与主备模型相同(almost)。
回到刚开始的问题,使用Raft驱动设计存储,是否我们就(免费)得到了强一致的系统?答案是否定的, 我们在Raft读数据时,通常会选择经济的leader lease防止脑裂,下图中,若系统发生了网络分区,leader无法继续续租,则认为可能发生了脑裂,此时我们有两个选择,立即返回失败(因为无法保证线性一致性)或者直接返回数据(可能发生数据回绕)。后者提升了可用性,并退化到主备模型相同的一致性。
raft base系统是否会导致既写raft wal,又写了一份存储引擎wal,额外增加了开销?别开玩笑了,将两个日志合并,关闭后者是基操了。
raft/paxos复制协议只是帮助完成了副本间数据的同步,复制协议规约了一种可能获得强一致性的游戏规则。基于Raft log index,可以简化系统扩展设计。而主备系统也不是无可救药的毒药,加上各种补丁,可能有不错的收益。下文看到,越来越多公司用上了raft+rocksdb架构,填补内存型redis成本高昂与性能/成本均衡间的空隙。
abase2[3]:多主系统,多租户,系统是挺复杂,各种补丁,底层是rocksdb/terarkdb/等磁盘存储。
ByteKV[4]:Raft base系统,感觉比较优雅,支持SQL,底层是rocksdb磁盘存储。
Squirrel[5]:内存型Redis,优化了各种扩缩容技术,跨地域容灾,大改RDB持久化逻辑。
Cellar[6]:Raft base系统,底层是rocksdb磁盘存储。
TIKV c++版:据说是tikv的C++重写版。Raft base系统,底层是rocksdb磁盘存储。
Tair:应该是类似Rocksdb的LSM存储。
Tendis: gossip版redis集群实现,底层是rocksdb,已开源。
.....(还有非常多...)
RedKV2:改进版gossip版redis集群实现,底层是rocksdb。
Raft base系统,底层是rocksdb磁盘存储。
pika、kvrocks:底层是rocksdb磁盘存储。有看到两位中国磁盘型redis做的最好开源系统在计划做Raft base 改造。希望未来NOSQL-CN会在AxeDB上实现出一套内存型/混合型Raft base Redis。
[1]
Architecture decisions in Neon: https://neon.tech/blog/architecture-decisions-in-neon[2]
论文: https://www.cnblogs.com/foxmailed/archive/2012/01/11/2318650.html[3]
abase2: https://www.51cto.com/article/709845.html[4]
ByteKV: https://zhuanlan.zhihu.com/p/145645524[5]
Squirrel: https://tech.meituan.com/2020/07/01/kv-squirrel-cellar.html[6]
Cellar: https://tech.meituan.com/2020/07/01/kv-squirrel-cellar.html