Redis中的key到底是什么?Redis(或任何键值存储)的初衷是为每个单独的数据片段提供一个特定的键或标识符。Redis 迅速用数据类型扩展了这个概念,其中一个键可以引用多个(甚至数百万)条数据。随着模块进入生态系统,键的概念被进一步扩展,因为单个数据现在可以跨越多个键(例如,对于RediSearch索引)。因此,当被问及 Redis 是否是键值存储时,我通常会回答“它来自数据库的键值行”,但请注意,在这一点上,很难证明 Redis 单独作为键值存储是合理的。然而,Redis 中的键仍然至关重要的一个地方是聚类。
Redis Cloud 入门:免费试用 https://redis.com/try-free/
什么是 Redis 集群?
在 Redis 中,数据驻留在集群(Redis Cluster)中的一个地方,每个节点或分片都有一部分keyspace。一个集群被划分为 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 中的事务只发生在同一个哈希槽内,这确保了最高的吞吐量。由于不需要节点间/分片间通信,因此消除了许多故障场景。鉴于此,您必须确保当您外出进行交易时,所涉及的所有密钥都在同一个插槽中。那么,您如何知道您的密钥是否与事务中的另一个密钥位于同一插槽(以及同一节点/分片)上?
Redis 标签
虽然许多密钥可能位于同一个哈希槽中,但从密钥命名的角度来看,这是不可预测的,并且在命名密钥时不断检查槽(在开源中使用CLUSTER KEYSLOT或在集群 API 模式下使用 Enterprise)是不明智的。解决此问题的最佳方法是进行一些高级计划和称为 hashtags 的功能。在开源 Redis 中,花括号({ 和 })是主题标签的符号,这两个字符之间的字符串通过 CRC16 散列函数进行处理。让我们看几个例子:
| 钥匙 | 散列伪代码 | 哈希槽 |
| 用户资料:1234 | CRC16('user-profile:1234') mod 16384 | 15990 |
| 用户会话:1234 | CRC16('user-session:1234') mod 16384 | 2963 |
| 用户资料:5678 | CRC16('user-profile:5678') mod 16384 | 9487 |
| 用户会话:5678 | CRC16('user-session:5678') mod 16384 | 4330 |
| 用户个人资料:{1234} | CRC16('1234') 模型 16384 | 6025 |
| 用户会话:{1234} | CRC16('1234') 模型 16384 | 6025 |
| 用户配置文件:{5678} | CRC16('5678') 模型 16384 | 3312 |
| 用户配置文件:{5678} | CRC16('5678') 模型 16384 | 3312 |
鉴于这些示例,您可以看到 Redis 不允许通过密钥 user-session:1234和user-profile:1234进行交易,但允许使用user-profile:{1234}和user-session:{1234} 进行交易。
注意:你可能会想,“太好了,把所有东西都放在一个哈希槽下,我就不用担心集群事务了!” 你不会孤单,因为我不止一次听说过这个错误引导的情节。Redis 不会阻止您这样做或类似的事情,但您最终会得到一个不平衡的集群,或者更糟糕的是,一个完整节点和许多空节点。仅在需要时才使用主题标签,即便如此,也要谨慎使用。
Redis Enterprise 可以使用此策略,但它还添加了另一个功能以使分片更加透明。您可以使用正则表达式来定义要通过散列函数放置的键的特定部分,而不是使用花括号。使用正则表达式 /user-.*:(?<tag>.+)/让我们重新审视上面的一些示例:
| 钥匙 | 散列伪代码 | 哈希槽 |
| 用户资料:1234 | CRC16('1234') 模型 16384 | 6025 |
| 用户会话:1234 | CRC16('1234') 模型 16384 | 6025 |
| 用户资料:5678 | CRC16('5678') 模型 16384 | 3312 |
| 用户会话:5678 | CRC16('5678') 模型 16384 | 3312 |
这个正则表达式足够灵活,也可以处理以“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 Keys in request don't hash to the same slot (command='HSET', key='user-session:1234') within 'MULTI'
> EXPIRE user-session:1234 7200
(error) ERR CROSSSLOT Keys in request don't hash to the same slot (command='EXPIRE', key='user-session:1234') within 'MULTI'
> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
请记住,一些哈希槽和密钥问题并不完全是事务,但在行为上有些相似——对多个密钥进行操作的单个命令。举个例子:
> LPUSH my-list 1 2 3
(integer) 3
> RPOPLPUSH my-list my-new-list
(error) ERR CROSSSLOT Keys in request don't hash to the same slot (command='RPOPLPUSH', key='my-new-list')
RPOPLPUSH 是一种原子操作,它从一个列表中取出一个元素并将其推送到另一个列表中。操作词是原子的。如果这两个列表位于两个不同的哈希槽中(很像在事务中),您将收到 CROSSSLOT 错误。开源 Redis 对此非常严格,禁止任何操作多个哈希槽的命令。Redis Enterprise 有一些简单命令的变通方法,特别是 MGET 和 MSET。
Redis 集群最佳实践
如果您是单个 Redis 实例的高级用户,那么迁移到集群可能会感觉有点奇怪。您依赖的某些命令和/或事务将不再适用于特定键,如果您真的不走运,您设计键空间的方式可能会出现问题。以下是一些关于设计应用程序以使其在集群上工作得最好的一些技巧:
- 想想键空间。密钥是否有一个共同特征可以智能地划分您的工作量(按用户、按操作、按时间等)?使用主题标签或正则表达式将键巧妙地分配到散列槽中。
- 避免需要事务操作的单个键下的全局状态,否则,您可能会遇到 CROSSSLOTS 错误。
- 评估您的 MULTI/EXEC 交易。看看您是否真的需要交易,或者管道是否可以。不要忘记考虑多键命令以及它们是否可以被多个命令替换。
原文标题:Redis Clustering Best Practices with Multiple Keys
原文作者:Redis官方
原文链接:https://redis.com/blog/redis-clustering-best-practices-with-keys/




