散列标签

在默认情况下,Redis 将根据用户输入的整个键计算出该键所属的槽,然后将键存储到相应的槽中。但是在某些情况下,出于性能方面的考虑,或者为了在同一个节点上对多个相关联的键执行批量操作,我们也会想要将一些原本不属于同一个槽的键放到相同的槽里面。

为了满足这一需求,Redis 为用户提供了散列标签(hash tag)功能,该功能会找出键中第一个被大括号 {} 包围并且非空的字符串子串(sub string),然后根据子串计算出该键所属的槽。这样一来,即使两个键原本不属于同一个槽,但只要它们拥有相同的被包围子串,那么程序计算出的散列值就是一样的,因此 Redis 集群就会把它们存储到同一个槽中。

比如,假设我们现在有一批用户数据,它们全部以 user:: 为前缀,后跟用户 ID,其中键 user::10086user::10087 原本应该分别属于两个不同的槽:

--使用CLUSTER KEYSLOT命令查看给定键所属的槽
127.0.0.1:30002> CLUSTER KEYSLOT user::10086
(integer) 14982 --该键属于14982槽
127.0.0.1:30002> CLUSTER KEYSLOT user::10087
(integer) 10919 --该键属于10919槽

但如果我们对这两个键使用散列标签功能,即使用大括号去包围它们的 user 子串,让 Redis 集群只根据这一子串计算键的散列值,那么这两个键将被分配至同一个槽:

127.0.0.1:30002> CLUSTER KEYSLOT {user}::10086
(integer) 5474
127.0.0.1:30002> CLUSTER KEYSLOT {user}::10087
(integer) 5474

为了验证这一点,我们可以实际地对这两个键执行设置操作。在未使用散列标签功能时,对 user::10086 键的设置将被转向至节点 30003 所属的槽 14982 中,而对 user::10087 键的设置将被转向至节点 30002 所属的槽 10919 中:

127.0.0.1:30001> HMSET user::10086 name peter age 28 job programmer
-> Redirected to slot [14982] located at 127.0.0.1:30003
OK
127.0.0.1:30003> HMSET user::10087 name jack age 34 job writer
-> Redirected to slot [10919] located at 127.0.0.1:30002
O

但是在使用散列标签功能的情况下,针对 {user}::10086{user}::10087 两个键的设置操作将不会引发转向,这是因为它们都被放置到了节点 30002 所属的槽 5474 中:

127.0.0.1:30002> HMSET {user}::10086 name peter age 28 job programmer
OK
127.0.0.1:30002> HMSET {user}::10087 name jack age 34 job writer
OK

关于散列标签的使用,有以下两点需要注意。

  • 首先,虽然从逻辑上来讲,我们把 user::10086 和 {user}::10086 看作同一个键,但由于散列标签只是 Redis 集群对键名的一种特殊解释,因此这两个键在实际中并不相同,它们可以同时存在于数据库,并且不会引发任何键冲突。比如,如果我们对节点 30002 执行 KEYS* 命令,就会看到 user::10086 和 {user}::10086 同时存在于数据库中:

    127.0.0.1:30002> KEYS *
    1) "{user}::10086"
    2) "{user}::10087"
    3) "user::10087"
  • 其次,散列标签只会根据键名中第一个被大括号包围的非空子串来计算散列值,而键名中其他被包围子串或者被包围空串则会被忽略。比如,虽然以下 3 个键都包含多个被包围子串,但 Redis 集群只会根据它们的 user 子串来计算它们的散列值,因为这是它们第一个被包围的非空子串:

    • {user}::{10086}

    • {user}::10086::{profile}

    • {user}::{10086}::{profile}

    而以下这两个键则不会触发散列标签功能,因为它们的被包围子串都为空:

    • -{}user::10086

    • -user{}::10086