Redis `ZUNIONSTORE` 与 `ZINTERSTORE`:有序集合的聚合操作与性能

各位朋友,今天咱们来聊聊 Redis 里的两个重量级选手:ZUNIONSTOREZINTERSTORE。这俩兄弟是干啥的呢?简单来说,就是用来对有序集合进行聚合操作的。说白了,就是把几个有序集合合并或者取交集,然后把结果存到新的有序集合里。

很多人可能觉得,不就合并个集合、取个交集嘛,有啥难的?我自己写个循环也能搞定。话是没错,但 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)

解释一下:

  • member1zset1zset3 中都存在,score 分别是 10 和 5,所以最终的 score 是 10 + 5 = 15。
  • member2zset1zset2 中都存在,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)

解释一下:

  • member1zset1zset3 中都存在,score 分别是 10 和 5,权重分别是 2 和 1,所以最终的 score 是 (10 2) + (5 1) = 25。
  • member2zset1zset2 中都存在,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)

解释一下:

  • member1zset1zset3 中都存在,score 分别是 10 和 5,所以最终的 score 是 min(10, 5) = 5。
  • member2zset1zset2 中都存在,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)

解释一下:

  • member1zset1zset3 中都存在,score 分别是 10 和 5,所以最终的 score 是 max(10, 5) = 10。
  • member2zset1zset2 中都存在,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. 性能考量

ZUNIONSTOREZINTERSTORE 的性能主要受到以下几个因素的影响:

  • 参与聚合的有序集合的数量: 集合越多,计算量越大。
  • 每个有序集合的大小: 集合越大,需要处理的元素就越多。
  • 权重和聚合方式: 计算权重和聚合 score 会增加额外的计算量。

一般来说,这两个命令的复杂度是 O(N),其中 N 是所有输入有序集合中元素的总数。但是,Redis 内部做了一些优化,比如使用跳跃表来加速查找,所以实际性能通常比理论复杂度要好。

一些性能优化建议:

  • 尽量减少参与聚合的有序集合的数量: 如果可以,尽量把多个集合合并成少数几个大的集合。
  • 避免在大型集合上频繁执行聚合操作: 可以考虑使用缓存来存储聚合结果。
  • 根据实际需求选择合适的聚合方式: 如果不需要权重和特殊的聚合方式,就不要使用它们。

4. 使用场景

ZUNIONSTOREZINTERSTORE 在实际应用中有很多用处。

  • 社交网络: 可以使用 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. 注意事项

  • ZUNIONSTOREZINTERSTORE 会覆盖目标有序集合。
  • 如果参与聚合的有序集合中存在相同的元素,它们的 score 会根据聚合方式进行合并。
  • 如果参与聚合的有序集合中存在不同的元素,ZUNIONSTORE 会把它们都包含在结果中,而 ZINTERSTORE 会忽略它们。
  • WEIGHTS 参数的数量必须和 key 的数量相同。
  • AGGREGATE 参数只能是 SUMMINMAX

总结

ZUNIONSTOREZINTERSTORE 是 Redis 中非常强大的命令,它们可以高效地对有序集合进行聚合操作。通过灵活的选项,我们可以根据实际需求定制聚合过程。掌握这两个命令,可以让我们更好地利用 Redis 解决各种各样的问题。

希望今天的讲解对大家有所帮助! 记住,技术就是要多实践,多思考,才能真正掌握。 别光听不练,快去 Redis 上试试吧!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注