Redis的集合(Set)类型提供了丰富的集合运算命令,其中SUNION用于计算多个集合的并集,而SUNIONSTORE则更进一步,把并集结果直接存储到一个新的集合中。对于需要反复使用同一个并集结果的场景,SUNIONSTORE可以避免每次向客户端传输大量数据,也能减少重复计算。下面从基础语法和实际使用两个角度展开。

SUNIONSTORE命令的基本用法
SUNIONSTORE的命令格式为SUNIONSTORE destination key [key ...],其中destination是目标集合的键名,后面可以跟一个或多个源集合键。命令执行后,Redis会计算所有源集合的并集,将结果写入destination,如果destination已经存在,会被完全覆盖。返回值是一个整数,表示目标集合中元素的数量。
如果某个源集合键不存在,Redis会把它当作空集合处理,不会报错。但需要注意的是,如果没有任何源集合存在,那么SUNIONSTORE会创建一个空的目标集合并返回0。另外,如果destination和某个源集合是同一个键,Redis会先计算并集,再覆盖原集合,操作是安全的,不会导致数据丢失或死循环。
先通过redis-cli演示一个简单的例子。假设有三个集合:set1包含a、b、c,set2包含c、d、e,set3包含e、f。执行SUNIONSTORE union_set set1 set2 set3后,union_set中会包含a、b、c、d、e、f六个元素。
127.0.0.1:6379> SADD set1 a b c (integer) 3 127.0.0.1:6379> SADD set2 c d e (integer) 3 127.0.0.1:6379> SADD set3 e f (integer) 2 127.0.0.1:6379> SUNIONSTORE union_set set1 set2 set3 (integer) 6 127.0.0.1:6379> SMEMBERS union_set 1) "a" 2) "b" 3) "c" 4) "d" 5) "e" 6) "f"
如果destination指向一个已经存在的其他集合,比如先执行SADD existing x y,再执行SUNIONSTORE existing set1 set2,那么existing原来的元素x、y会被完全清除,只保留set1和set2的并集。这一点在实际使用中容易造成数据意外丢失,需要特别留意。
SUNIONSTORE和SUNION的核心区别
很多开发者刚开始接触Redis集合运算时,容易混淆SUNION和SUNIONSTORE。SUNION命令直接返回并集结果给客户端,不修改任何已有数据。而SUNIONSTORE把结果写回Redis服务器内部,返回的只是目标集合的元素数量。从网络传输角度看,如果并集结果很大,SUNIONSTORE只需要返回一个整数,而SUNION需要把整个集合传回客户端;从计算角度看,两条命令在服务器端都需要遍历所有源集合并去重,计算复杂度均为O(N),其中N是所有源集合元素总数。
但SUNIONSTORE还有内存写回的开销,目标集合会占用额外的内存。如果并集结果很大而且后续不会再次使用,那么SUNION反而更经济,因为不需要持久化。反之,如果并集结果会被多次读取,比如在多个下游服务中共享,SUNIONSTORE可以避免每次重复计算和传输。
用Python的redis-py客户端可以更直观地感受到两者的差异。以下代码分别用SUNION和SUNIONSTORE获取三个集合的并集。
import redis
r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True)
# 准备数据
r.sadd('set1', 'a', 'b', 'c')
r.sadd('set2', 'c', 'd', 'e')
r.sadd('set3', 'e', 'f')
# 使用SUNION:结果返回到客户端
union_result = r.sunion('set1', 'set2', 'set3')
print(union_result) # {'a', 'b', 'c', 'd', 'e', 'f'}
# 使用SUNIONSTORE:结果存储在Redis中,返回元素个数
stored_count = r.sunionstore('union_set', 'set1', 'set2', 'set3')
print(stored_count) # 6
# 读取目标集合确认
print(r.smembers('union_set')) # {'a', 'b', 'c', 'd', 'e', 'f'}
从输出结果可以看到,SUNION返回了一个Python集合对象,数据已经完整下载到本地;而SUNIONSTORE只返回6这个数字,数据留在Redis服务器中,客户端需要时再通过SMEMBERS或SSCAN获取。当并集结果非常庞大时,这种差异在网络延迟和带宽占用上会非常明显。
典型应用场景与性能优化建议
一个经典的使用场景是社交关系中的共同好友或好友合并。例如用户A的好友集合和用户B的好友集合,需要生成一个“可能认识的人”候选池,就可以用SUNIONSTORE把两个集合的并集写入一个临时集合,然后根据需要做差集或随机抽样。这种操作如果每次都通过客户端计算,会浪费大量网络和CPU资源。
另一个常见场景是标签聚合。比如商品被打了多个标签,每个标签对应一个商品ID集合。运营人员想查看同时带有某几个标签的所有商品,可以先SUNIONSTORE这些标签集合的并集,再进一步做交集或其他分析。如果并集结果需要频繁被多个接口读取,提前存储能显著降低实时计算压力。
不过,SUNIONSTORE也不是没有代价。目标集合会占用内存,而且如果源集合数据频繁变动,存储的并集结果会过期,需要定期重新计算或依赖业务层维护。在内存敏感的环境中,建议评估目标集合的存活时间,合理设置EXPIRE。另外,由于SUNIONSTORE是原子操作,在执行大集合并集时可能会阻塞Redis事件循环,对于数据量极大的情况(比如千万级元素),可能要考虑使用Redis Cluster下的跨槽限制,因为SUNIONSTORE要求所有涉及的键在同一个哈希槽中,跨槽会报错。在集群模式下,可以通过hash tag保证键落在同一节点。
最后提醒一下,SUNIONSTORE的别名是SUNIONSTORE,有些老版本客户端可能只支持SUNIONSTORE,但命令本质上相同。在编写脚本时,建议先EXISTS检查目标集合是否需要保留,避免误覆盖重要数据。

