dot Redis 8 发布了——而且它是开源的

了解更多

Redis 集群中处理多个键的最佳实践

在 Redis 中,到底是什么?Redis(或任何键值存储)最初的目的是为每条独立的数据提供一个特定的键或标识符。Redis 很快通过数据类型扩展了这个概念,单个键可以引用多条(甚至数百万条)数据。随着模块进入生态系统,键的概念进一步扩展,因为现在一条数据可以跨越多个键(例如用于搜索和查询索引)。所以,当被问及 Redis 是否是键值存储时,我通常回答“它源自键值数据库系列”,但需要注意的是,在这一点上,很难仅将 Redis 视为键值存储。然而,键在 Redis 中仍然至关重要的一个地方是集群

Redis Cloud 入门:免费试用

什么是 Redis Cluster?

Redis Cluster 是 Redis 数据存储的一种分布式实现,它允许数据在多个 Redis 节点之间进行分片。在 Redis Cluster 中,数据被划分到多个 Redis 节点上,这样每个节点只持有一部分总数据集。这使得集群能够通过添加更多节点来水平扩展并处理增加的负载。在 Redis 中,数据在集群中存储在一个位置,每个节点或分片都拥有一部分键空间。集群被划分为 16,384 个槽位——这是 Redis 集群中节点或分片的最大数量。 当然,当运行高可用性时,您的数据可能存储在副本中,但单个键绝不会被分散到多个节点中。

由于大多数集群包含的节点数量要少得多,这些哈希槽是键的逻辑划分。在一个过于简化的 4 节点集群示例中,我们将有以下布局

槽位节点
0 – 4,095节点 #0
4,096 – 8,191节点 #1
8,192 – 12,287节点 #2
12,288 – 16,384节点 #3

注意:在 Redis Enterprise 中,每个节点会进一步划分为分片。其方式与此相同,但不是划分为节点,而是划分为分片。

例如,如果您知道一个键位于槽位 2,000,那么您就知道数据驻留在节点 #0 上。如果键位于槽位 9,000,那么它在节点 #2 上。实际上,这要复杂得多(槽位会不断移动和重新平衡),但为了理解事务和键,这种简化的集群概念性理解就足够了。

那么键与槽位是如何关联的呢?这些槽位实际上是哈希槽,每个键都通过一个哈希函数进行计算,从任意长度的字符串中数学推导出一个数字。哈希最常出现在关于密码哈希的公开讨论中,这是一个相关但复杂得多的计算。就像您的密码不是直接存储,而是其数学表示一样,您请求的键实际上可以归结为其数学表示(在本例中是使用 CRC16 哈希函数)。CRC16 将返回一个 14 位数字,然后我们可以对 16384 取模。有趣的是,这恰好是可用的哈希槽数量,不是吗?

这一切与 Redis 中的事务有何关联?

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

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

Redis 标签

虽然许多键可能位于同一个哈希槽中,但这从键命名角度来看是不可预测的,而且在命名键时不断检查槽位(使用开源版本的CLUSTER KEYSLOT 或 Enterprise Cluster API 模式)是不明智的。处理此问题的最佳方法是进行一些预先规划并使用一项称为标签的功能。在开源 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 请求中的键未哈希到同一个槽位 (command='HSET', key='user-session:1234') 在 'MULTI' 事务中
> EXPIRE user-session:1234 7200
(error) ERR CROSSSLOT 请求中的键未哈希到同一个槽位 (command='EXPIRE', key='user-session:1234') 在 'MULTI' 事务中
> EXEC
(error) EXECABORT 事务因先前错误而中止。

请记住,一些哈希槽和键问题不完全是事务,但在行为上有些相似——即操作多个键的单个命令。请看这个例子

> LPUSH my-list 1 2 3
(integer) 3
> RPOPLPUSH my-list my-new-list
(error) ERR CROSSSLOT 请求中的键未哈希到同一个槽位 (command='RPOPLPUSH', key='my-new-list')

RPOPLPUSH 是一个原子操作,它将一个元素从一个列表弹出并推入另一个列表。关键词是“原子”。如果这两个列表位于不同的哈希槽上(很像事务中的情况),您就会收到 CROSSSLOT 错误。开源 Redis 对此非常严格,任何操作多个哈希槽的命令都是禁止的。Redis Enterprise 对一些简单命令提供了一些变通方法,特别是 MGET 和 MSET。

Redis 集群最佳实践

如果您一直是 Redis 单实例的高级用户,迁移到集群可能会感觉有点奇怪。您依赖的一些命令和/或事务将不再适用于特定键,如果您运气不好,您设计键空间的方式可能会有问题。以下是一些关于如何设计您的应用程序以使其在集群上运行得最好的建议

  1. 思考键空间。键是否存在一个可以智能地划分您的工作负载的共同特征(按用户、按操作、按时间等)?使用标签或正则表达式智能地将键分配到哈希槽中。
  2. 避免将需要事务性操作的全局状态放在单个键下,否则很可能会遇到 CROSSSLOTS 错误。
  3. 评估您的 MULTI/EXEC 事务。看看您是否确实需要事务,或者流水线是否可行。别忘了考虑多键命令,以及它们是否可以用多个命令代替。

想了解更多?

观看我们最近关于“购买 vs 构建:开源 Redis 与 Redis Enterprise 中的集群与配置”的技术讲座!