dot 在您所在城市的活动中体验快速发展的未来。

加入我们参加 Redis Released

使用多种键的 Redis 集群最佳实践

Redis 中的到底是什么?Redis(或任何键值)存储器的最初目的是为每个独立的数据设定一个特定键或标识符。Redis 很 快用数据类型扩充了这个概念,在这种情况下,一个单一键可以引用多条(甚至是数百万条)数据。随着模块加入生态系统,键的概念得到了进一步延伸,因为现在一条单一的数据可以跨越多个键(例如对搜索与查询)索引。因此,当被问及 Redis 是否是一个键值存储器时,我通常会回答“它源自键值数据库”,但需要注意的是,在这个时候,很难仅仅把 Redis 定义为一个键值存储器了。然而,在 Redis 中,键仍然至关重要的一处是集群

开始使用 Redis Cloud:免费试用

什么是 Redis 集群?

Redis 集群是 Redis 数据存储的分散式实现,它可以让数据分割在多个 Redis 节点上。在 Redis 集群中,数据会分割在多个 Redis 节点上,以使每个节点只保存整个数据集中的一小部分。这可以让集群横向扩展,并通过添加更多节点来应对更大的负载。在 Redis 中,数据会在集群中的一个地方,每个节点或分片都会拥有键空间的一部分。一个集群会分成 16384 个槽 - Redis 集群中节点或分片的最大数量。当然,在你使用高可用性运行时,你的数据可能会保存在副本中,但任何时候都不会把一个单一键分割在多个节点上。

由于大多数集群只包含很少量的节点,因此这些哈希槽会成为键的逻辑划分。在 4 节点集群的极简示例中,我们会得到如下布局

节点
0 – 4095节点 #0
4096 – 8191节点 #1
8192 – 12287节点 #2
12288 – 16384节点 #3

注意:在 Redis Enterprise 中,每个节点会进行进一步的细分。操作方式相同,但不是节点,而是分为分片。

举例来说,如果你有一个你确切知道在槽 2000 中的键,那么你就会知道数据保存在节点 #0 上。如果键在槽 9000 中,那么它就在节点 #2 上。在现实中,它会比这个复杂得多(槽会不断移动并重新平衡),但为了理解事务和键,对集群的这种简化概念性理解就足够了。

那么密钥与槽位之间如何相关?这些槽位实际上是哈希槽位,其中每个密钥会经过哈希函数处理,以从任意长度的字符串中以数学方式推导出一个数字。当涉及密码哈希时,哈希会最频繁地出现在公开对话中,而密码哈希是一种相关但更为复杂的计算。以密码不会直接存储而是以密码的数学表示存储的方式,您请求的密钥实际上会归结为其数学表示(在此情况下使用 CRC16 哈希函数)。CRC16 将返回一个 14 位数字,我们然后可以对其进行模 16384 的运算。有趣的是,这正是可用哈希槽位数,不是吗?

这与 Redis 中的事务有何关系?

Redis 中的事务仅发生在同一个哈希槽位内,从而确保了最高的吞吐量。由于不需要跨节点/分片通信,因此消除了许多故障场景。根据这一点,您必须确保在进行事务时,涉及的所有密钥都在同一槽位中。那么,您如何才能知道您的密钥是否与事务中的另一个密钥位于同一槽位(以及同一节点/分片)中?

https://www.youtube.com/embed/YF3wj5d_tkc

Redis 哈希标签

虽然许多密钥有可能位于同一个哈希槽位中,但这从密钥命名角度来看是不可预测的,并且在命名密钥时不断检查槽位(在开放源代码中使用CLUSTER KEYSLOT或在群集 API 模式中使用 Enterprise)是不合理的。处理此问题的最佳方法是通过一些高级规划和一项名为哈希标签的功能。在开放源代码 Redis 中,花括号({ 和 })是哈希标签的标志符,这两个字符之间的字符串将经过 CRC16 哈希函数处理。让我们来看几个示例:

密钥哈希伪代码哈希槽位
user-profile:1234CRC16(‘user-profile:1234’) mod 1638415990
user-session:1234CRC16(‘user-session:1234’) mod 163842963
user-profile:5678CRC16(‘user-profile:5678’) mod 163849487
user-session:5678CRC16(‘user-session:5678’) mod 163844330
user-profile:{1234}CRC16(‘1234’) mod 163846025
user-session:{1234}CRC16(‘1234’) mod 163846025
user-profile:{5678}CRC16(‘5678’) mod 163843312
user-profile:{5678}CRC16(‘5678’) mod 163843312

根据这些示例,您可以看到 Redis 不会允许对密钥进行事务user-session:1234user-profile:1234,但会允许使用user-profile:{1234}user-session:{1234}。

说明:您可能会想:“真棒,将所有内容都放在同一个哈希槽中,这样我就不用担心事务会集中了!”您不会是孤独的,因为我已不止一次听到过这种想法。Redis 不会阻止您这样做或类似的事情,但是最终您将得到一个不平衡的集群,更糟的是,一个满节点和许多空节点。仅在必要时使用哈希标签,即使在必要时也要少用。

Redis Enterprise 可以使用此策略,但它还添加了另一项功能来让分片更加透明。您可以使用正则表达式定义键的特定部分以通过哈希函数进行计算,而不是使用花括号。使用正则表达式 /user-.*:(?<tag>.+)/让我们重新审视上面的一些示例:

密钥哈希伪代码哈希槽位
user-profile:1234CRC16(‘1234’) mod 163846025
user-session:1234CRC16(‘1234’) mod 163846025
user-profile:5678CRC16(‘5678’) mod 163843312
user-session:5678CRC16(‘5678’) mod 163843312

此正则表达式足够灵活,还可以处理以“user-”开头的其他键,因此我们可以拥有诸如“user-image”或“user-pagecount”这样的键。在此方案中,每个用户的信息将保留在单个哈希槽中,从而在单个用户范围内进行各种事务。

让我们进一步扩展这个示例。假设用户更改了其个人资料中的部分信息,并且我们要更新个人资料和会话信息,还要延长其会话时间,使其不会过期。以下是事务的典型(如果简化)版本

> MULTI
OK
> HSET user-profile:1234 username "king foo"
QUEUED
> HSET user-session:1234 username "king foo"
QUEUED
> EXPIRE user-session:1234 7200
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
3) (integer) 1

在 Redis Enterprise 中,此设置的正则表达式可正常工作,因为键的哈希部分相同。如果您要在开源 Redis 上运行此命令,则需要确保您的键具有花括号,否则会遇到 CROSSSLOT 错误。这种类型的错误的好处是 Redis 会立即通知您无效事务/槽越界违规:

> MULTI
OK
> HSET user-profile:1234 username "king foo"
QUEUED
> HSET user-session:1234 username "king foo"
(error) ERR CROSSSLOT 在“MULTI”内,请求中的键没有哈希到相同槽(命令='HSET',键='user-session:1234')
> EXPIRE user-session:1234 7200
(error) ERR CROSSSLOT 在“MULTI”内,请求中的键没有哈希到相同槽(命令='EXPIRE',键='user-session:1234')
> EXEC
(error) EXECABORT 由于前面的错误,事务已放弃。

请记住,某些哈希槽和键问题并非完全是事务,但在行为上有些类似 - 操纵多个键的单个命令。考虑以下示例

> LPUSH my-list 1 2 3
(integer) 3
> RPOPLPUSH my-list my-new-list
(error) ERR CROSSSLOT 在“RPOPLPUSH”内,请求中的键没有哈希到相同槽(key='my-new-list')

RPOPLPUSH 是一种原子操作,用于从一个列表中取出一个元素并将其推送到另一个列表中。操作字是原子。如果这两个列表驻留在两个不同的哈希槽中(非常类似于事务中),你将获得 CROSSSLOT 错误。开源 Redis 对此非常严格,并且禁止任何操作多个哈希槽的命令。Redis Enterprise 针对简单命令有几种变通方法,特别是 MGET 和 MSET。

Redis 集群最佳做法

如果你一直是 Redis 单实例的高级用户,迁移到集群可能会感觉有点奇怪。你所依赖的一些命令和/或事务将不再适用于特定键,如果你真的不走运,你设计密钥空间的方式可能会出现问题。以下是一些有关设计你的应用程序以便在集群上最佳工作的提示

  1. 考虑密钥空间。键是否存在通用特征,可以明智地划分你的工作负载(按用户、操作、时间等)?使用井号标签或正则表达式将键明智地分配到哈希槽中。
  2. 避免将全局状态放在需要事务化操作的单个键下面,否则,你可能会遇到 CROSSSLOTS 错误。
  3. 评估你的 MULTI/EXEC 事务。看看你是否真正需要一个事务,或者一个管道就行了。不要忘记考虑多键命令以及它们是否可以用多个命令替换。

想要了解更多吗?

观看我们最近的科技讲座,主题为 Redis 开源 VS Redis Enterprise 中的购买与构建:集群和配置!