目录
- 1. Set 集合
- 1.1 Set介绍
- 1.2 常见命令
- 1.2.1 SADD命令
- 1.2.2 SMEMBERS命令
- 1.2.3 SISMEMBER命令
- 1.2.4 SCARD命令
- 1.2.5 SPOP命令
- 1.2.6 SMOVE命令
- 1.2.7 SREM命令
- 1.3 集合间操作
- 1.3.1 SINTER命令
- 1.3.2 SINTERSTORE命令
- 1.3.3 SUNION命令
- 1.3.4 SUNIONSTORE命令
- 1.3.5 SDIFF命令
- 1.3.6 SDIFFSTORE命令
- 1.4 命令小结
- 1.5 内部编码
- 1.6 使用场景
- 2. Zset 有序集合
- 2.1 Zset介绍
- 2.2 常见命令
- 2.2.1 ZADD命令
- 2.2.2 ZCARD命令
- 2.2.3 ZCOUNT命令
- 2.2.4 ZRANGE命令
- 2.2.5 ZREVRANGE命令
- 2.2.6 ZRANGEBYSCORE命令
- 2.2.7 ZPOPMAX命令
- 2.2.8 BZPOPMAX命令
- 2.2.9 ZPOPMIN命令
- 2.2.10 BZPOPMIN命令
- 2.2.11 ZRANK命令
- 2.2.12 ZREVRANK命令
- 2.2.13 ZSCORE命令
- 2.2.14 ZREM命令
- 2.2.15 ZREMRANGEBYRANK命令
- 2.2.16 ZREMRANGEBYSCORE命令
- 2.2.17 ZINCRBY命令
- 2.3 集合间操作
- 2.3.1 ZINTERSTORE命令
- 2.3.2 ZUNIONSTORE命令
- 2.4 命令小结
- 2.5 内部编码
- 2.6 使用场景
- 3. 渐进式遍历SCAN命令
- 4. 数据库管理
1. Set 集合
1.1 Set介绍
(1)集合类型也是保存多个字符串类型的元素的,但和列表类型不同的是,集合中1)元素之间是无序的2)元素不允许重复,如下图所示。一个集合中最多可以存储22 - 1个元素。Redis 除了支持集合内的增删查改操作,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多问题。
1.2 常见命令
1.2.1 SADD命令
(1)SADD 是 Redis 中用于向集合(Set)添加元素的命令。集合是一个无序的字符串集合,其中每个元素都是唯一的,即集合中不会有重复的元素。语法如下:
SADD key member [member ...]
- key:集合的键名。
- member:要添加到集合中的一个或多个元素。
- 返回值:整数。成功添加到集合的新元素数量。如果某个元素已经存在于集合中,则不会被再次添加,因此返回值可能会小于传入的元素数量。
(2)示例:
- 添加一个元素到集合
SADD myset "element1"
(integer) 1
- 添加多个元素到集合
SADD myset "element2" "element3"
(integer) 2
- 尝试添加已存在的元素
SADD myset "element1" "element4"
(integer) 1
在这里,element1 已经存在于集合中,所以只有 element4 被添加。
(3)注意事项:
- 集合中的元素是无序的,因此你不能保证元素存储的顺序。
- 集合中的元素是唯一的,因此重复添加同一个元素不会改变集合的内容。
- Redis 集合的大小受限于服务器的内存,但通常可以存储大量元素。
1.2.2 SMEMBERS命令
(1)SMEMBERS 是 Redis 中用于获取集合(Set)中所有成员的命令。以下是对 SMEMBERS 命令的详细解释:
SMEMBERS key
- key:集合的键名。
- 返回值:数组。包含集合中所有成员的列表。如果集合为空,则返回空数组。
(2)示例:
- 获取集合的所有成员
SMEMBERS myset
- 假设集合 myset 包含 “element1”, “element2”, “element3”,则返回:
1) "element1"
2) "element2"
3) "element3"
注意:返回的成员顺序可能与它们在集合中的实际存储顺序不同,因为 Redis 集合是无序的。
- 获取空集合的成员
SMEMBERS emptyset
- 如果集合 emptyset 为空,则返回:
(empty array)
(3)注意事项:
- SMEMBERS 命令返回的是一个列表,列表中的元素顺序是随机的,不保证与添加时的顺序一致。
- 如果集合中有重复的元素(实际上在 Redis 集合中这是不可能的,因为集合中的元素是唯一的),SMEMBERS 命令只会返回每个元素的一个副本。
- SMEMBERS 命令的时间复杂度为 O(N),其中 N 是集合中元素的数量。因此,对于包含大量元素的集合,使用 SMEMBERS 命令可能会有一定的性能开销。
1.2.3 SISMEMBER命令
(1)SISMEMBER 是 Redis 中用于检查一个元素是否是集合(Set)成员的命令。以下是对 SISMEMBER 命令的详细解释:
SISMEMBER key member
- key:集合的键名。
- member:要检查是否是集合成员的元素。
- 返回值:整数。如果元素是集合的成员,则返回 1;如果元素不是集合的成员,或集合的键不存在,则返回 0。
(2)示例:
- 检查元素是否是集合的成员
SISMEMBER myset "element1"
#假设集合 myset 包含 "element1",则返回:
(integer) 1
- 检查不存在的元素
SISMEMBER myset "nonexistent"
# 如果 "nonexistent" 不是集合 myset 的成员,则返回:
(integer) 0
- 检查不存在的集合
SISMEMBER nonexistentset "element1"
# 如果集合 nonexistentset 不存在,则返回:
(integer) 0
(3)注意事项:
- SISMEMBER 命令的时间复杂度为 O(1),因为 Redis 集合底层使用的是哈希表结构,所以检查元素是否存在是非常高效的。
- 如果集合中的元素数量非常大,使用 SISMEMBER 命令仍然能够保持较快的响应速度。
- 在使用 SISMEMBER 命令之前,需要确保集合已经被正确创建,并且包含了要检查的元素。可以使用 SADD 命令向集合中添加元素。
1.2.4 SCARD命令
(1)SCARD 是 Redis 中用于获取集合(Set)中元素数量的命令。以下是对 SCARD 命令的详细解释:
SCARD key
- key:集合的键名。
- 返回值:整数。集合中元素的数量。如果集合的键不存在,则返回 0。
(2)示例:
- 获取集合的元素数量
SCARD myset
# 假设集合 myset 包含 "element1", "element2", "element3",则返回:
(integer) 3
- 获取空集合的元素数量
SCARD emptyset
#如果集合 emptyset 为空,则返回:
(integer) 0
- 获取不存在的集合的元素数量
SCARD nonexistentset
# 如果集合 nonexistentset 不存在,则返回:
(integer) 0
(3)注意事项:
- SCARD 命令的时间复杂度通常为 O(1),因为 Redis 集合底层使用的是哈希表或整数集合(intset)等高效的数据结构来存储元素,所以获取元素数量是非常快的。
- 在使用 SCARD 命令之前,不需要对集合进行任何预处理或特殊操作,只需确保集合的键名是正确的。
- 如果集合中的元素数量非常大(例如,数百万或数千万个元素),SCARD 命令仍然能够保持较快的响应速度,因为 Redis 的集合数据结构被设计为能够高效地处理这种情况。
1.2.5 SPOP命令
(1)SPOP 是 Redis 中用于移除并返回集合(Set)中的一个或多个随机元素的命令。以下是对 SPOP 命令的详细解释:
SPOP key [count]
- key:集合的键名。
- count:(可选)要移除并返回的元素数量。在 Redis 3.2.0 及更高版本中可用。如果不指定此参数,则默认移除并返回一个元素。
- 返回值当不指定 count 参数时:
- 如果集合存在且不为空,则返回被移除的随机元素(字符串)。
- 如果集合不存在或为空,则返回 nil。
- 返回值当指定 count 参数时:
- 如果 count 的值大于集合的元素数量,则返回集合中的所有元素。
- 如果集合存在且不为空,则返回一个包含被移除的随机元素的数组。
- 如果集合不存在或为空,则返回空数组。
(2)示例:
- 移除并返回一个随机元素
SADD myset "one" "two" "three"
SPOP myset
假设集合 myset 包含 “one”, “two”, “three”,SPOP 命令可能返回 “one”, “two” 或 “three” 中的一个,并且该元素会从集合中被移除。例如,如果返回 “one”,则执行后集合 myset 将只包含 “two” 和 “three”。
- 移除并返回多个随机元素
SADD myset "four" "five" "six"
SPOP myset 3
假设集合 myset 现在包含 “two”, “three”, “four”, “five”, “six”(在添加了 “four”, “five”, “six” 之后),SPOP myset 3 命令可能返回包含三个随机元素的数组,例如 [“two”, “four”, “six”],并且这些元素会从集合中被移除。执行后集合 myset 将只包含剩余的元素。
(3)注意事项:
- SPOP 命令是原子性的,即执行该命令期间不会被其他命令打断。
- 如果指定的 count 值大于集合的元素数量,SPOP 命令会移除并返回集合中的所有元素。
- SPOP 命令不会保证返回元素的均匀分布,如果需要保证元素的均匀分布,可能需要使用其他算法或方法。
1.2.6 SMOVE命令
(1)Redis 的 SMOVE 命令用于将集合(Set)中的指定元素从一个集合移动到另一个集合。以下是关于 SMOVE 命令的详细解释:
SMOVE source destination member
- source:源集合的键名。
- destination:目标集合的键名。
- member:要从源集合移动到目标集合的元素。
- 返回值:整数。如果元素被成功移动,则返回 1;如果元素不是源集合的成员,并且没有进行任何操作,则返回 0。
(2)示例:
- 移动元素
SADD myset1 "one" "two" "three"
SADD myset2 "four"
SMOVE myset1 myset2 "two"
执行上述命令后,元素 “two” 会从 myset1 移动到 myset2。SMEMBERS myset1 将返回 [“one”, “three”],而 SMEMBERS myset2 将返回 [“four”, “two”]。
- 尝试移动不存在的元素
SMOVE myset1 myset2 "five"
如果 “five” 不是 myset1 的成员,则此命令将返回 0,并且 myset2 不会发生变化。
(3)注意事项:
- SMOVE 命令是原子性的,即执行该命令期间不会被其他命令打断。
- 如果源集合不存在或不包含指定的元素,SMOVE 命令不会执行任何操作,并返回 0。
- 如果目标集合已经包含要移动的元素,则 SMOVE 命令会从源集合中删除该元素,但不会向目标集合添加重复的元素(因为集合中的元素是唯一的)。
- 如果源集合或目标集合的键不是集合类型,SMOVE 命令将返回一个错误。
1.2.7 SREM命令
(1)Redis 的 SREM 命令用于从集合(Set)中移除一个或多个指定的成员元素。如果指定的成员元素在集合中不存在,则该命令会忽略这些不存在的元素。以下是关于 SREM 命令的详细解释:
SREM key member1 [member2 ...]
- key:集合的键名。
- member1 [member2 …]:一个或多个要从集合中移除的成员元素。
- 返回值:整数。被成功移除的元素的数量,不包括被忽略的不存在的元素。
(2)示例:
- 移除单个元素
SADD myset "one" "two" "three"
SREM myset "two"
执行上述命令后,元素 “two” 会从 myset 中被移除。SMEMBERS myset 将返回 [“one”, “three”]。
- 移除多个元素
SADD myset "four" "five"
SREM myset "four" "six"
执行上述命令后,元素 “four” 会从 myset 中被移除,而 “six” 因为不存在于 myset 中,所以会被忽略。SMEMBERS myset 将返回 [“one”, “three”, “five”](假设之前的 “one” 和 “three” 仍然存在)。
- 尝试移除不存在的集合中的元素
SREM nonexistentset "someelement"
如果 nonexistentset 集合不存在,则此命令将返回错误。
(3)注意事项:
- SREM 命令是原子性的,即执行该命令期间不会被其他命令打断。
- 如果指定的集合不存在,SREM 命令会返回一个错误。
- 如果指定的元素在集合中不存在,SREM 命令会忽略这些元素,并继续尝试移除其他存在的元素。
- SREM 命令的时间复杂度为 O(N),其中 N 是要移除的元素数量。然而,由于 Redis 集合底层使用的是哈希表等高效数据结构,所以即使元素数量很大,SREM 命令通常也能保持较快的响应速度。
1.3 集合间操作
(1)交集(inter) 、并集(union) 、差集(diff) 的概念如下图所示:
1.3.1 SINTER命令
(1)Redis 的 SINTER 命令用于返回两个或多个集合的交集。如果给定的集合中有一个是空集,那么结果也将是空集(根据集合运算定律)。以下是关于 SINTER 命令的详细解释:
SINTER key1 [key2 ...]
- key1 [key2 …]:一个或多个集合的键名。
- 返回值:数组。包含所有给定集合的交集成员的列表。如果集合不存在或为空,结果将是空数组。
(2)示例:
- 获取两个集合的交集
SADD myset1 "one" "two" "three"
SADD myset2 "two" "three" "four"
SINTER myset1 myset2
执行上述命令后,SINTER myset1 myset2 将返回 [“two”, “three”],因为 “two” 和 “three” 是 myset1 和 myset2 的交集元素。
- 获取多个集合的交集
SADD myset3 "three" "five"
SINTER myset1 myset2 myset3
执行上述命令后,SINTER myset1 myset2 myset3 将返回 [“three”],因为 “three” 是 myset1、myset2 和 myset3 的唯一交集元素。
- 处理空集合或不存在的集合
SINTER myset1 nonexistentset
如果 nonexistentset 集合不存在,则 SINTER myset1 nonexistentset 将返回空数组,因为不存在的集合被视为空集。
(3)注意事项:
- SINTER 命令的时间复杂度取决于集合中元素数量的最小值(O(N),其中 N 是最小集合的元素数量)。然而,由于 Redis 集合底层使用的是哈希表等高效数据结构,所以即使元素数量很大,SINTER 命令通常也能保持较快的响应速度。
- 如果需要处理大量数据或优化性能,可以考虑使用分片处理、并行处理或优化查询条件等策略。
1.3.2 SINTERSTORE命令
(1)Redis 的 SINTERSTORE 命令与 SINTER 命令类似,但它不是返回交集的结果,而是将交集的结果存储到一个新的集合中。以下是关于 SINTERSTORE 命令的详细解释:
SINTERSTORE destination key1 [key2 ...]
- destination:存储交集结果的新集合的键名。
- key1 [key2 …]:一个或多个要计算交集的集合的键名。
- 返回值:整数。存储在 destination 集合中的交集元素的数量。
(2)示例:
- 存储两个集合的交集
SADD myset1 "one" "two" "three"
SADD myset2 "two" "three" "four"
SINTERSTORE myset_intersection myset1 myset2
执行上述命令后,myset_intersection 将包含 [“two”, “three”],这是 myset1 和 myset2 的交集元素。同时,SINTERSTORE 命令返回 2,表示交集中有两个元素。
- 存储多个集合的交集
SADD myset3 "three" "five"
SINTERSTORE myset_intersection_all myset1 myset2 myset3
执行上述命令后,myset_intersection_all 将包含 [“three”],这是 myset1、myset2 和 myset3 的交集元素。SINTERSTORE 命令返回 1,表示交集中有一个元素。
- 处理空集合或不存在的集合
SINTERSTORE myset_intersection myset1 nonexistentset
如果 nonexistentset 集合不存在,则 myset_intersection 将是一个空集合,因为不存在的集合被视为空集。SINTERSTORE 命令返回 0,表示交集中没有元素。
(3)注意事项:
- 如果 destination 集合已经存在,SINTERSTORE 命令会将其覆盖,并存储新的交集结果。
- SINTERSTORE 命令的时间复杂度取决于集合中元素数量的最小值(O(N),其中 N 是最小集合的元素数量)。然而,由于 Redis 集合底层使用的是哈希表等高效数据结构,所以即使元素数量很大,SINTERSTORE 命令通常也能保持较快的响应速度。
- 在使用 SINTERSTORE 命令时,请确保 destination 键名不与其他重要的键名冲突,以避免数据丢失或覆盖。
1.3.3 SUNION命令
(1)Redis 的 SUNION 命令用于返回两个或多个集合的并集。不存在的集合 key 被视为空集。以下是关于 SUNION 命令的详细解释:
SUNION key1 [key2 ...]
- key1 [key2 …]:一个或多个集合的键名。
- 返回值:数组。包含所有给定集合的并集成员的列表。
(2)示例:
- 获取两个集合的并集
SADD myset1 "apple" "banana" "cherry"
SADD myset2 "banana" "date" "elderberry"
SUNION myset1 myset2
执行上述命令后,SUNION myset1 myset2 将返回 [“apple”, “banana”, “cherry”, “date”, “elderberry”],这是 myset1 和 myset2 的并集元素。
- 获取多个集合的并集
SADD myset3 "fig" "grape"
SUNION myset1 myset2 myset3
执行上述命令后,SUNION myset1 myset2 myset3 将返回 [“apple”, “banana”, “cherry”, “date”, “elderberry”, “fig”, “grape”],这是 myset1、myset2 和 myset3 的并集元素。
- 处理不存在的集合
SUNION myset1 nonexistentset
如果 nonexistentset 集合不存在,则 SUNION myset1 nonexistentset 将返回 myset1 的所有元素,因为不存在的集合被视为空集,对并集结果没有影响。
(3)注意事项:
- SUNION 命令不会改变传入的集合,它只是返回一个新的集合,包含了所有给定集合的成员。
- SUNION 命令的时间复杂度取决于所有给定集合中元素数量的总和(O(N),其中 N 是所有集合元素数量的总和)。然而,由于 Redis 集合底层使用的是哈希表等高效数据结构,所以即使元素数量很大,SUNION 命令通常也能保持较快的响应速度。
1.3.4 SUNIONSTORE命令
(1)Redis 的 SUNIONSTORE 命令与 SUNION 命令类似,但它不是返回并集的结果,而是将并集的结果存储到一个新的集合中。以下是关于 SUNIONSTORE 命令的详细解释:
SUNIONSTORE destination key1 [key2 ...]
- destination:存储并集结果的新集合的键名。
- key1 [key2 …]:一个或多个要计算并集的集合的键名。
- 返回值:整数。存储在 destination 集合中的并集元素的数量。
(2)示例:
- 存储两个集合的并集
SADD myset1 "apple" "banana" "cherry"
SADD myset2 "banana" "date" "elderberry"
SUNIONSTORE myset_union myset1 myset2
执行上述命令后,myset_union 将包含 [“apple”, “banana”, “cherry”, “date”, “elderberry”],这是 myset1 和 myset2 的并集元素。同时,SUNIONSTORE 命令返回 5,表示并集中有五个元素。
- 存储多个集合的并集
SADD myset3 "fig" "grape"
SUNIONSTORE myset_union_all myset1 myset2 myset3
执行上述命令后,myset_union_all 将包含 [“apple”, “banana”, “cherry”, “date”, “elderberry”, “fig”, “grape”],这是 myset1、myset2 和 myset3 的并集元素。SUNIONSTORE 命令返回 7,表示并集中有七个元素。
- 处理不存在的集合
SUNIONSTORE myset_union myset1 nonexistentset
如果 nonexistentset 集合不存在,则 myset_union 将包含 myset1 的所有元素以及一个空集(实际上空集对并集结果没有影响),因此 myset_union 的内容将与 myset1 相同。SUNIONSTORE 命令返回的元素数量将等于 myset1 中的元素数量。
(3)注意事项:
- 如果 destination 集合已经存在,SUNIONSTORE 命令会将其覆盖,并存储新的并集结果。
- SUNIONSTORE 命令的时间复杂度取决于所有给定集合中元素数量的总和(O(N),其中 N 是所有集合元素数量的总和)。然而,由于 Redis 集合底层使用的是哈希表等高效数据结构,所以即使元素数量很大,SUNIONSTORE 命令通常也能保持较快的响应速度。
- 在使用 SUNIONSTORE 命令时,请确保 destination 键名不与其他重要的键名冲突,以避免数据丢失或覆盖。
1.3.5 SDIFF命令
(1)Redis 的 SDIFF 命令用于返回两个或多个集合之间的差集。差集是指第一个集合中存在,但在其他集合中不存在的元素。以下是关于 SDIFF 命令的详细解释:
SDIFF key1 [key2 ...]
- key1:第一个集合的键名。
- [key2 …]:一个或多个要与第一个集合计算差集的集合的键名。
- 返回值:数组。包含差集成员的列表,即只存在于第一个集合中但不在其他集合中的元素。
(2)示例:
- 计算两个集合的差集
SADD myset1 "apple" "banana" "cherry"
SADD myset2 "banana" "date"
SDIFF myset1 myset2
执行上述命令后,SDIFF myset1 myset2 将返回 [“apple”, “cherry”],因为这两个元素只存在于 myset1 中,而不在 myset2 中。
- 计算多个集合的差集
SADD myset3 "cherry" "fig"
SDIFF myset1 myset2 myset3
执行上述命令后,SDIFF myset1 myset2 myset3 将返回 [“apple”]。这里需要注意的是,差集的计算是相对于第一个集合 myset1 的,即返回在 myset1 中但不在 myset2 和 myset3 中的元素。
- 处理不存在的集合
SDIFF myset1 nonexistentset
如果 nonexistentset 集合不存在,则 SDIFF myset1 nonexistentset 将返回 myset1 的所有元素,因为不存在的集合被视为空集,对差集结果没有影响(相当于从第一个集合中减去一个空集)。
(3)注意事项:
- SDIFF 命令的时间复杂度取决于第一个集合的元素数量以及后续集合的查找次数(大致为 O(N),其中 N 是第一个集合的元素数量)。然而,由于 Redis 集合底层使用的是哈希表等高效数据结构,所以即使元素数量很大,SDIFF 命令通常也能保持较快的响应速度。
- SDIFF 命令不会改变传入的集合,它只是返回一个新的集合,包含了差集成员。
1.3.6 SDIFFSTORE命令
(1)Redis 的 SDIFFSTORE 命令用于将两个或多个集合之间的差集存储到一个新的集合中。差集是指第一个集合中存在,但在其他集合中不存在的元素。以下是关于 SDIFFSTORE 命令的详细解释:
SDIFFSTORE destination key1 [key2 ...]
- destination:要存储差集结果的新集合的键名。
- key1:第一个集合的键名。
- [key2 …]:一个或多个要与第一个集合计算差集的集合的键名。
- 返回值:整数。存储在 destination 集合中的差集元素的数量。
(2)示例:
- 存储两个集合的差集
SADD myset1 "apple" "banana" "cherry"
SADD myset2 "banana" "date"
SDIFFSTORE myset_diff myset1 myset2
执行上述命令后,myset_diff 将包含 [“apple”, “cherry”],这是 myset1 和 myset2 的差集元素,即只存在于 myset1 中但不在 myset2 中的元素。同时,SDIFFSTORE 命令返回 2,表示差集中有两个元素。
- 存储多个集合的差集
SADD myset3 "cherry" "fig"
SDIFFSTORE myset_diff_all myset1 myset2 myset3
执行上述命令后,myset_diff_all 将包含 [“apple”]。这里需要注意的是,差集的计算是相对于第一个集合 myset1 的,即返回在 myset1 中但不在 myset2 和 myset3 中的元素。SDIFFSTORE 命令返回 1,表示差集中有一个元素。
- 处理不存在的集合
SDIFFSTORE myset_diff myset1 nonexistentset
如果 nonexistentset 集合不存在,则 myset_diff 将包含 myset1 的所有元素,因为不存在的集合被视为空集,对差集结果没有影响(相当于从第一个集合中减去一个空集)。SDIFFSTORE 命令返回的元素数量将等于 myset1 中的元素数量。
(3)注意事项:
- 如果 destination 集合已经存在,SDIFFSTORE 命令会将其覆盖,并存储新的差集结果。
- SDIFFSTORE 命令的时间复杂度取决于第一个集合的元素数量以及后续集合的查找次数(大致为 O(N),其中 N 是所有集合元素数量的总和)。然而,由于 Redis 集合底层使用的是哈希表等高效数据结构,所以即使元素数量很大,SDIFFSTORE 命令通常也能保持较快的响应速度。
- 在使用 SDIFFSTORE 命令时,请确保 destination 键名不与其他重要的键名冲突,以避免数据丢失或覆盖。
1.4 命令小结
(1)下表总结了集合类型的常见命令,开发人员可以根据自身需求进行选择:
1.5 内部编码
(1)集合类型的内部编码有两种:
- intset (整数集合) :当集合中的元素都是整数并且元素的个数小于set-max- intset-entries配置(默认512个)时,Redis 会选用intset来作为集合的内部实现,从而减少内存的使用。
- hashtable (哈希表) :当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
(2)当元素个数较少并且都为整数时,内部编码为intset:
127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 4
127.0.0.1:6379> object encoding setkey
"intset"
(3)当元素个数超过512个,内部编码为hashtable:
127.0.0.1:6379> sadd setkey 1 2 3 4
(integer) 513
127.0.0.1:6379> object encoding setkey
"hashtable"
(4)当存在元素不是整数时,内部编码为hashtable:
127.0.0.1:6379> sadd setkey a
(integer) 1
127.0.0.1:6379> object encoding setkey
"hashtable"
1.6 使用场景
(1)集合类型比较典型的使用场景是标签(tag) 。例如A用户对娱乐、体育板块比较感兴趣,B用户对历史、新闻比较感兴趣,这些兴趣点可以被抽象为标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于增强用户体验和用户黏度都非常有帮助。例如一个电子商务网站会对不同标签的用户做不同的产品推荐。下面的演示通过集合类型来实现标签的若干功能。
- 给用户添加标签:
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
- 给标签添加用户:
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:4 user:9 user:28
- 删除用户下的标签:
srem user:1:tags tag1 tag5
...
- 删除标签下的用户:
srem tag1:users user:1
srem tag5:users user:1
...
- 计算用户的共同兴趣标签:
sinter user:1:tags user:2:tags
2. Zset 有序集合
2.1 Zset介绍
(1)有序集合相对于字符串、列表、哈希、集合来说会有一-些陌生。它保留了集合不能有重复成员的特点,但与集合不同的是,有序集合中的每个元素都有-个唯- -的浮 点类型的分数(score) 与之关联,着使得有序集合中的元素是可以维护有序性的,但这个有序不是用下标作为排序依据而是用这个分数。如下图示,该有序集合显示了三国中的武将的武力。
(2)有序集合提供了获取指定分数和元素范围查找、计算成员排名等功能,合理地利用有序集合,可以帮助我们在实际开发中解决很多问题。有序集合中的元素是不能重复的,但分数允许重复。类比于一次考试之后,每个人一定有一个唯一的分数,但分数允许相同。下表是列表、集合、有序集合三者的异同点。
2.2 常见命令
2.2.1 ZADD命令
(1)ZADD 是 Redis 中用于向有序集合(sorted set)添加成员或更新成员分数的命令。有序集合中的每个成员都有一个与之关联的分数(score),Redis 根据这些分数自动对成员进行排序。语法如下:
ZADD key score1 member1 [score2 member2 ...]
- key:有序集合的键名。
- score1 member1:第一个成员的分数和成员值。
- [score2 member2 …]:可选的,其他成员的分数和成员值。
- 返回值:成功添加或更新成员后,返回添加或更新的成员总数。
(2)示例:
- 添加单个成员
ZADD myzset 1 "one"
这将向有序集合 myzset 中添加一个成员 “one”,其分数为 1。
- 添加多个成员
ZADD myzset 2 "two" 3 "three"
这将向有序集合 myzset 中添加两个成员 “two” 和 “three”,它们的分数分别为 2 和 3。
- 更新成员分数。如果成员已经存在于有序集合中,ZADD 会更新该成员的分数。
ZADD myzset 10 "one"
这将把成员 “one” 的分数更新为 10。
(3)注意事项:
- 分数可以是任何双精度浮点数。
- 成员在有序集合中是唯一的,但如果有多个成员具有相同的分数,它们会根据字典顺序进行排序。
- 使用 ZRANGE、ZREVRANGE 等命令可以查询有序集合中的成员及其分数。
2.2.2 ZCARD命令
(1)Redis 的 ZCARD 命令用于计算有序集合(sorted set)中元素的数量。以下是对 ZCARD 命令的详细解释:
ZCARD key
- key:有序集合的键名。
- 返回值:当键存在且是有序集类型时,返回有序集的基数(即元素数量)。当键不存在时,返回 0。
(2)示例:
- 计算有序集合的元素数量。假设有一个有序集合 myzset,其中包含了几个成员和它们的分数。可以使用 ZCARD 命令来计算这个有序集合中的元素数量。
ZCARD myzset
如果 myzset 中有 3 个元素,那么命令将返回 3。
(3)注意事项:
- ZCARD 命令只能用于有序集合类型的键。如果尝试将其用于其他类型的键(如字符串、列表、集合、哈希表等),将会导致错误。
- ZCARD 命令的时间复杂度为 O(1),这意味着在大多数情况下,它的性能是很好的。然而,如果有序集合中的元素数量非常大,虽然计算元素数量本身仍然是 O(1) 的操作,但整体系统性能可能会受到其他因素的影响(如内存使用、CPU 负载等)。
- 在并发场景下,ZCARD 命令是安全的,因为 Redis 的大部分命令都是原子性的。这意味着多个客户端可以同时执行 ZCARD 命令而不会相互干扰。
2.2.3 ZCOUNT命令
(1)Redis 的 ZCOUNT 命令用于计算有序集合(sorted set)中指定分数区间的成员数量。以下是对 ZCOUNT 命令的详细解释:
ZCOUNT key min max
- key:有序集合的键名。
- min:分数的最小值(包含)。
- max:分数的最大值(包含)。
- 返回值:返回分数在 min 和 max 之间的成员数量。
(2)示例:
- 计算分数区间内的成员数量。假设有一个有序集合 myzset,其中包含了几个成员和它们的分数:
ZADD myzset 1 "one" 2 "two" 3 "three" 4 "four" 5 "five"
- 现在,我们想要计算分数在 2 到 4 之间的成员数量,可以使用 ZCOUNT 命令:
ZCOUNT myzset 2 4
这将返回 3,因为有三个成员的分数在 2 到 4 之间,即 “two”、“three” 和 “four”。
(3)注意事项:
- ZCOUNT 命令的时间复杂度为 O(log N + M),其中 N 是有序集合的元素数量,M 是分数区间内的元素数量。由于 Redis 使用了高效的数据结构(如跳表)来实现有序集合,因此 ZCOUNT 命令的性能通常很好。
- 分数区间是包含边界值的,即 min 和 max 指定的分数值也会被计算在内。
- 如果 key 不存在,或者 key 不是有序集合类型,ZCOUNT 命令将返回 0。
2.2.4 ZRANGE命令
(1)Redis 的 ZRANGE 命令用于根据成员在有序集合(sorted set)中的分数(score)从低到高返回指定区间的成员及其分数。以下是对 ZRANGE 命令的详细解释:
ZRANGE key start stop [WITHSCORES]
- key:有序集合的键名。
- start:返回区间的起始索引(包含)。索引从 0 开始。
- stop:返回区间的结束索引(包含)。索引可以是 -1,表示最后一个成员。
- [WITHSCORES]:可选参数。如果指定,返回的成员将包括它们的分数。
- 返回值:返回一个列表,包含指定区间内的成员。如果指定了 WITHSCORES,则每个成员后面都跟着它的分数。
(2)示例:
- 获取有序集合中的成员。假设有一个有序集合 myzset,其中包含了几个成员和它们的分数:
ZADD myzset 1 "one" 2 "two" 3 "three" 4 "four" 5 "five"
- 现在,我们想要获取索引从 0 到 2(即前三个)的成员,可以使用 ZRANGE 命令:
ZRANGE myzset 0 2
# 这将返回:
1) "one"
2) "two"
3) "three"
- 获取有序集合中的成员及其分数。如果我们还想要获取这些成员的分数,可以使用 WITHSCORES 参数:
ZRANGE myzset 0 2 WITHSCORES
# 这将返回:
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
(3)注意事项:
- ZRANGE 命令的时间复杂度为 O(log N + M),其中 N 是有序集合的元素数量,M 是返回的成员数量。由于 Redis 使用了高效的数据结构(如跳表)来实现有序集合,因此 ZRANGE 命令的性能通常很好。
- 索引是从 0 开始的,并且可以是负数。例如,-1 表示最后一个成员,-2 表示倒数第二个成员,依此类推。
- 如果 key 不存在,或者 key 不是有序集合类型,ZRANGE 命令将返回一个空列表。
2.2.5 ZREVRANGE命令
(1)Redis的ZREVRANGE命令用于返回有序集合(sorted set)中指定区间内的成员,但成员的位置是按分数值递减(从大到小)来排列的。以下是对ZREVRANGE命令的详细解释:
ZREVRANGE key start stop [WITHSCORES]
- key:有序集合的键名。
- start:返回区间的起始索引(包含)。索引从0开始,也可以为负数,表示从有序集合的尾部开始计算索引。
- stop:返回区间的结束索引(不包含)。索引可以是-1,表示最后一个成员之前的位置(实际上不会返回该成员)。
- [WITHSCORES]:可选参数。如果指定,返回的每个成员后面都会跟着它的分数。
- 返回值:返回一个列表,包含指定区间内的成员。如果指定了WITHSCORES,则每个成员后面都跟着它的分数。
(2)示例:
- 获取有序集合中的成员(降序)。假设有一个有序集合salary,其中包含了几个成员和它们的分数:
ZADD salary 3500 "peter" 4000 "tom" 5000 "jack"
- 现在,我们想要获取所有成员,但要求按分数从高到低排列,可以使用ZREVRANGE命令:
ZREVRANGE salary 0 -1
# 这将返回:
1) "jack"
2) "tom"
3) "peter"
- 获取有序集合中的成员及其分数(降序)。如果我们还想要获取这些成员的分数,可以使用WITHSCORES参数:
ZREVRANGE salary 0 -1 WITHSCORES
# 这将返回:
1) "jack"
2) "5000"
3) "tom"
4) "4000"
5) "peter"
6) "3500"
(3)注意事项:
- ZREVRANGE命令的时间复杂度为O(log N + M),其中N是有序集合的元素数量,M是返回的成员数量。由于Redis使用了高效的数据结构(如跳表)来实现有序集合,因此ZREVRANGE命令的性能通常很好。
- 如果key不存在,或者key不是有序集合类型,ZREVRANGE命令将返回一个空列表。
- 索引是从0开始的,也可以是负数。例如,-1表示最后一个成员,-2表示倒数第二个成员,依此类推。
2.2.6 ZRANGEBYSCORE命令
(1)Redis 的 ZRANGEBYSCORE 命令用于根据分数范围返回有序集合(sorted set)中的成员。以下是对 ZRANGEBYSCORE 命令的详细解释:
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
- key:有序集合的键名。
- min 和 max:分数范围的最小值和最大值。成员分数必须在 min 和 max 之间(包括 min 和 max,除非使用了开区间符号)。
- [WITHSCORES]:可选参数。如果指定,返回的每个成员后面都会跟着它的分数。
- [LIMIT offset count]:可选参数。用于分页,offset 是结果集的起始索引(从 0 开始),count 是要返回的结果数量。
- 返回值:返回一个列表,包含指定分数范围内的成员。如果指定了 WITHSCORES,则每个成员后面都跟着它的分数。
(2)示例:
- 获取分数范围内的成员。假设有一个有序集合 salary,其中包含了几个成员和它们的分数:
ZADD salary 2500 "jack" 5000 "tom" 12000 "peter"
- 现在,我们想要获取分数在 2500 到 5000 之间(包含 2500 和 5000)的成员,可以使用 ZRANGEBYSCORE 命令:
ZRANGEBYSCORE salary 2500 5000
# 这将返回:
1) "jack"
2) "tom"
- 获取分数范围内的成员及其分数。如果我们还想要获取这些成员的分数,可以使用 WITHSCORES 参数:
ZRANGEBYSCORE salary 2500 5000 WITHSCORES
# 这将返回:
1) "jack"
2) "2500"
3) "tom"
4) "5000"
- 分页获取分数范围内的成员。假设我们想要分页获取分数在 0 到 +inf(正无穷大)之间的成员,每页显示 1 个,第二页的数据可以使用 LIMIT 参数:
ZRANGEBYSCORE salary 0 +inf LIMIT 1 1
这将返回第二页的第一个成员(注意索引是从 0 开始的,所以 LIMIT 1 1 表示跳过第一个成员,返回第二个成员,但由于这里只有两个成员且没有明确的顺序保证除非按分数排序,所以实际结果可能取决于集合中的成员数量和分数分布):
1) "tom"(或 "jack",取决于集合中成员的具体数量和分数,以及是否有其他未显示的成员)
但请注意,上面的分页示例在只有少量成员时可能不够直观。在实际情况中,分页通常用于处理大量数据,并且会结合具体的业务逻辑来确定 offset 和 count 的值。
(3)注意事项:
- 默认情况下,分数区间是闭区间,即包含 min 和 max。如果希望使用开区间,可以在 min 或 max 前加上 ( 符号。例如,ZRANGEBYSCORE zset (1 5 返回所有符合条件 1 < score <= 5 的成员。
- 分数可以是整数或浮点数。
- 如果 key 不存在,或者 key 不是有序集合类型,ZRANGEBYSCORE 命令将返回一个空列表。
- ZRANGEBYSCORE 命令的时间复杂度为 O(log N + M),其中 N 是有序集合的元素数量,M 是返回的成员数量。
2.2.7 ZPOPMAX命令
(1)Redis的ZPOPMAX命令用于移除并返回有序集合(sorted set)中分值最高的成员。以下是关于ZPOPMAX命令的详细解释:
ZPOPMAX key [count]
- key:有序集合的键名。
- [count]:可选参数,用于指定要移除并返回的成员数量。如果不指定,默认只移除并返回一个成员。
- 返回值:返回一个数组,包含被移除成员的成员名和分值。数组的第一个元素是成员名,第二个元素是该成员的分值。如果指定了count参数,则会返回多个这样的成员-分值对。
(2)示例:
- 假设有一个有序集合salary,存储了员工的薪水信息:
ZADD salary 5500 "mary" 3500 "peter" 4000 "tom"
此时,salary有序集合中的成员按分值从高到低排列为mary、tom、peter。
- 移除并返回分值最高的一个成员
ZPOPMAX salary
# 返回结果可能是:
1) "mary"
2) "5500"
表示移除了分值最高的成员mary,其分值为5500。
- 移除并返回分值最高的多个成员
ZPOPMAX salary 2
# 返回结果可能是:
1) "tom"
2) "4000"
3) "peter"
4) "3500"
表示移除了分值最高的两个成员tom和peter,以及它们各自的分值。注意,实际返回的成员顺序可能因Redis版本或实现细节而有所不同,但分值最高的成员肯定会被移除。
(3)注意事项:
- ZPOPMAX命令会修改原始有序集合,即移除指定的成员。
- 如果key不存在或key不是有序集合类型,ZPOPMAX命令将返回一个空数组。
- ZPOPMAX命令的时间复杂度为O(N),其中N为命令移除的元素数量。当count参数较大时,性能可能会受到影响。
2.2.8 BZPOPMAX命令
(1)Redis 的 BZPOPMAX 命令是 ZPOPMAX 命令的阻塞版本。它用于从有序集合(sorted set)中弹出分值最高的成员,如果该有序集合在指定时间内没有足够的元素可供弹出,则命令会阻塞直到超时或有元素可以弹出为止。语法如下:
BZPOPMAX key [key ...] timeout
- key:一个或多个有序集合的键名。Redis 会按照提供的键的顺序检查每个有序集合,并在找到有足够元素可以弹出的集合时停止检查。
- timeout:阻塞的超时时间(以秒为单位)。如果指定的时间内没有任何有序集合有元素可以弹出,则命令返回 nil。
- 返回值:返回一个数组,数组的第一个元素是被弹出成员所在的有序集合的键名,第二个元素是被弹出的成员名,第三个元素是该成员的分值。
如果在超时时间内没有任何有序集合有元素可以弹出,则返回 nil。
(2)示例:
- 假设有两个有序集合 task_queue_1 和 task_queue_2,分别存储了不同任务的优先级信息:
ZADD task_queue_1 10 "task1_1" 20 "task1_2"
ZADD task_queue_2 5 "task2_1" 15 "task2_2"
此时,task_queue_1 中的任务按分值从高到低排列为 task1_2、task1_1,task_queue_2 中的任务按分值从高到低排列为 task2_2、task2_1。
- 使用 BZPOPMAX 命令从这两个有序集合中弹出分值最高的任务,并设置超时时间为 10 秒:
BZPOPMAX task_queue_1 task_queue_2 10
# 假设在命令执行时 task_queue_2 的 task2_2 是所有任务中分值最高的,那么返回结果可能是:
1) "task_queue_2"
2) "task2_2"
3) "15"
表示从 task_queue_2 中弹出了分值最高的任务 task2_2,其分值为 15。如果在超时时间内没有任何有序集合有元素可以弹出,则返回 nil。
(3)注意事项:
- BZPOPMAX 命令是阻塞命令,因此在执行期间会占用一个 Redis 连接。
- 如果提供了多个键,Redis 会按照提供的顺序依次检查每个键对应的有序集合,直到找到有足够元素可以弹出的集合为止。
- 如果所有提供的键对应的有序集合在超时时间内都没有元素可以弹出,则命令返回 nil。
- BZPOPMAX 命令的时间复杂度为 O(1) 加上移除元素所需的时间(通常很快),但在阻塞期间可能会消耗额外的系统资源。
2.2.9 ZPOPMIN命令
(1)Redis的ZPOPMIN命令用于移除并返回有序集合(sorted set)中分值最低的成员。这个命令在Redis 5.0.0版本及以后可用,是处理有序集合中元素的一个重要工具。语法如下:
ZPOPMIN key [count]
- key:有序集合的键名。
- [count]:可选参数,用于指定要移除并返回的成员数量。如果不指定,默认只移除并返回一个成员。
- 返回值:返回一个数组,数组的第一个元素是被移除的成员名,第二个元素是该成员的分值。如果指定了count参数,则会返回多个这样的成员-分值对。
(2)示例:
- 假设有一个有序集合scores,存储了学生的分数信息:
ZADD scores 85 "Alice" 90 "Bob" 78 "Charlie"
此时,scores有序集合中的成员按分值从低到高排列为Charlie、Alice、Bob。
- 移除并返回分值最低的一个成员
ZPOPMIN scores
# 返回结果可能是:
1) "Charlie"
2) "78"
表示移除了分值最低的成员Charlie,其分值为78。
- 移除并返回分值最低的多个成员
ZPOPMIN scores 2
# 返回结果可能是:
1) "Alice"
2) "85"
3) "Charlie"
4) "78"
注意:实际返回的成员顺序可能因Redis的具体实现而有所不同,但分值最低的成员肯定会被移除。
这个命令移除了分值最低的两个成员Alice和Charlie(或按实际排序后的最低两个成员),并返回了它们的名称和分值。
(3)注意事项:
- ZPOPMIN命令会修改原始有序集合,即移除指定的成员。
- 如果key不存在或key不是有序集合类型,ZPOPMIN命令将返回一个空数组。
- ZPOPMIN命令的时间复杂度为O(N),其中N为命令移除的元素数量。当count参数较大时,性能可能会受到影响。
2.2.10 BZPOPMIN命令
(1)Redis的BZPOPMIN命令是ZPOPMIN命令的阻塞版本,用于从有序集合(sorted set)中弹出分值最低的成员。如果在指定的有序集合中没有元素可以弹出,那么该命令会阻塞客户端连接,直到有元素可以弹出或者达到指定的超时时间为止。语法如下:
BZPOPMIN key [key ...] timeout
- key:一个或多个有序集合的键名。Redis会按照键的顺序依次检查每个有序集合,并在找到第一个非空集合时弹出其中分值最低的成员。
- timeout:阻塞的超时时间(以秒为单位)。如果在这个时间内没有任何有序集合有元素可以弹出,那么命令会返回nil。
- 返回值:返回一个数组,包含三个元素:被弹出元素所在的有序集合的键名、被弹出元素的成员名、以及被弹出元素的分值。
如果在超时时间内没有任何有序集合有元素可以弹出,则返回nil。
(2)示例:
- 假设有两个有序集合queue1和queue2,分别存储了不同的任务,每个任务都有一个分值表示其优先级(分值越低,优先级越高):
ZADD queue1 1 "task1_1" 3 "task1_2"
ZADD queue2 2 "task2_1" 4 "task2_2"
此时,queue1中的任务按分值从低到高排列为task1_1、task1_2,queue2中的任务按分值从低到高排列为task2_1、task2_2。
- 使用BZPOPMIN命令从这两个有序集合中弹出分值最低的任务,并设置超时时间为10秒:
BZPOPMIN queue1 queue2 10
# 假设在命令执行时queue1的task1_1是所有任务中分值最低的,那么返回结果可能是:
1) "queue1"
2) "task1_1"
3) "1"
表示从queue1中弹出了分值最低的任务task1_1,其分值为1。
(3)注意事项:
- BZPOPMIN命令是阻塞命令,因此在执行期间会占用一个Redis连接。
- 如果提供了多个键,Redis会按照提供的顺序依次检查每个键对应的有序集合,直到找到第一个非空集合并弹出其中分值最低的成员为止。
- 如果所有提供的键对应的有序集合在超时时间内都没有元素可以弹出,那么命令会返回nil。
- BZPOPMIN命令的时间复杂度为O(1)(用于找到第一个非空集合)加上移除元素所需的时间(通常很快),但在阻塞期间可能会消耗额外的系统资源。
2.2.11 ZRANK命令
(1)Redis的ZRANK命令用于返回有序集合(sorted set)中指定成员的排名。这里的有序集合成员是按分数值递增(从小到大)顺序排列的。语法如下:
ZRANK key member
- key:有序集合的键名。
- member:要查询排名的有序集合成员。
- 返回值:如果成员是有序集合key的成员,则返回该成员的排名(索引)。排名是从0开始的,即分数最低的成员排名为0。如果成员不是有序集合key的成员,则返回nil。
(2)示例:
- 假设有一个有序集合salary,存储了员工的薪水信息:
ZADD salary 3500 "peter" 4000 "tom" 5000 "jack"
此时,salary有序集合中的成员按分数从低到高排列为peter、tom、jack。
- 使用ZRANK命令查询tom的排名:
ZRANK salary tom
# 返回结果可能是:
(integer) 1
表示tom在salary有序集合中的排名为1(因为是从0开始计数的,所以peter的排名为0,tom紧随其后)。
(3)注意事项:
- ZRANK命令的时间复杂度为O(log(N)),其中N为有序集合中的元素数量。因此,即使有序集合包含大量元素,查询排名通常也是很快的。
- 有序集合中的成员是唯一的,但分数可以相同。如果多个成员具有相同的分数,那么它们的排名将根据它们在有序集合中的添加顺序来决定(先添加的排名靠前)。不过,需要注意的是,在Redis 3.0.2及之前的版本中,如果存在分数相同的成员,ZRANK命令可能会返回这些成员中任意一个的排名,而不是一个确定的顺序。从Redis 3.2版本开始,这个问题得到了修复,分数相同的成员会按照它们被添加到有序集合中的顺序来排序。
- ZRANK命令只返回成员的排名,而不返回成员的分数。如果需要同时获取成员的排名和分数,可以使用ZRANGE命令并指定WITHSCORES选项来查询有序集合中的元素及其分数,然后手动找到所需成员的排名和分数。或者,在Redis 7.2.0及更高版本中,可以使用ZRANK命令的withscore参数来同时获取成员的排名和分数(但需要注意版本兼容性)。
2.2.12 ZREVRANK命令
(1)Redis的ZREVRANK命令用于返回有序集合(sorted set)中指定成员的逆向排名,即按分数值递减(从大到小)顺序排列的排名。语法如下:
ZREVRANK key member
- key:有序集合的键名。
- member:要查询排名的有序集合成员。
- 返回值:如果成员是有序集合key的成员,则返回该成员的逆向排名(索引)。排名是从0开始的,即分数最高的成员排名为0。如果成员不是有序集合key的成员,则返回nil。
(2)示例:
- 假设有一个有序集合salary,存储了员工的薪水信息:
ZADD salary 3500 "peter" 5000 "jack" 4000 "tom"
此时,salary有序集合中的成员按分数从高到低排列为jack、tom、peter。
- 使用ZREVRANK命令查询peter的逆向排名:
ZREVRANK salary peter
# 返回结果可能是:
(integer) 2
表示peter在salary有序集合中的逆向排名为2(因为是从0开始计数的,所以jack的逆向排名为0,tom为1,peter为2)。
(3)注意事项:
- ZREVRANK命令的时间复杂度为O(log(N)),其中N为有序集合中的元素数量。因此,查询逆向排名通常也是很快的。
- 与ZRANK命令不同,ZREVRANK是按照分数从高到低的顺序来排名成员的。
- 如果多个成员具有相同的分数,在Redis 3.2及更高版本中,这些成员会按照它们被添加到有序集合中的顺序来排序(尽管分数相同,但添加顺序决定了它们的相对位置)。在Redis 3.0.2及之前的版本中,分数相同的成员的排序可能是不确定的。
2.2.13 ZSCORE命令
(1)Redis的ZSCORE命令用于获取有序集合(sorted set)中指定成员的分数值。语法如下
ZSCORE key member
- key:有序集合的键名。
- member:要获取分数值的有序集合成员。
- 返回值:如果成员是有序集合key的成员,则返回该成员的分数值,以字符串形式表示。如果成员不是有序集合key的成员,或者有序集合key不存在,则返回nil。
(2)示例:
- 假设有一个有序集合salary,存储了员工的薪水信息:
ZADD salary 3500 "peter" 5000 "jack" 4000 "tom"
- 此时,可以使用ZSCORE命令查询tom的薪水(即分数值):
ZSCORE salary tom
# 返回结果可能是:
"4000"
表示tom在salary有序集合中的分数值为4000。
(3)注意事项:
- ZSCORE命令的时间复杂度为O(1),即查询操作是非常快的。
- 有序集合中的成员是唯一的,每个成员都关联一个分数值,用于排序。
分数值可以是任意类型的数值,包括整数和浮点数。 - 如果有序集合不存在或者指定的成员不在有序集合中,ZSCORE命令会返回nil。
2.2.14 ZREM命令
(1)Redis的ZREM命令用于从有序集合(sorted set)中移除一个或多个成员。语法如下:
ZREM key member [member ...]
- key:有序集合的键名。
- member:一个或多个要从有序集合中移除的成员。
-
- 返回值:被成功移除的成员的数量。
(2)示例:
- 假设有一个有序集合salary,存储了员工的薪水信息:
ZADD salary 3500 "peter" 5000 "jack" 4000 "tom"
- 此时,可以使用ZREM命令移除jack和tom:
ZREM salary jack tom
# 返回结果可能是:
(integer) 2
表示成功移除了2个成员。此时,salary有序集合中只剩下peter。
(3)注意事项:
- ZREM命令的时间复杂度为O(log(N)+M),其中N为有序集合中的元素数量,M为要移除的成员数量。如果M较大,移除操作可能会稍微慢一些,但通常仍然是很快的。
- 如果尝试移除一个不存在的成员,该操作会被忽略,并且不会影响返回的成功移除成员的数量。
- 当所有指定的成员都被成功移除后,如果有序集合变为空集,那么它仍然存在于Redis中,但没有任何成员。要完全删除一个空的有序集合,需要使用DEL命令。
2.2.15 ZREMRANGEBYRANK命令
(1)Redis的ZREMRANGEBYRANK命令用于移除有序集合(sorted set)中指定排名(rank)区间内的所有成员。这个命令允许你通过成员的排名来指定要移除的成员范围。语法如下:
ZREMRANGEBYRANK key start stop
- key:有序集合的键名。
- start:移除操作的起始排名(包含)。排名是以0为底的索引,即0表示有序集合的第一个成员,1表示第二个成员,以此类推。也可以使用负数索引,其中-1表示最后一个成员,-2表示倒数第二个成员,以此类推。
- stop:移除操作的结束排名(包含)。
- 返回值:被移除成员的数量。
(2)示例:
- 假设有一个有序集合salary,存储了员工的薪水信息:
ZADD salary 3500 "peter" 5000 "jack" 4000 "tom"
此时,salary有序集合中的成员按分数从低到高排列为peter(排名0)、tom(排名1)、jack(排名2)。
- 使用ZREMRANGEBYRANK命令移除排名在0到1之间的成员(即peter和tom):
ZREMRANGEBYRANK salary 0 1
# 返回结果可能是:
(integer) 2
表示成功移除了2个成员。此时,salary有序集合中只剩下jack。
(3)注意事项:
- ZREMRANGEBYRANK命令的时间复杂度为O(log(N)+M),其中N为有序集合中的元素数量,M为被移除的元素数量。虽然时间复杂度中包含了一个对数部分和一个线性部分,但由于M通常不会很大,因此这个命令通常执行得很快。
- 排名区间是包含start和stop的,即start和stop指定的排名对应的成员都会被移除。
- 如果start或stop的排名超出了有序集合的实际排名范围,Redis会忽略超出的部分,并且只移除在范围内的成员。例如,如果start为-5而有序集合只有3个成员,那么Redis会从排名0开始移除成员。
- 移除操作后,有序集合的排名会重新调整,以反映剩余成员的新顺序。
2.2.16 ZREMRANGEBYSCORE命令
(1)Redis的ZREMRANGEBYSCORE命令用于移除有序集合(sorted set)中指定分数(score)区间内的所有成员。这个命令通过分数范围来精确控制要移除的成员集合。语法如下:
ZREMRANGEBYSCORE key min max
- key:有序集合的键名。
- min:移除操作的分数区间的最小值(包含)。
- max:移除操作的分数区间的最大值(包含)。
- 返回值:被移除成员的数量。
(2)示例:
- 假设有一个有序集合salary,存储了员工的薪水信息:
ZADD salary 3500 "peter" 5000 "jack" 4000 "tom"
-
此时,salary有序集合中的成员按分数从低到高排列为peter(3500分)、tom(4000分)、jack(5000分)。
-
使用ZREMRANGEBYSCORE命令移除分数在3500到4000之间的成员(即peter和tom):
ZREMRANGEBYSCORE salary 3500 4000
# 返回结果可能是:
(integer) 2
表示成功移除了2个成员。此时,salary有序集合中只剩下jack。
(3)注意事项:
- 分数区间是包含min和max的,即min和max指定的分数对应的成员都会被移除。
- 可以使用-inf和+inf来表示负无穷大和正无穷大,以移除所有小于或大于某个特定分数的成员。
- 如果min或max的分数值超出了有序集合中的实际分数范围,Redis会忽略超出的部分,并且只移除在范围内的成员。
- 移除操作后,有序集合的排名会重新调整,以反映剩余成员的新顺序和分数。
2.2.17 ZINCRBY命令
(1)Redis的ZINCRBY命令用于对有序集合(sorted set)中指定成员的分数进行增加操作。这个命令允许你通过传递一个增量值来更新成员的分数,从而实现动态排序。语法如下:
ZINCRBY key increment member
- key:有序集合的键名。
- increment:要增加给成员的分数值,可以是正数(表示增加)也可以是负数(表示减少)。
- member:要更新分数的有序集合成员。
- 返回值:成员更新后的新分数值,以字符串形式表示。
(2)示例:
- 假设有一个有序集合salary,存储了员工的薪水信息:
ZADD salary 3500 "peter" 5000 "jack" 4000 "tom"
-
此时,salary有序集合中的成员按分数从低到高排列为peter(3500分)、tom(4000分)、jack(5000分)。
-
使用ZINCRBY命令给tom的薪水增加1000分:
ZINCRBY salary 1000 "tom"
# 返回结果可能是:
"5000"
表示tom的薪水从4000分增加到了5000分(注意,这里返回的是更新后的分数值)。此时,salary有序集合中的成员排序可能会发生变化,因为tom的薪水现在和jack相同了,但Redis会根据分数的精确值来维持排序,如果有需要的话(例如,使用ZRANGE命令查看时)。
(3)注意事项:
- 如果指定的成员不存在于有序集合中,ZINCRBY命令会将其添加到集合中,并将其分数设置为指定的增量值(即increment)。
- 分数值可以是整数值或双精度浮点数,提供了很大的灵活性来存储和更新成员的分数。
- ZINCRBY命令的时间复杂度为O(log(N)),其中N为有序集合中的元素数量。这使得在大型有序集合中更新成员分数时仍然能够保持较高的性能。
2.3 集合间操作
2.3.1 ZINTERSTORE命令
(1)有序集合的交集操作:
(2)Redis的ZINTERSTORE命令用于计算给定的一个或多个有序集合(sorted set)的交集,并将该交集(结果集)储存到另一个有序集合中。这个命令在处理集合数据时非常有用,特别是当你需要找出多个集合中共有的元素及其分数时。语法如下:
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
- destination:指定结果集保存的集合键。
- numkeys:指定有多少个集合参与交集运算。
- key [key …]:指定参与交集运算的集合的键。
- [WEIGHTS weight [weight …]](可选):指定参与交集运算各集合分数的权重参数。如果不指定,则默认权重为1。
- [AGGREGATE SUM|MIN|MAX](可选):指定交集中元素的分数的取值方式。SUM表示各集合中该元素的分数乘以权重后求和(默认);MIN表示取各集合中该元素分数的最小值;MAX表示取各集合中该元素分数的最大值。
- 返回值:保存到目标结果集的成员数量。
(3)示例:
- 假设有两个有序集合mid_test和fin_test,存储了学生的考试成绩:
ZADD mid_test 70 "Li Lei" 70 "Han Meimei" 99.5 "Tom"
ZADD fin_test 88 "Li Lei" 75 "Han Meimei" 99.5 "Tom"
- 使用ZINTERSTORE命令计算这两个集合的交集,并将结果保存到sum_point集合中:
ZINTERSTORE sum_point 2 mid_test fin_test
# 返回结果可能是:
(integer) 3
- 表示成功计算出了3个交集成员。此时,sum_point集合中的成员及其分数为:
ZRANGE sum_point 0 -1 WITHSCORES
# 返回结果可能是:
1) "Han Meimei"
2) "145"
3) "Li Lei"
4) "158"
5) "Tom"
6) "199"
这里,每个成员的分数是其在mid_test和fin_test两个集合中分数的和。
(4)注意事项:
- 如果指定的destination已经存在,则结果集会覆盖这个键,无论之前这个键是什么类型,是否包含元素。因此,在使用ZINTERSTORE命令时,务必注意目标键的命名和存在性。
- ZINTERSTORE命令的时间复杂度为O(N*M),其中N是参与交集运算的集合数量,M是这些集合中成员数量的总和(在最优情况下)。因此,在处理大型集合时,可能需要考虑性能问题。
- 权重和聚合方式提供了很大的灵活性,允许你根据实际需求来计算交集成员的分数。例如,你可以给不同的集合设置不同的权重,以反映它们在最终结果中的重要性;你也可以选择使用最小值或最大值作为交集成员的分数,而不是求和。
2.3.2 ZUNIONSTORE命令
(1)有序集合的并集操作:
(2)Redis的ZUNIONSTORE命令用于计算给定的一个或多个有序集合(sorted set)的并集,并将该并集(结果集)储存到另一个有序集合中。与ZINTERSTORE命令不同,ZUNIONSTORE处理的是集合的并集而不是交集。语法如下:
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
- destination:指定结果集保存的集合键。
- numkeys:指定有多少个集合参与并集运算。
- key [key …]:指定参与并集运算的集合的键。
- [WEIGHTS weight [weight …]](可选):指定参与并集运算各集合分数的权重参数。如果不指定,则默认权重为1。
- [AGGREGATE SUM|MIN|MAX](可选):指定并集中元素的分数的取值方式。SUM表示各集合中该元素的分数乘以权重后求和(默认);MIN表示取各集合中该元素分数的最小值;MAX表示取各集合中该元素分数的最大值。
- 返回值:保存到目标结果集的成员数量。
(2)示例:
- 假设有两个有序集合set1和set2,存储了不同的分数:
ZADD set1 1 "one" 2 "two" 3 "three"
ZADD set2 2 "two" 3 "three" 4 "four"
使用ZUNIONSTORE命令计算这两个集合的并集,并将结果保存到union_set集合中:
ZUNIONSTORE union_set 2 set1 set2
# 返回结果可能是:
(integer) 4
- 表示成功计算出了4个并集成员。此时,union_set集合中的成员及其分数为(注意,如果两个集合中有相同的成员,则它们的分数会根据AGGREGATE选项进行合并):
ZRANGE union_set 0 -1 WITHSCORES
# 返回结果可能是:
1) "one"
2) "1"
3) "two"
4) "4" -- 因为两个集合中都有"two",所以分数是1+3=4(默认权重为1)
5) "three"
6) "6" -- 同理,"three"的分数是2+4=6
7) "four"
8) "4"
- 如果你给集合设置了不同的权重,比如set1的权重是2,set2的权重是3,那么计算并集时,每个集合中成员的分数会先乘以对应的权重,然后再进行合并:
ZUNIONSTORE weighted_union 2 set1 set2 WEIGHTS 2 3
# 此时,weighted_union集合中的成员及其分数可能会是:
bash
1) "one"
2) "2" -- 1*2=2
3) "two"
4) "10" -- 2*2+3*2=10
5) "three"
6) "12" -- 3*2+4*3=12
7) "four"
8) "12" -- 0*2+4*3=12(注意,"four"在set1中不存在,所以其分数是0*2=0加上set2中的4*3=12)
但是,请注意上面的four的分数计算示例中,我假设了set1中没有four这个成员,其分数为0(在实际情况下,不存在的成员不会被考虑在内,除非使用了WEIGHTS并且明确指定了权重,但在这个上下文中,我们只是为了说明权重的计算方法)。实际上,由于set1中没有four,所以在计算weighted_union时,four的分数只会是set2中four的分数乘以权重3,即4*3=12。
2.4 命令小结
(1)有序集合命令:
2.5 内部编码
(1)有序集合类型的内部编码有两种:
- ziplist (压缩列表) :当有序集合的元素个数小于zset- max -ziplist-entries配置(默认128个)同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis 会用ziplist来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。
- skiplist (跳表) :当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的操作效率会下降。
- 当元素个数较少且每个元素较小时,内部编码为ziplist:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zsetkey
"ziplist"
- 当元素个数超过128个,内部编码skiplist:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3 ... 省略 ... 82 e129
(integer) 129
127.0.0.1:6379> object encoding zsetkey
"skiplist"
- 当某个元素大于64字节时,内部编码skiplist:
127.0.0.1:6379> zadd zsetkey 50 "one string bigger than 64 bytes ... 省略 ..."
(integer) 1
127.0.0.1:6379> object encoding zsetkey
"skiplist"
2.6 使用场景
(1)有序集合比较典型的使用场景就是排行榜系统。例如常见的网站上的热榜信息,榜单的维度可能是多方面的:按照时间、按照阅读量、按照点赞量。本例中我们使用点赞数这个维度,维护每天的热榜:
(2)添加用户赞数:
- 例如用户james发布了一篇文章,并获得3个赞,可以使用有序集合的zadd和zincrby功能:
zadd user:ranking:2022-03-15 3 james
- 之后如果再获得赞,可以使用zincrby:
zincrby user:ranking:2022-03-15 1 james
(3)取消用户赞数:
- 由于各种原因(例如用户注销、用户作弊等)需要将用户删除,此时需要将用户从榜单中删除掉,可以使用zrem。例如删除成员tom:
zrem user:ranking:2022-03-15 tom
(4)展示获取赞数最多的10个用户:
- 此功能使用zrevrange命令实现:
zrevrangebyrank user:ranking:2022-03-15 0 9
(5)展示用户信息以及用户分数:
- 此功能将用户名作为键后缀,将用户信息保存在哈希类型中,至于用户的分数和排名可以使用zscore和zrank来实现。
hgetall user:info:tom
zscore user:ranking:2022-03-15 mike
zrank user:ranking:2022-03-15 mike
3. 渐进式遍历SCAN命令
(1)Redis使用scan命令进行渐进式遍历键,进而解决直接使用keys获取键时可能出现的阻塞问题。每次scan命令的时间复杂度是0(1),但是要完整地完成所有键的遍历,需要执行多次scan。整个过程如下图所示。
首次scan从0开始。当scan返回的下次位置为0时,遍历结束。
(2)Redis中的SCAN命令是用于迭代遍历Redis键空间的命令,它提供了一种非阻塞且支持增量迭代的方式来遍历数据库中的键。以下是关于SCAN命令的详细介绍:
SCAN cursor [MATCH pattern] [COUNT count]
- cursor:一个整数,用于表示迭代器的当前位置。起始值一般为0,表示从第一个元素开始迭代。每次调用SCAN命令后,命令会返回一个新的游标值,用于下一次迭代。
- MATCH pattern(可选):用于匹配键的模式。只有与模式匹配的键才会被返回。这有助于在大数据集中快速找到符合特定条件的键。
- COUNT count(可选):指定每次迭代返回的键的数量。默认值为10,但Redis可能会返回更多或更少的键,具体取决于数据集的大小和内部实现。
- 返回值:SCAN命令的返回值是一个包含两个元素的数组:
- 第一个元素是用于下一次迭代的新游标值。
- 第二个元素是一个数组,包含了当前迭代批次中返回的所有键。
(3)示例:
- 遍历所有键:
127.0.0.1:6379> SCAN 0
1) "5"
2) 1) "key1"
2) "key2"
...
在上面的示例中,从游标0开始迭代,返回了下一批元素的游标值5和当前批次的键列表。
使用MATCH进行模式匹配:
bash
127.0.0.1:6379> SCAN 0 MATCH user:*
- “3”
-
- “user:1000”
- “user:1001”
…
上面的命令返回了所有以"user:"开头的键。
- 使用COUNT指定返回键的数量:
127.0.0.1:6379> SCAN 0 COUNT 20
1) "5"
2) 1) "key1"
2) "key2"
...
20) "key20"
上面的命令指定了每次迭代返回20个键。
(4)特点与注意事项:
- 非阻塞:SCAN命令是非阻塞的,可以避免一次性遍历大数据集带来的阻塞问题。
- 无序返回:SCAN命令返回的结果是无序的,不保证每次返回的顺序一致。
- 可能重复返回:由于迭代过程中可能有键的插入或删除,SCAN命令可能会返回重复的键。使用方需要自行去重。
- 增量遍历:通过游标实现增量遍历,适合于逐步处理大数据集。
(5)除了scan以外,Redis 面向哈希类型、集合类型、有序集合类型分别提供了hscan、sscan、 zscan 命令,它们的用法和scan基本类似。渐进性遍历scan虽然解决了阻塞的问题,但如果在遍历期间键有所变化(增加、修改、删除),可能导致遍历时键的重复遍历或者遗漏,这点务必在实际开发中考虑。
4. 数据库管理
(1)Redis提供了几个面向Redis数据库的操作,分别是dbsize、select、 flushdb、 flushall 命令,本机将通过具体的使用常见介绍这些命令。
- 切换数据库:
select dbIndex
许多关系型数据库,例如MySQL支持在一个实例下有多个数据库存在的,但是与关系型数据库用字符来区分不同数据库名不同,Redis 只是用数字作为多个数据库的实现。Redis 默认配置中是有16 个数据库。select 0操作会切换到第一个数据库,select 15会切换到最后一个数据库。0号数据库和15号数据库保存的数据是完全不冲突的, 即各种有各自的键值对。默认情况下,我们处于数据库0。redis管理的数据库:
Redis中虽然支持多数据库,但随着版本的升级,其实不是特别建议使用多数据库特性。如果真的需要完全隔离的两套键值对,更好的做法是维护多个Redis实例,而不是在一个Redis实例中维护多数据库。这是因为本身Redis并没有为多数据库提供太多的特性,其次无论是否有多个数据库,Redis 都是使用单线程模型,所以彼此之间还是需要排队等待命令的执行。同时多数据库还会让开发、调试和运维工作变得复杂。所以实践中,始终使用数据库0其实是一个很好的选择。
- 清除数据库:flushdb / flushall命令用于清除数据库,区别在于flushdb只清除当前数据库,flushall 会清楚所有数据库。