各位朋友,今天咱们来聊聊 Redis 里的两个重量级选手:ZUNIONSTORE
和 ZINTERSTORE
。这俩兄弟是干啥的呢?简单来说,就是用来对有序集合进行聚合操作的。说白了,就是把几个有序集合合并或者取交集,然后把结果存到新的有序集合里。
很多人可能觉得,不就合并个集合、取个交集嘛,有啥难的?我自己写个循环也能搞定。话是没错,但 Redis 可是专业的,它在性能方面做了很多优化,用起来效率杠杠的。而且,这两个命令还提供了一些灵活的选项,可以满足各种各样的需求。
接下来,咱们就深入了解一下这两个命令,看看它们到底有多厉害。
1. ZUNIONSTORE
:有序集合的并集
ZUNIONSTORE
命令的作用是将多个有序集合的并集存储到一个新的有序集合中。它的语法如下:
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
咱们来拆解一下这个命令的各个部分:
destination
: 这是目标有序集合的名称,也就是合并后的结果要存储的地方。如果这个集合已经存在,那么它会被覆盖。numkeys
: 需要合并的有序集合的数量。key [key ...]
: 需要合并的有序集合的名称列表。至少要有一个 key,否则就没意义了。WEIGHTS weight [weight ...]
: 这是一个可选参数,用于给每个有序集合的 score 赋予权重。每个 weight 对应一个 key。在合并时,每个元素的 score 会乘以对应的权重。如果省略这个参数,那么所有有序集合的权重都默认为 1。AGGREGATE SUM|MIN|MAX
: 这是一个可选参数,用于指定如何聚合相同元素的 score。默认值是SUM
,也就是把相同元素的 score 加起来。你也可以选择MIN
或者MAX
,分别表示取最小值或者最大值。
举个例子:
假设我们有三个有序集合:
zset1
:member1 (10), member2 (20), member3 (30)
zset2
:member2 (15), member3 (25), member4 (35)
zset3
:member1 (5), member3 (20), member5 (40)
现在,我们想把这三个集合合并成一个新的有序集合 zset_union
。
场景1:简单合并,权重都为1,score相加
ZUNIONSTORE zset_union 3 zset1 zset2 zset3
执行完这个命令后,zset_union
的内容将会是:
member1 (15), member2 (35), member3 (75), member4 (35), member5 (40)
解释一下:
member1
在zset1
和zset3
中都存在,score 分别是 10 和 5,所以最终的 score 是 10 + 5 = 15。member2
在zset1
和zset2
中都存在,score 分别是 20 和 15,所以最终的 score 是 20 + 15 = 35。member3
在三个集合中都存在,score 分别是 30、25 和 20,所以最终的 score 是 30 + 25 + 20 = 75。member4
只在zset2
中存在,score 是 35,所以最终的 score 是 35。member5
只在zset3
中存在,score 是 40,所以最终的 score 是 40。
场景2:指定权重
假设我们想给 zset1
的权重设置为 2,zset2
的权重设置为 3,zset3
的权重设置为 1。
ZUNIONSTORE zset_union 3 zset1 zset2 zset3 WEIGHTS 2 3 1
执行完这个命令后,zset_union
的内容将会是:
member1 (25), member2 (85), member3 (155), member4 (105), member5 (40)
解释一下:
member1
在zset1
和zset3
中都存在,score 分别是 10 和 5,权重分别是 2 和 1,所以最终的 score 是 (10 2) + (5 1) = 25。member2
在zset1
和zset2
中都存在,score 分别是 20 和 15,权重分别是 2 和 3,所以最终的 score 是 (20 2) + (15 3) = 85。member3
在三个集合中都存在,score 分别是 30、25 和 20,权重分别是 2、3 和 1,所以最终的 score 是 (30 2) + (25 3) + (20 * 1) = 155。member4
只在zset2
中存在,score 是 35,权重是 3,所以最终的 score 是 35 * 3 = 105。member5
只在zset3
中存在,score 是 40,权重是 1,所以最终的 score 是 40 * 1 = 40。
场景3:指定聚合方式为 MIN
假设我们想把相同元素的 score 取最小值。
ZUNIONSTORE zset_union 3 zset1 zset2 zset3 AGGREGATE MIN
执行完这个命令后,zset_union
的内容将会是:
member1 (5), member2 (15), member3 (20), member4 (35), member5 (40)
解释一下:
member1
在zset1
和zset3
中都存在,score 分别是 10 和 5,所以最终的 score 是 min(10, 5) = 5。member2
在zset1
和zset2
中都存在,score 分别是 20 和 15,所以最终的 score 是 min(20, 15) = 15。member3
在三个集合中都存在,score 分别是 30、25 和 20,所以最终的 score 是 min(30, 25, 20) = 20。member4
只在zset2
中存在,score 是 35,所以最终的 score 是 35。member5
只在zset3
中存在,score 是 40,所以最终的 score 是 40。
场景4:指定聚合方式为 MAX
假设我们想把相同元素的 score 取最大值。
ZUNIONSTORE zset_union 3 zset1 zset2 zset3 AGGREGATE MAX
执行完这个命令后,zset_union
的内容将会是:
member1 (10), member2 (20), member3 (30), member4 (35), member5 (40)
解释一下:
member1
在zset1
和zset3
中都存在,score 分别是 10 和 5,所以最终的 score 是 max(10, 5) = 10。member2
在zset1
和zset2
中都存在,score 分别是 20 和 15,所以最终的 score 是 max(20, 15) = 20。member3
在三个集合中都存在,score 分别是 30、25 和 20,所以最终的 score 是 max(30, 25, 20) = 30。member4
只在zset2
中存在,score 是 35,所以最终的 score 是 35。member5
只在zset3
中存在,score 是 40,所以最终的 score 是 40。
2. ZINTERSTORE
:有序集合的交集
ZINTERSTORE
命令的作用是将多个有序集合的交集存储到一个新的有序集合中。它的语法和 ZUNIONSTORE
非常相似:
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
除了作用不同,其他参数的含义和 ZUNIONSTORE
完全一样。
举个例子:
还是用之前的三个有序集合:
zset1
:member1 (10), member2 (20), member3 (30)
zset2
:member2 (15), member3 (25), member4 (35)
zset3
:member1 (5), member3 (20), member5 (40)
现在,我们想求这三个集合的交集,并存储到 zset_intersection
中。
场景1:简单求交集,权重都为1,score相加
ZINTERSTORE zset_intersection 3 zset1 zset2 zset3
执行完这个命令后,zset_intersection
的内容将会是:
member3 (75)
解释一下:
只有 member3
同时存在于三个集合中,所以它是交集的唯一成员。它的 score 是 30 + 25 + 20 = 75。
场景2:指定权重
假设我们想给 zset1
的权重设置为 2,zset2
的权重设置为 3,zset3
的权重设置为 1。
ZINTERSTORE zset_intersection 3 zset1 zset2 zset3 WEIGHTS 2 3 1
执行完这个命令后,zset_intersection
的内容将会是:
member3 (155)
解释一下:
member3
的 score 是 (30 2) + (25 3) + (20 * 1) = 155。
场景3:指定聚合方式为 MIN
假设我们想把相同元素的 score 取最小值。
ZINTERSTORE zset_intersection 3 zset1 zset2 zset3 AGGREGATE MIN
执行完这个命令后,zset_intersection
的内容将会是:
member3 (20)
解释一下:
member3
的 score 是 min(30, 25, 20) = 20。
场景4:指定聚合方式为 MAX
假设我们想把相同元素的 score 取最大值。
ZINTERSTORE zset_intersection 3 zset1 zset2 zset3 AGGREGATE MAX
执行完这个命令后,zset_intersection
的内容将会是:
member3 (30)
解释一下:
member3
的 score 是 max(30, 25, 20) = 30。
3. 性能考量
ZUNIONSTORE
和 ZINTERSTORE
的性能主要受到以下几个因素的影响:
- 参与聚合的有序集合的数量: 集合越多,计算量越大。
- 每个有序集合的大小: 集合越大,需要处理的元素就越多。
- 权重和聚合方式: 计算权重和聚合 score 会增加额外的计算量。
一般来说,这两个命令的复杂度是 O(N),其中 N 是所有输入有序集合中元素的总数。但是,Redis 内部做了一些优化,比如使用跳跃表来加速查找,所以实际性能通常比理论复杂度要好。
一些性能优化建议:
- 尽量减少参与聚合的有序集合的数量: 如果可以,尽量把多个集合合并成少数几个大的集合。
- 避免在大型集合上频繁执行聚合操作: 可以考虑使用缓存来存储聚合结果。
- 根据实际需求选择合适的聚合方式: 如果不需要权重和特殊的聚合方式,就不要使用它们。
4. 使用场景
ZUNIONSTORE
和 ZINTERSTORE
在实际应用中有很多用处。
- 社交网络: 可以使用
ZUNIONSTORE
来合并多个用户的关注列表,从而获取所有被关注的用户。可以使用ZINTERSTORE
来查找共同关注的用户。 - 电商网站: 可以使用
ZUNIONSTORE
来合并多个商品的推荐列表,从而获取更全面的推荐结果。可以使用ZINTERSTORE
来查找同时购买了多个商品的用户。 - 游戏应用: 可以使用
ZUNIONSTORE
来合并多个用户的排行榜,从而生成总排行榜。可以使用ZINTERSTORE
来查找同时完成了多个任务的用户。
5. 代码示例 (Python + Redis)
import redis
# 连接 Redis
r = redis.Redis(host='localhost', port=6379, db=0)
# 清空之前的集合
r.delete('zset1', 'zset2', 'zset3', 'zset_union', 'zset_intersection')
# 创建三个有序集合
r.zadd('zset1', {'member1': 10, 'member2': 20, 'member3': 30})
r.zadd('zset2', {'member2': 15, 'member3': 25, 'member4': 35})
r.zadd('zset3', {'member1': 5, 'member3': 20, 'member5': 40})
# ZUNIONSTORE 示例
r.zunionstore('zset_union', ['zset1', 'zset2', 'zset3'])
print("ZUNIONSTORE (SUM):", r.zrange('zset_union', 0, -1, withscores=True))
r.zunionstore('zset_union', ['zset1', 'zset2', 'zset3'], weights=[2, 3, 1])
print("ZUNIONSTORE (WEIGHTS):", r.zrange('zset_union', 0, -1, withscores=True))
r.zunionstore('zset_union', ['zset1', 'zset2', 'zset3'], aggregate='MIN')
print("ZUNIONSTORE (MIN):", r.zrange('zset_union', 0, -1, withscores=True))
r.zunionstore('zset_union', ['zset1', 'zset2', 'zset3'], aggregate='MAX')
print("ZUNIONSTORE (MAX):", r.zrange('zset_union', 0, -1, withscores=True))
# ZINTERSTORE 示例
r.zinterstore('zset_intersection', ['zset1', 'zset2', 'zset3'])
print("ZINTERSTORE (SUM):", r.zrange('zset_intersection', 0, -1, withscores=True))
r.zinterstore('zset_intersection', ['zset1', 'zset2', 'zset3'], weights=[2, 3, 1])
print("ZINTERSTORE (WEIGHTS):", r.zrange('zset_intersection', 0, -1, withscores=True))
r.zinterstore('zset_intersection', ['zset1', 'zset2', 'zset3'], aggregate='MIN')
print("ZINTERSTORE (MIN):", r.zrange('zset_intersection', 0, -1, withscores=True))
r.zinterstore('zset_intersection', ['zset1', 'zset2', 'zset3'], aggregate='MAX')
print("ZINTERSTORE (MAX):", r.zrange('zset_intersection', 0, -1, withscores=True))
6. 注意事项
ZUNIONSTORE
和ZINTERSTORE
会覆盖目标有序集合。- 如果参与聚合的有序集合中存在相同的元素,它们的 score 会根据聚合方式进行合并。
- 如果参与聚合的有序集合中存在不同的元素,
ZUNIONSTORE
会把它们都包含在结果中,而ZINTERSTORE
会忽略它们。 WEIGHTS
参数的数量必须和key
的数量相同。AGGREGATE
参数只能是SUM
、MIN
或MAX
。
总结
ZUNIONSTORE
和 ZINTERSTORE
是 Redis 中非常强大的命令,它们可以高效地对有序集合进行聚合操作。通过灵活的选项,我们可以根据实际需求定制聚合过程。掌握这两个命令,可以让我们更好地利用 Redis 解决各种各样的问题。
希望今天的讲解对大家有所帮助! 记住,技术就是要多实践,多思考,才能真正掌握。 别光听不练,快去 Redis 上试试吧!