最近看完了Redis的几种数据类型的数据结构之后,反思了一下之前面试中被问到的问题。有的问题在面试时还以为面试官在考察大数据方面相关的知识,被问的一脸懵逼,随着对Redis的了解,才知道当时面试的时候自己有多沙雕。

  之前主要关注的是Redis的主备、哨兵、集群模式,AOF和RDB机制,如何做分布式锁这类文章,看到有关底层数据结构的内容就理解的不够深入,只有大概的印象,到了面试的时候很多基础的内容答不上来。

  面试之后我对自己的答案还保持了几天的蜜汁自信。
稳了.jpeg
  现在再回想自己面试时的答案,只能装弱智。
弱智.jpeg

  下面是之前面试过程中的一些问题。

  1. 查看直播间实时观看人数,个别大主播会有千万在线观众,通过直播间要知道那些用户在直播间内,通过用户要知道当前在哪个直播间和在当前直播间的停留时间。

    • 面试时的回答:

      • 用户id当做key,value中记录当前直播间id和进入直播间时间戳,key设置过期时间,建立webscoket或轮询记录进入直播间和退出事件,定时发送心跳更新key的过期时间,通过当前时间戳和value中的时间戳比较,得出停留时间。
      没想到如何解决关闭浏览器后无法获取用户下线导致退出直播间的事件。
      • 直播间id当做key,用Set存储直播间内的用户id,统计直播间Set集合的key数量得到直播间内人数。
      对于大主播的直播间,当时想到的方案是使用Hash方式将Set集合拆分成多个集合。
    • 现在的回答:

      • 解决关闭浏览器无法获取退出事件。
      监听key的过期事件,退出浏览器后心跳结束,一段时间内key会过期,获取到过期事件后执行退出直播间动作。
      • 解决直播间人数在千万级的情况下,使用Set存储用户id导致空间使用过大。
      使用BitMap存储当前直播间的用户,使用BitMap可以极大的节省存储空间,1千万用户只需要 10000000 / (8 * 1024 * 1024) = 1.2MB的内存。
  2. 英雄联盟排行榜10亿用户查看top100用户,查看当前用户自身排名。

    • 面试时的回答:

      • 使用MySQL存储,根据不同大区进行分表,假设平均每个大区1千万数据,分一百个表,定期缓存总排行top100,缓存任务方式为每张表取top100然后两两合并,可以通过并发等方式优化取最终结果。
      没想到如何找到当前用户的排行名次。
    • 现在的回答:

      • 使用Redis的sorted set存储全部用户的排位分数,以分数作为score 用户id作为value。sorted set使用跳表结构存储数据,sorted set支持按score排序进行范围查找,并且支持根据value查找对应的score排名。sorted set最大成员数为2^32 - 1能存储40亿数据。
  3. 获取包含某个前缀的key的集合
  • 面试时的回答:

    • 没答上来,只说了我知道在集群模式下可以通过对key添加{}可以指定key中的某一部分进行hash,可以将某些key路由到同一个节点上。
    我之前看过获取key集合时不能一起全部获取,在key很多的情况下会导致Redis执行时间过长导致卡死,所以需要用迭代器的方式逐步扫描key,但是不知道如何按匹配的方式查找key。
    • 现在的回答:

      • Redis的scan命令支持pattern参数,可以在迭代的时候匹配到包含某个字符串的key。hash,set,sorted set类型也有各自的scan命令。
  1. redis集群模式有什么要注意的。
  • 面试时的回答:

    • 没答上来,只是搭建过Redis的哨兵和集群模式,生产环境没有使用过集群模式。
    • 现在的回答:

      • 从网上查找知道了一种集群下会发生的问题。集群模式下使用lua时,脚本中涉及到的key要属于同一个node,否则会报错。解决的方式可以通过给key中部分内容加上{},使需要操作的key都能路由到同一个node节点上。
      这只是我查到的一种异常情况,真正使用的过程中肯定会遇到各种各样的问题。
  1. redis底层数据结构-动态字符串。

    • 面试时的回答:

      • 没答上来,之前看过介绍Redis底层数据结构的文章,只是看了一遍,并没有仔细的了解每种类型的特性,只有一个大概的概念,动态字符串在扩容时会预留出一部分空间,字符串改变的时候可以减少重复分配空间操作,小于1M每次扩容时空间翻倍,大于1M每次多分配1M。
  • 现在的回答:

    • Redis的每种类型都有几种编码方式对应,redisObject中会记录当前对象是哪种类型和此类型下使用那种编码方式,还会有一个属性指向当前对象的底层数据结构。embstr和raw编码的数据结构实现方式是动态字符串。动态字符串会存储字符数组长度和空闲空间。储字符数组长度的优点是O(1)的时间复杂度获取字符串长度。通过记录空闲空间,SDS 实现了空间预分配和惰性空间释放两种优化策略。长度小于等于 39 个字节的字符串,编码类型为 embstr,底层数据结构则是 embstr 编码 SDS。embstr 编码是专门用来保存短字符串的,它和 raw 编码最大的不同在于:raw 编码会调用两次内存分配分别创建 redisObject 结构和 sdshdr 结构;而 embstr 编码则是只调用一次内存分配,在一块连续的空间上同时包含 redisObject 结构和 sdshdr 结构。对于 embstr 编码来说,只要修改了字符串的值,此时字符串对象的编码就会从 embstr 变为 raw。

  看完Redis一些相关文章后,我又想到以下使用场景。

  1. 记录某内容的点赞数量可以使用bitmap,因为点赞可能会出现取消点赞的情况,并且需要看到自己是否点过赞,所以不能只用HyperLogLog记录总数。
  2. 只记录视频播放数量可以使用HyperLogLog,实现HyperLogLog的算法使得总数中是去除用户重复观看次数的。如果需要记录用户的观看记录可以使用bitmap。

哦豁
1 声望1 粉丝