rqlite是一个轻量级的、开源的、用Go编写的分布式关系数据库,它使用SQLite作为其存储引擎。6.0.0版本已经发布使集群更加强大。它还为更多的重要功能奠定了基础。
6.0有什么新内容?
6.0使用现有的Raft节点间TCP连接,在rqlite节点间复用了多个逻辑连接。这种多路复用存在于早期版本中,后来被删除,但现在以一种更复杂、更强大的方式重新引入。
这个新的逻辑连接允许追随者以更强大和无状态的方式找到领导者,当提供需要客户端联系领导者的查询时。它还在这个逻辑连接上使用了协议缓冲区,导致了更强大、可扩展的代码。
rqlite集群中的节点间通信已经发展很多年,看看这3种设计模式的背景是很有意思的。在这种情况下,我所说的节点间通信是什么意思?我指的是在集群节点之间传输rqlite本身特有的信息,但不是Hashicorp代码之间的Raft特有通信。
首先要理解的是,每个rqlite节点都暴露了两个网络接口--第一,一个由Hashicorp Raft系统管理的Raft网络地址,第二,一个HTTP API,客户用它来查询SQLite数据库。从客户的角度来看,Raft网络地址是一个实现细节,它只是为了实现集群功能。
由于Hashicorp Raft的实现,每个节点都知道其他节点的Raft网络地址,而每个rqlite节点也必须了解Leader的HTTP URL_,所以_它可以告知客户端代码在哪里重定和查询。由于领导者可以改变,甚至被完全取代,每个rqlite节点必须随时拥有关于领导者HTTP URL的最新信息。
核心的挑战是,这些关于HTTP URL的信息不是Hashicorp Raft内在处理的,因为Raft层只知道Raft网络地址。HTTP URLs是rqlite的概念,而不是Raft的概念。
因此,如果服务客户的请求需要与领导者联系,就需要rqlite代码来处理将客户重定向到领导者的HTTP URL(不是所有的查询都涉及领导者,但大多数都涉及)。
如何做到这一点已经随着时间的推移而演变。
模式1:客户必须搜索领导者
上图是一个3节点的rqlite集群。在rqlite 2.x中,如果客户联系一个跟随者,跟随者返回的信息客户可以用来找到领导者,但客户可能需要联系过程中的其他每个节点来确定哪个是领导者。
在3.x版本之前,如果请求的操作只能由领导者提供,rqlite节点会将领导者的Raft网络地址返回给客户端(假设该节点本身不是领导者)。这迫使客户端检查所有其他集群节点,看哪个节点有相关的Raft地址,然后将查询本身重定向到该节点的HTTP API URL。
虽然这意味着rqlite的代码比后来的版本要简单得多,但这种方法只有在客户端知道rqlite集群中的每个节点,或者总是知道哪个节点是Leader的情况下才有效。这不是很实用,尤其是当集群中的节点发生变化或失效时。没有可靠的、简单的方法可以让客户端代码知道这些变化,所以客户端可以有效地与集群失去联系。
模式2:节点通过Raft共识沟通HTTP API URLs
在3.x、4.x和5.x中,rqlite节点使用Raft共识将Raft网络地址映射到HTTP API URLs,对于每个节点都是如此。如果客户端联系追随者,追随者知道领导者的HTTP URL(通过Raft共识更新和管理的映射),并将其返回给客户端。这通常是有效的,但从长远来看,这个实现并不健全。
到了3.0版本,很明显rqlite Follower节点需要返回适当的HTTP重定向。但问题是_Follower节点如何_在_任何时候_都_知道Leader的HTTP URL_,而不考虑集群发生的任何变化?
3.x、4.x和5.x版本使用Raft共识系统本身来广播这一信息。当一个节点加入集群时,它将向Raft日志提交一个更改,更新将Raft网络地址映射到HTTP API URL的配置。由于Raft共识系统,每个节点都知道其他每个节点的Raft网络地址,所以理论上,每个节点都可以通过这种映射了解其他每个节点的HTTP API URL。因此,每个节点都知道领导者的HTTP API URL,甚至在领导者变更时也是如此。
这种设计在很长一段时间内运行良好,但也有一些不足之处。一个可能的问题是,如果一个节点加入了集群,但却没有更新它的HTTP API URL映射(无论什么原因)该怎么办?这有可能发生,因为这是对Raft系统的两个不同的变化,但可能性很小。但如果发生了,集群可能需要大量的人工干预才能恢复。
随着时间的推移,其他的错误被发现,这表明当集群成员定期改变时,这种方法并不健全。这种设计还要求操作者明确地从集群配置中删除失败的节点,否则Raft地址到HTTP-URL的映射就会变得陈旧。最终,这种设计意味着除了SQLite数据库外,还需要通过Raft存储额外的状态。而更多的状态意味着有更多的机会出现错误。
因此,虽然这种方法在3.x、4.x和5.x版本中多年来运作良好,但到了6.0版本,已经发现了一种更好的方法。因此,与其说是修复错误,不如说是重新设计。
模式3:追随者按要求查询领导者
在6.x版本中,如果客户端联系追随者,追随者将根据需求查询领导者的HTTP URL,并将其返回给客户端,然后客户端将直接联系领导者。这种设计是无状态的,而且不涉及Raft共识。
在6.0版本中,当追随者需要了解领导者的HTTP API时,它可以按需联系领导者。关键是,它通过Raft TCP连接与领导者联系,无论如何,这个连接必须始终存在--没有这个连接,节点就不是集群的一部分,而集群有更大的问题。因此,这种新的设计并没有引入任何新的故障模式,尽管它确实引入了一个额外的网络跳。
这种类型的设计也是无状态的,即使联系Leader以了解其HTTP API URL失败了,这种失败也是在查询时发生的--这意味着客户可以被告知,客户可以决定如何处理。最有可能的是,如果错误是暂时性的,客户端可以简单地重试请求。
你可以在CHANGELOG中找到关于6.0设计的进一步细节。
未来的版本中会有什么?
这种新的设计和实现使得实现以下即将到来的功能更加简单。
连接池
6.0确实导致了节点之间更多的网络流量,如果一个追随者被不断地查询。这并不是大量的网络流量,但节点之间的连接池将有助于减少连接建立时间。
透明的请求转发
今天,如果一个rqlite_Follower_收到一个必须由_Leader_服务的请求,它将返回HTTP 301。现在,有了更复杂的节点间通信机制,未来的版本将允许Follower节点代表客户端直接查询Leader,并将结果直接返回给客户端。这将使CLI的使用和客户端库的编码更简单,并使集群更容易工作--允许rqlite更透明地提供查询。
更好的Kubernetes支持
在6.0的开发过程中,我从Sosivio的团队那里得到了一些很大的帮助。他们发现了5.x系列中的一些bug,并提供了关于如何改变rqlite以便在Kubernetes上更好地工作的最佳实践建议。这些改变可能会在未来的版本中出现。
接下来的步骤
下载6.0版本,并尝试更强大的集群--并关注未来版本的新功能。