让Raft变快100倍 - Dragonboat的写优化

栏目: 后端 · 发布时间: 5年前

内容简介:详细的benchmark信息可参考github项目首页,同时也烦请大家点Star鼓励:

Dragonboat 是近期开源的 Go 实现的多组Raft库,16字节荷载的写可以持续在900万次每秒,9:1高读写比下可以持续在超千万次每秒。

详细的benchmark信息可参考github项目首页,同时也烦请大家点Star鼓励:

github.com/lni/dragonboat​github.com

Dragonboat的性能是持续优化改进的结果,它从2017年年中时候的每秒10万次写,一路走来,经过数十项优化以后最终成为当前开源版本所能具备的1000万次写每秒的性能。对于同样的吞吐需求,更高效的实现意味着库本身消耗的资源将更少,能为用户应用留出的资源更多。Dragonboat在2.8GHz志强上,对于16字节荷载,平均每个CPU核心能承担超过每秒40万次写,且在测试中线性伸展至22核每秒900万次写,属于当前最高效多组Raft实现。

这里从Raft协议的实现优化角度展开,从数十项已应用的优化中,介绍几个写请求的处理中的性能优化实例。

待提议的请求

当用户提交一个写请求(称为Propose,即提议)后,从性能考虑一般它并不是被立刻处理,而需要暂存。Go实现中,首先想到的多半是用channel来暂存,并通过channel将待提议的请求传递给负责处理的部分。 etcd的实现正是如此

casem:=<-propc:m.From=r.idr.Step(m)

这样做的性能问题显而易见,每次只能从channel中取出一个请求后逐一处理。而channel是个功能丰富的内建类型,比如协程的特性决定对channel的操作甚至可以带来协程的调度。

在Dragonboat中,在处理单个请求的地方,均避免使用channel做任何数据的传递。在处理待提议请求上,使用的是由两个slice来回切换而构成的一个可以整体访问的queue,每次取出当前slice中的所有待提议请求,一次合并(batching)地完成提议。

上述是写请求,读请求的暂存也是同样的问题同样的处理。

让Raft变快100倍 - Dragonboat的写优化

熟悉内建类型channel的第一步应该是理解何时避免使用它

Raft Log Entry的磁盘保存

Raft要求被commit的entry至少在过半数的机器上完成了落盘的保存,这确保只要quorum存活,磁盘上必然有所有已commit的entry。当每秒面对千万规模的Log Entry的写入,因为基数巨大,任何冗余的数据的写入都浪费磁盘空间与带宽,进而拖累延迟与吞吐性能。

以使用RocksDB等Key-Value store库来存储Raft的Log Entry库为例,对于某个Raft组而言,通常每个log entry被作为一条记录存储,它的key和value可按照如下方式决定:

让Raft变快100倍 - Dragonboat的写优化

本科一年级《计算基础》教科书对上述问题均有详细描述

这样的简单设计的问题在于:

Key和Value里存在大量的重复值,如上述的ClusterID和Term字段

Key和Value里均含有大量低信息量的值,如上述Index字段,正常情况下它应该是连续递增的

每个 entry被设定为一个Key-Value store的记录,可被独立地读写访问。但对共识库而言,磁盘上entry的访问具有很强的访问局部性,某entry被读写以后,它下一条Log Entry很可能是接着马上要被访问。

对上述简单的设计,试想一下如需每秒1000万次写,则每秒需添加1000万条记录,内含1000万个值完全重复的ClusterID与Term记录,这必然是低效的。

以etcd为例,其WAL包负责Rafty Log Entry的保存,虽没有使用Key-Value Store,且因为单组而无需保存ClusterID,但上述三个问题etcd依然都具备。

Dragonboat中,Log Entry的存储避免上述归纳的三个问题。在Leader不变,从而Term不变Index连续递增的通常情况下,Log Entry被安排为如下形式后保存,大幅降低了空间的浪费和写入带宽的需求,对延迟的改善也很明显。

让Raft变快100倍 - Dragonboat的写优化

永远不要DDoS攻击自己的存储系统

Raft Log Entry的模型

上一部分介绍了磁盘上存储Raft Log Entry,与内存内存放的Log Entry一起,根据具体哪些Log Entry被存放在内存那,哪些被存放在磁盘上,这样一个划分方法构成了Log Entry的具体存储模型。

通常的实现,比如etcd,是简单将所有Log Entry分为已落盘保存和仅在内存内两部分,如下图:

让Raft变快100倍 - Dragonboat的写优化

设计是简约还是简陋,咱跑个分吧

这种简单的划分最主要是根据Raft协议对Log Entry存储方式的要求,也就是Entry被多数节点保存至磁盘以后才可以被Commit并后续被执行。在这种简单设计中,一个Entry的存储方式随时间和状态的变迁如下:

让Raft变快100倍 - Dragonboat的写优化

swap分区真的不带基于内存的LRU cache ;)

问题也很明显:Entry被执行的时候,已经不在内存中了,需要从磁盘上读取,从而大幅度影响效率。etcd的用户,比如CockroachDB特地为此增加了一个LRU的cache层,以试图减小这样的读盘开销。但随之带来的,是庞大的维护这个LRU cache的性能成本支出。

Dragonboat不采用etcd式的两级Log Entry模型,而是采用一个三级的设计,确保近期马上会使用的Entry始终在内存里,且无需额外维护一个cache层,甚至无需额外的内存拷贝。

让Raft变快100倍 - Dragonboat的写优化

16GBytes ECC REG内存售价略高于100美元,折合一线软件工程师一小时工资

同时,如果需要,通过简单的限制最大允许内存使用量,便可避免因内存中存有太多Log Entry而使得内存使用过量的问题。待提议请求与收到的复制Log Entry的消息根据异步网络假设,均可随意丢弃,以确保限制内存用量。

数据序列化

因为是Go语言的实现,Dragonboat最初使用Google全家桶来做很多组件的实现。比如节点间通讯最初用gRPC,而数据序列化则顺理成章的使用Protobuf。显而易见的,这种选择必定是和高性能无缘了。

先从表面上看,Protobuf是一种特别慢的序列化方案,gogo protobuf在它的基础上有所改良,但依旧是很慢的。Dragonboat目前采用的是Protobuf加Colfer的混合方案,对性能敏感的类型,比如Raft的Log Entry,使用Colfer做序列化以取得更好的性能。详细的性能对比,可以参考 这里

Protobuf同时是一种allocation不友善的序列化库,其Go实现中,在Unmarshal一个含有Log Entry slice的结构体的时候,会因为slice反复扩容而反复对已Unmarshal的Log Entry做额外的拷贝搬运。针对这个问题,Dragonboat也给予了优化。

然后,上述序列化的表面问题,归根结底依旧是技术实现细节。真正的优化,必须贴合协议、算法,从原理上考虑。在一个Raft库里,序列化的最大问题是反复多次的序列化:

Leader上的Log Entry在落盘写的时候被序列化一次,在网络发送的时候,至少被序列化一次,甚至因为发送目标的不同,而被序列化多次。

Follower收到Leader发来的已经序列化了的Log Entry的内容,首先是将它们反序列化操作,然后落盘写的时候却又再次序列化它们。

Dragonboat将在下一阶段优化这一问题。

更多分享即将到来

下面的知乎回答包含了Dragonboat开发优化中所遇到的所有golang语言、runtime相关的性能问题。近期将对所列出的问题详细展开,逐一分析成因与解决办法。

你遇到过哪些高质量的 Go 语言面试题?​www.zhihu.com

版权信息

在不修改本文内容的前提下,欢迎完整转载。

本文标题图片是英国Bloodhound Car,图片版权为BBC所有。


以上所述就是小编给大家介绍的《让Raft变快100倍 - Dragonboat的写优化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C++ Primer 中文版(第 4 版)

C++ Primer 中文版(第 4 版)

Stanley B.Lippman、Josée LaJoie、Barbara E.Moo / 李师贤、蒋爱军、梅晓勇、林瑛 / 人民邮电出版社 / 2006 / 99.00元

本书是久负盛名的C++经典教程,其内容是C++大师Stanley B. Lippman丰富的实践经验和C++标准委员会原负责人Josée Lajoie对C++标准深入理解的完美结合,已经帮助全球无数程序员学会了C++。本版对前一版进行了彻底的修订,内容经过了重新组织,更加入了C++ 先驱Barbara E. Moo在C++教学方面的真知灼见。既显著改善了可读性,又充分体现了C++语言的最新进展和当......一起来看看 《C++ Primer 中文版(第 4 版)》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试