在处理排行榜、标签系统、用户画像这类业务时,我们经常需要找出同时出现在多个有序集合中的成员。比如一个用户既在“活跃用户榜”又在“付费用户榜”里,这个交集怎么算?在Redis 6.2之前,只能用ZINTERSTORE先把结果写入一个新键再读取,多一次网络往返。6.2版本新增的ZINTER命令直接返回交集结果,省去了中间键的创建和清理,用起来干净利落。这篇文章就来把ZINTER的语法、参数和典型用法彻底讲清楚。

ZINTER基本语法与AGGREGATE聚合方式
ZINTER的基本语法是ZINTER numkeys key [key ...] [WEIGHTS weight ...] [AGGREGATE 。其中numkeys指定参与运算的键数量,后面跟上对应的键名。最关键的是AGGREGATE参数,它决定了当某个成员同时存在于多个集合时,最终分值如何计算。
默认使用SUM方式,也就是把该成员在各个集合中的分值直接相加。比如成员tom在集合A中分值是10,在集合B中分值是20,SUM模式下结果就是30。MIN则取所有分值中的最小值,MAX取最大值。这三种模式各有适用场景:计算综合热度用SUM,找最低价格用MIN,取最高评分用MAX。
redis> ZADD ranking:a 10 tom 20 jerry 30 spike (integer) 3 redis> ZADD ranking:b 5 tom 15 jerry (integer) 2 redis> ZINTER 2 ranking:a ranking:b 1) "tom" 2) "jerry" redis> ZINTER 2 ranking:a ranking:b AGGREGATE SUM WITHSCORES 1) "tom" 2) "15" 3) "jerry" 4) "35"
注意观察上面的结果:tom的分值是10+5=15,jerry的分值是20+15=35。spike只存在于第一个集合中,不在交集里,所以不会出现在结果中。WITHSCORES选项让返回值变成成员和分值交替的形式,这在需要展示具体分值的场景下很实用。
WEIGHTS权重参数的工作原理
WEIGHTS参数允许给每个参与运算的集合指定一个乘数系数,计算时会先将成员在该集合中的分值乘以权重,再交给AGGREGATE处理。权重的默认值是1,如果指定WEIGHTS,必须为每一个键都提供一个权重值,不能只给部分键设置。
举个实际的例子:假设要计算用户的综合活跃分,登录天数占30%,下单次数占70%。可以把登录天数存在login:days集合,下单次数存在order:count集合,然后用0.3和0.7作为权重做加权和。这种需求如果不用WEIGHTS,就得在写入数据时预先乘好系数,一旦业务比例调整就得全量刷数据,非常麻烦。
redis> ZADD login:days 100 tom 80 jerry (integer) 2 redis> ZADD order:count 20 tom 50 jerry (integer) 2 redis> ZINTER 2 login:days order:count WEIGHTS 0.3 0.7 AGGREGATE SUM WITHSCORES 1) "tom" 2) "44" 3) "jerry" 4) "59"
验证一下:tom的结果是100*0.3+20*0.7=30+14=44,jerry是80*0.3+50*0.7=24+35=59。权重值支持浮点数,也支持负数,不过在大多数业务里用正数权重做加权平均就够了。需要提醒的是,权重只是线性缩放,如果业务模型需要非线性组合,就得在应用层做二次计算。
ZINTER与ZINTERSTORE的区别及选择建议
ZINTERSTORE是老命令,从早期版本就存在,它会把交集结果写入到一个新的目标键中,返回值是结果集的成员数量。ZINTER是它的只读版本,只返回结果不落盘。两者的参数几乎完全一致,区别就在目标键上。
| 对比项 | ZINTER | ZINTERSTORE |
|---|---|---|
| 结果处理 | 直接返回客户端 | 写入目标键 |
| 版本要求 | 6.2及以上 | 所有版本 |
| 是否产生新键 | 否 | 是 |
| 结果可分页 | 否,一次性返回 | 是,可配合ZRANGE分页 |
选择的原则很简单:如果交集结果只是临时看一眼,比如管理后台的即时查询、接口的实时计算,用ZINTER更合适,没有键管理的负担。如果结果要被反复使用,比如每天凌晨算好当天的活跃付费用户榜单,供多个页面读取,那就用ZINTERSTORE把结果持久化到目标键,再设置过期时间,避免每次请求都重算。
redis> ZINTERSTORE combined:20240101 2 ranking:a ranking:b AGGREGATE MAX (integer) 2 redis> EXPIRE combined:20240101 86400 (integer) 1 redis> ZRANGE combined:20240101 0 -1 WITHSCORES 1) "tom" 2) "10" 3) "jerry" 4) "20"
性能考量与注意事项
交集运算的时间复杂度是O(N*K)加O(M*logM),其中N是所有集合中基数最小的那个集合的大小,K是参与运算的集合数量,M是结果集大小。从这个复杂度可以得出第一个优化思路:尽量让numkeys保持较小,把参与运算的集合控制在两到三个,数量太多时Redis会做大量跳表遍历,阻塞主线程。
第二个要点是集合的选择。Redis在实现上会从基数最小的集合开始遍历,所以业务设计时如果能让某个筛选条件的集合天然很小(比如“钻石会员”集合通常远小于“全体用户”集合),交集计算就会非常快。反过来,如果两个千万级的大集合做交集,即使最终结果只有几十个成员,遍历开销也小不了,这种情况建议把计算挪到从库执行,或者在业务低峰期批量处理。
还要注意ZINTER要求所有参与运算的键都是有序集合类型,如果其中混入了普通set或string,命令会直接报WRONGTYPE错误。另外ZINTER是一次性返回全部结果的,如果预估结果集可能很大(比如上万成员),直接用ZINTER会把大量数据一次性推给客户端,容易造成网络拥积,此时应该改用ZINTERSTORE加ZRANGE分页读取的方案。
最后提一点实践经验:ZINTER和ZINTERSTORE都会在键不存在时把空集合当作空处理,不会报错,但要用EXISTS确认键是否真的存在,避免因为键名拼写错误导致业务拿到空结果还以为是没有交集。结合合理的过期策略和权重设计,ZINTER可以优雅地解决大部分交集计算需求。
Redis ZINTER有序集合交集ZINTERSTORE修改时间:2026-09-11 23:54:40