跳转到主要内容

分区:如何在多个实例之间拆分数据

分区是将数据拆分到多个 KeyDB 实例的过程,这样每个实例将只包含键的子集。本文档的第一部分将向您介绍分区的概念,第二部分将向您展示 KeyDB 分区的替代方案。

分区为何有用#

KeyDB 中的分区有两个主要目标:

  • 它允许使用多台计算机的内存总和来创建更大的数据库。没有分区,您将受限于单台计算机所能支持的内存量。
  • 它允许将计算能力扩展到多个核心和多台计算机,并将网络带宽扩展到多台计算机和多个网络适配器。

分区基础#

有不同的分区标准。假设我们有四个 KeyDB 实例 R0R1R2R3,以及许多代表用户的键,如 user:1user:2 等等,我们可以找到不同的方法来选择在哪个实例中存储给定的键。换句话说,有*不同的系统来映射*一个给定的键到一个给定的 KeyDB 服务器。

执行分区最简单的方法之一是范围分区,它是通过将对象范围映射到特定的 KeyDB 实例来实现的。例如,我可以说从 ID 0 到 ID 10000 的用户将进入实例 R0,而从 ID 10001 到 ID 20000 的用户将进入实例 R1,以此类推。

这个系统是可行的,并且在实践中确实有使用,但是,它的缺点是需要一个将范围映射到实例的表。这个表需要被管理,并且每种类型的对象都需要一个表,因此在 KeyDB 中范围分区通常是不可取的,因为它比其他替代分区方法效率低得多。

范围分区的一个替代方案是哈希分区。这个方案适用于任何键,不需要键的格式为 object_name:<id>,并且非常简单:

  • 取键名并使用一个哈希函数(例如,crc32 哈希函数)将其转换成一个数字。例如,如果键是 foobarcrc32(foobar) 会输出类似 93024922 的值。
  • 对这个数字使用模运算,将其转换为 0 到 3 之间的一个数字,这样这个数字就可以映射到我的四个 KeyDB 实例之一。93024922 模 4 等于 2,所以我知道我的键 foobar 应该存储在 R2 实例中。*注意:模运算返回除法运算的余数,在许多编程语言中用 % 运算符实现。*

还有许多其他执行分区的方法,但通过这两个例子,您应该能明白大概意思。一种高级的哈希分区形式称为一致性哈希,并由一些 KeyDB 客户端和代理实现。

分区的不同实现#

分区可以是软件栈中不同部分的责任。

  • 客户端分区意味着客户端直接选择正确的节点来写入或读取给定的键。许多 KeyDB 客户端实现了客户端分区。
  • 代理辅助分区意味着我们的客户端将请求发送到一个能够说 KeyDB 协议的代理,而不是直接发送到正确的 KeyDB 实例。代理将根据配置的分区方案确保将我们的请求转发到正确的 KeyDB 实例,并将回复发送回客户端。KeyDB 和 Memcached 的代理 Twemproxy 实现了代理辅助分区。
  • 查询路由意味着您可以将查询发送到任意一个实例,该实例将确保将您的查询转发到正确的节点。KeyDB 集群实现了一种混合形式的查询路由,在客户端的帮助下(请求不是直接从一个 KeyDB 实例转发到另一个,而是客户端被*重定向*到正确的节点)。

分区的缺点#

KeyDB 的一些功能与分区不太兼容:

  • 涉及多个键的操作通常不受支持。例如,如果两个集合存储在映射到不同 KeyDB 实例的键中,您就无法对它们执行交集操作(实际上有办法做到这一点,但不是直接的)。
  • 涉及多个键的 KeyDB 事务无法使用。
  • 分区的粒度是键,因此不可能对只有一个巨大键(如一个非常大的有序集合)的数据集进行分片。
  • 使用分区时,数据处理更加复杂,例如,您必须处理多个 RDB / AOF 文件,并且要备份数据,您需要聚合来自多个实例和主机的持久化文件。
  • 增加和移除容量可能很复杂。例如,KeyDB 集群支持在运行时添加和移除节点,从而实现数据的几乎透明的重新平衡,但其他系统如客户端分区和代理不支持此功能。然而,一种称为*预分片*的技术在这方面有所帮助。

数据存储还是缓存?#

尽管在 KeyDB 中,无论将其用作数据存储还是缓存,分区的概念上是相同的,但当用作数据存储时存在一个显著的限制。当 KeyDB 用作数据存储时,给定的键必须始终映射到同一个 KeyDB 实例。当 KeyDB 用作缓存时,如果给定的节点不可用,使用不同的节点并不是大问题,我们可以随心所欲地改变键-实例映射,以提高系统的*可用性*(即系统响应我们查询的能力)。

一致性哈希的实现通常能够在给定键的首选节点不可用时切换到其他节点。同样,如果您添加一个新节点,部分新键将开始存储在新节点上。

这里的主要概念如下:

  • 如果 KeyDB 用作缓存,使用一致性哈希进行扩展和缩减很容易。
  • 如果 KeyDB 用作存储,则使用固定的键到节点的映射,因此节点数量必须是固定的,不能改变。否则,就需要一个能够在添加或删除节点时在节点之间重新平衡键的系统,目前只有 KeyDB 集群能够做到这一点 - KeyDB 集群已普遍可用并可用于生产。

预分片#

我们了解到分区的一个问题是,除非我们将 KeyDB 用作缓存,否则添加和删除节点可能很棘手,使用固定的键-实例映射要简单得多。

然而,数据存储需求可能会随着时间而变化。今天我可能用 10 个 KeyDB 节点(实例)就够了,但明天我可能需要 50 个节点。

由于 KeyDB 的占用空间极小且轻量(一个备用实例使用 1 MB 的内存),解决这个问题的一个简单方法是从一开始就启动大量实例。即使你只从一台服务器开始,你也可以从第一天起就决定生活在一个分布式世界中,在你的单台服务器上运行多个 KeyDB 实例,并使用分区。

而且你可以从一开始就选择一个相当大的实例数量。例如,32 或 64 个实例对于大多数用户来说可能已经足够,并为增长提供了足够的空间。

这样,随着您的数据存储需求增加,需要更多 KeyDB 服务器时,您所做的只是将实例从一台服务器移动到另一台。一旦您添加了第一台额外的服务器,您将需要将一半的 KeyDB 实例从第一台服务器移动到第二台,以此类推。

使用 KeyDB 复制,您很可能能够在对用户影响最小或没有停机时间的情况下完成移动:

  • 在新服务器上启动空实例。
  • 将这些新实例配置为源实例的副本以移动数据。
  • 停止您的客户端。
  • 用新服务器的 IP 地址更新已移动实例的配置。
  • 向新服务器中的副本发送 `REPLICAOF NO ONE` 命令。
  • 用更新后的新配置重新启动您的客户端。
  • 最后关闭旧服务器上不再使用的实例。

KeyDB 分区的实现#

到目前为止,我们已经从理论上涵盖了 KeyDB 分区,但实践中呢?您应该使用什么系统?

KeyDB 集群#

KeyDB 集群将是获得自动分片和高可用性的首选方式。您可以在集群教程中获取有关 KeyDB 集群的更多信息。

一旦 KeyDB 集群可用,并且如果您的语言有兼容 KeyDB 集群的客户端,KeyDB 集群将成为 KeyDB 分区的事实标准。

KeyDB 集群是*查询路由*和*客户端分区*的混合体。

Twemproxy#

Twemproxy 是 Twitter 为 Memcached ASCII 和 KeyDB 协议开发的一个代理。它是单线程的,用 C 语言编写,并且速度极快。它是根据 Apache 2.0 许可证条款发布的开源软件。

Twemproxy 支持在多个 KeyDB 实例之间自动分区,并可选择在节点不可用时将其剔除(这将改变键-实例映射,因此只有在将 KeyDB 用作缓存时才应使用此功能)。

它*不是*一个单点故障,因为您可以启动多个代理,并指示您的客户端连接到第一个接受连接的代理。

基本上,Twemproxy 是客户端和 KeyDB 实例之间的一个中间层,它将以最小的额外复杂性为我们可靠地处理分区。

支持一致性哈希的客户端#

Twemproxy 的一个替代方案是使用一个通过一致性哈希或其他类似算法实现客户端分区的客户端。有多个 Redis 客户端支持一致性哈希,特别是 Redis-rbPredisJedis

请查看Redis 客户端的完整列表(KeyDB 兼容),以检查是否有适用于您的语言的、具有成熟一致性哈希实现的客户端。