介绍
Raft是当前广泛使用的共识算法。流行的系统,如Kafka、Cockroach DB、MongoDB、Neo4j、Splunk等,都使用Raft来实现共识。系统要么是最终一致性的,要么是强一致性的。线性一致性是一致性模型中最强大的,但实现它可能很耗时。键值数据库出现在市场上,以避免SQL数据库的复杂性并提供横向扩展性。这些数据库主要提供两种操作:get(key)和put(key, value)。
在我对Raft相关论文的探索中,我找到了一篇有趣的文章,标题为‘Rethink the Linearizability Constraints of Raft for Distributed Key-Value Stores’。本文详细介绍了在类似键值数据库中减少读写线性操作延迟以提高吞吐量的技术。本文提供了对这篇论文的简要概述。
Raft如何处理写请求?
Raft处理写请求涉及三个关键操作:追加(Append)、提交(Commit)和应用(Apply),因此引入了一系列用于读写请求处理的索引,以确保线性一致性。这些索引包括日志索引、提交索引和应用索引。
图片来源:Paper 9458806
1.追加
当客户端向领导者发送写请求时,请求中的日志将被分配一个唯一递增的索引号。领导者将日志附加到本地日志存储,并向跟随者发送用于复制的附加条目请求。领导者收到来自大多数跟随者的附加条目请求的响应后,将日志设置为已提交,即新写入的数据在系统中是安全的。
2.提交
当领导者收到附加条目请求的大多数跟随者的响应时,日志被设置为已提交,即新写入的数据在系统中是安全的。
3.应用
领导者开始将已提交的日志应用到其本地状态机,并并行通知跟随者执行相同的操作。每个节点在应用操作成功后将更新其应用索引。只有在领导者将日志应用到状态机之后,领导者才能向客户端返回响应。
写请求序列
Raft如何处理读请求?
为了实现线性一致性,所有读请求都由领导者自身处理。Raft还为此引入了读索引。当领导者收到读请求时,请求的读索引设置为当前提交索引。只有当领导者的应用索引不小于读索引时,领导者才能执行读请求并将结果返回给客户端。在这种情况下,我们可以确保客户端不会获取到陈旧的数据。
对这些操作的评估
文章讨论了所有这些操作的时间消耗评估,如复制、提交和应用。根据评估,应用操作是最耗时的。对于带有高速网络的系统,网络开销较低。日志的结构简单,日志附加通常是一个具有较高速度的顺序I/O操作。因此,更新复杂的状态机最可能成为性能瓶颈。
下图显示了值大小从1KB到1MB时,应用操作和所有其他操作的时间消耗百分比。它显示应用实际上是慢的,占写请求总时间的近40%。
如何改进这个?
与其他类型的数据库相比,键值存储是一个简单的数据库。写操作只是将键和值插入或更新到数据库,而读操作只是为给定的键读取相应的值。文章证明,去除请求处理中的应用阶段可能会减少延迟。应用操作可以异步执行。读和写操作不需要等待应用完成。为此,文章引入了两种新方法:
- 提交返回(用于写操作)
- 立即读(用于读操作)
1.提交返回
这个想法是一旦请求中的日志被提交,直接向客户端返回成功响应,而不必等待将日志应用到状态机。因此,这个方法被称为提交返回。提交返回不会破坏KV存储的正确性和线性一致性。
提交返回
2.立即读
在Raft中,读操作在查询状态机之前等待应用索引追上读索引。当实施提交返回时,应用索引和读索引之间的差距会比传统Raft方法中的差距更大。因此,提交返回可以提升写处理但可能降低读处理。然而,如果
我们能够消除由于应用索引和读索引之间的差距而引起的等待时间,读性能将得到显著改善。
由于键值对的读操作简单,我们可以直接根据位于读索引和应用索引之间的日志数据副本执行读请求,而不必查询状态机。如果日志中存在键的值,则立即返回,否则查询状态机并返回结果。通过这种方式,读性能将大大提高。
例子:立即读
采用这种方法,平均写入延迟将减少36.4% ∼ 39.9%,平均读取延迟将减少5.8% ∼ 16.2%,与Raft相比。有关详细信息,请参阅实际论文,文章中提供了论文链接。