当前位置:首页 > 问答 > 正文

Redis 集合那些事儿,怎么用才能让应用跑得更快点

Redis的集合(Set)是一个非常有用的数据结构,它存储的不是一个个键值对,而是一堆不重复的字符串元素,你可以把它想象成一个没有顺序、也绝不会有重复名字的名单,很多人只用它来简单地存数据、取数据,这其实是大材小用了,用对了方法,它能帮你解决很多棘手的问题,让应用效率提升一大截。

集合的看家本领:去重和快速判断存在

这是集合最基本也是最核心的能力,你的应用要记录一天内所有登录过的用户ID,防止重复记录,如果你用列表(List)来存,每次有新用户登录,你都得把列表遍历一遍,看看这个ID是不是已经存在了,用户量一大,这操作就慢得吓人,但用集合就简单了,直接用 SADD login_today user_id123 命令,Redis会自动帮你检查,如果已经有了,就不添加,没有才加进去,你想知道某个用户今天是否登录过?用 SISMEMBER login_today user_id123 命令,Redis能瞬间给你答案,速度极快,因为它内部使用了哈希表等机制,不像遍历列表那样费时。

根据Redis官方文档对集合命令的介绍,SADDSISMEMBERSREM等命令的时间复杂度都是O(1),这意味着无论集合里有1万个元素还是100万个元素,执行这些操作的速度都几乎一样快,这个特性非常适合需要高频进行成员存在性判断的场景。

用集合运算搞定复杂业务逻辑

集合真正强大的地方在于它能进行交集(SINTER)、并集(SUNION)和差集(SDIFF)运算,这能让你用很少的代码实现很复杂的功能。

举个例子,一个社交平台,你想找出“我”和“朋友A”的共同关注,假设“我”的关注集合叫 user:my_id:following,朋友A的关注集合叫 user:friend_a_id:following,只需要一个命令:SINTER user:my_id:following user:friend_a_id:following,Redis就会立刻返回我们俩共同关注的人的ID列表,如果你自己写程序来实现,得写循环去比较,非常麻烦。

再比如,电商平台给用户推荐商品,可以用差集,找出“喜欢商品A的人也喜欢什么?”可以先有一个喜欢商品A的用户集合 product:A:likers,再有一个目标用户(比如他自己)已经购买过的商品集合 user:xxx:bought,然后用 SDIFF product:A:likers user:xxx:bought,就能找出别人都喜欢但这个人还没买的东西,剔除掉他已经有的,推荐结果更精准。

《Redis实战》这本书里提到,利用集合运算可以优雅地解决很多标签系统、共同好友、兴趣匹配等问题,将原本需要在应用层进行复杂计算的压力转移到了Redis内部,由于Redis是内存操作,这些计算会非常迅速。

让集合跑得更快的几个小技巧

光会用还不够,用得好才能让应用飞起来。

  1. 小心大集合的聚合操作:虽然 SINTERSUNION 这些命令很快,但如果你要对两个包含百万级元素的超大集合求交集,这个操作本身还是会消耗不少CPU资源和时间的,可能会暂时阻塞Redis的其他命令,解决办法是,尽量不要直接对巨大的活集合进行运算,可以考虑定期(比如每天凌晨)用一个后台任务把需要频繁查询的集合运算结果提前算好,存成一个新的集合(比如叫 common_follows:my:friend_a),查询的时候直接 SMEMBERS 这个结果集就行了,速度飞快。

  2. 选择合适的集合命令:当你需要获取集合所有成员时,有 SMEMBERSSSCAN 两个命令。SMEMBERS 会一次性把所有元素都返回给你,如果集合非常大(比如几十万元素),可能会撑爆你的网络带宽或客户端内存,而 SSCAN 命令则是分批、游标式地遍历,不会长时间占用Redis服务器资源,对客户端也更友好。面对未知大小的集合,优先使用 SSCAN

  3. 利用随机弹出处理任务:集合还有一个有意思的命令 SPOP,它能随机移除并返回一个元素,这个特性非常适合用来实现“随机抽奖”或者“分布式任务队列”,你有一个任务池集合 task_pool,多个工作进程可以同时用 SPOP 来“抢”任务,因为Redis是单线程的,它能保证每个任务只会被一个客户端拿到,实现了天然的分布式锁机制,既简单又可靠,相比用列表的 LPOPSPOP 的随机性能避免任务在队列头部堆积,让 worker 处理任务更均衡。

  4. 别忘了设置过期时间:像文章开头提到的“当日登录用户”集合,它通常只在当天有效,你应该在每天第一次添加用户时,用 EXPIRE 命令给这个集合设置一个24小时的过期时间(EXPIRE login_today 86400),这样Redis会自动清理过期数据,你就不用写额外的代码去删除它,既能节省内存,又避免了内存泄漏的风险,这是一种很好的实践。

Redis集合远不止是个存东西的篮子,把它“去重”和“集合运算”的特性和你的业务场景结合起来,多想想“这个判断能不能用 SISMEMBER 解决?”“这个需求能不能用 SINTERSDIFF 一步到位?”,你就会发现很多代码可以简化,性能也能得到显著提升。

Redis 集合那些事儿,怎么用才能让应用跑得更快点