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

Redis里怎么灵活玩转组合key,避免踩坑又高效用法分享

说到Redis,很多人刚开始用的时候,最喜欢的就是用简单的key,比如user:123来存用户信息,这没错,很简单直接,但业务一复杂,问题就来了,你想查“某个用户收藏了哪些文章?”或者“某个标签下最近三天有哪些新帖子?”,这时候,如果你只用单个key,要么得查全量数据在内存里自己过滤(慢且耗资源),要么就得维护一堆很难管理的key,这就是我们今天要聊的,怎么灵活地玩转组合key。

组合key的核心思想,其实不是把key的名字取得花里胡哨,而是利用Redis丰富的数据结构,把有关联的数据巧妙地组织在一起,目的就是为了让你能用最少的网络请求,最高效地查到想要的数据。

第一招:用Set(集合)搞定多对多关系

这个招数在社交场景里特别管用,用户和用户之间的关系,用户和兴趣标签之间的关系,根据Redis官方文档对Set数据结构的介绍,Set里的元素是唯一的、无序的,非常适合用来存储这种关系。

举个例子,你有两个用户,ID分别是1和2,用户1关注了用户2,你怎么存呢?你可以为每个用户创建两个Set类型的key:

  • user:1:followings -> 这个Set里存放的是用户1关注的所有人的ID,{2, 3, 5}
  • user:2:followers -> 这个Set里存放的是关注用户2的所有人的ID,{1, 4}

你看,这样存储之后,查询就变得异常简单:

  • 查“用户1关注了谁?”:直接SMEMBERS user:1:followings,一步到位。
  • 查“用户2被谁关注了?”:直接SMEMBERS user:2:followers,也是一步到位。
  • 甚至查“用户1和用户3的共同关注?”:直接用Set的交集命令SINTER user:1:followings user:3:followings,Redis帮你算得好好的。

这种方法的好处是,关系维护和查询都非常快,完全是O(1)或者O(N)的复杂度(N是集合大小),避免了在应用层做复杂的循环和过滤。

第二招:用Sorted Set(有序集合)玩转排行榜和时间线

Sorted Set是Set的升级版,每个元素都带一个分数(score),这个分数太有用了,可以用来排序,根据Redis实战案例,这个结构是构建排行榜、延迟队列、时间线系统的神器。

做一个文章点赞排行榜,你可以创建一个Sorted Set类型的key,叫article:likes:rank

  • 键(key):article:likes:rank
  • 值(member):文章ID
  • 分数(score):这篇文章获得的点赞数

当有人给一篇文章点赞时,你就执行ZINCRBY article:likes:rank 1 文章ID,这个命令非常聪明,如果文章ID已经存在,就给它的分数加1;如果不存在,就创建它并设置分数为1,要查Top 10的文章?一句ZREVRANGE article:likes:rank 0 9 WITHSCORES就搞定了,效率极高。

再比如,做朋友圈的时间线,每个用户发一条状态,你可以这样做:

  • 为每个用户维护一个Sorted Set,key叫user:123:feed,member是状态ID,score是发布时间戳。
  • 维护一个全局的Sorted Set,key叫global:feed,同样存放所有状态ID和发布时间戳。

当用户A关注用户B后,可以把用户B最近的状态ID,合并(ZUNIONSTORE)到用户A的个人时间线Sorted Set里(实际实现会有更优化的方式,比如推拉结合),这样,用户查看朋友圈时,直接按score倒序从自己的时间线Sorted Set里取就行了,速度飞快。

第三招:用Hash(哈希)来分组管理属性

一个对象有很多属性,比如用户有姓名、年龄、城市等等,如果你用普通的String类型,每个属性存一个key(如user:123:name, user:123:age),那么取用户完整信息就要跑很多次网络请求,这叫“缓存原子性”问题,很亏。

这时候,Hash结构就派上用场了,根据Redis命令参考,Hash允许你在一个key里面存储多个字段(field)和值(value),你可以把整个用户信息存成一个Hash:

  • key: user:123
  • 字段和值:name "张三", age "30", city "北京"

这样,操作就高效多了:

  • 取用户所有信息:HGETALL user:123,一次网络往返就拿全了。
  • 只更新用户年龄:HSET user:123 age 31,只修改一个字段,不影响其他。
  • 只获取用户名和城市:HMGET user:123 name city,可以按需获取。

这种方法特别适合存储那些字段多、但可能不会每次都全部用到的对象,既保证了操作的原子性,又减少了网络开销。

避免踩坑的小提示

  1. 别让key太长:key的名字要有意义,但也别太长,比如system:config:database:mysql:master:connection:pool:max_size这种,虽然清晰,但浪费内存,可以用缩写或者编码替代长单词。
  2. 警惕大key:一个Set里面有几百万个元素,或者一个Hash有几千个字段,这种叫“大key”,大key会导致操作变慢,甚至阻塞Redis,解决办法是拆分,比如按日期拆分article:likes:20240521,或者按ID取模拆分到多个key里。
  3. TTL设置要小心:给你组合key设置过期时间时,要确保相关的key都有合理的过期策略,别用户信息过期了,他对应的关注列表还没过期,导致数据不一致。
  4. 命名要有规律:比如都用冒号分隔,形成一种命名空间。user:123:profile, user:123:followers,这样在管理(比如用KEYS user:123:*模式匹配)或者清理时,会非常方便。

玩转Redis组合key,精髓在于“用数据结构表达数据关系”,Set管归属、Sorted Set管排序、Hash管对象,避开大key和长key的坑,根据你的查询需求去设计key的结构,而不是简单地把数据库表结构照搬过来,这样,你的Redis用起来就会既灵活又高效。

Redis里怎么灵活玩转组合key,避免踩坑又高效用法分享