凉了呀,面试官叫我设计一个排行榜。 (中)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 凉了呀,面试官叫我设计一个排行榜。 (中)

为了后面分享的顺利进行,我这里只讲几个需要用到的操作:

  • 添加 member 命令格式:zadd key score member [score member ...]
  • 增加 member 的 score 命令格式:zincrby key increment member
  • 获取 member 排名命令格式:zrank/zrevrank key member
  • 返回指定排名范围内的 member 命令格式:zrange/zrevrange key start end [withscores]

先看第一个:添加 member。

比如我们把示意图中的数据添加到到有序集合里面去,语法是这样的:

  • zadd key score member [score member ...]

意思是可以一次添加一对或者多对 score-member,比如下面这两个命令:

  • zadd sport:ranking:20210227 10026 why
  • zadd sport:ranking:20210227 10158 mx 30169 les 48858 skr 66079 jay

image.png


执行之后,返回的数字代表添加成功的 member 个数。

我用专门操作 Redis 的 RDM 可视化工具来查看插入的数据,和我自己画的示意图相差无几:


image.png


接着看第二个:增加 member 的 score

微信运动排行榜的数据是实时更新的。

目前 member 为 why 的步数是 10268,假设我吃完晚饭出门跑步去了,又跑了 5000 步。

这时得更新我的步数,就用 zincrby 命令,语法是这样的:

  • zincrby key increment member

对应上面场景的执行命令是这样的:

  • zincrby sport:ranking:20210227 5000 why

执行完成后,会返回 why 的步数,可以看到从 10026 变成了 15026 :


image.png


所以我们只需要更新 score 就行了,至于排名的变化,Redis 会帮忙保证的。

然后看第三个命令:获取 member 排名

语法是这样的:

  • 获取 member 排名:zrank key member
  • 获取 member 排名:zrevrank key member

首先,排名都是 0 开始计算的。

zrank 是按照分数从低到高返回 member 排名。

zrevrank 是按照分数从高到低返回 member 排名。

比如现在要获取 jay 的排名,用 zrank 返回结果就是 4。

  • zrank sport:ranking:20210227 jay

当用 zrevrank 时,jay 的排名就是 0:

  • zrevrank sport:ranking:20210227 jay

image.png


所以,在微信步数排行榜的这个需求中,步数越多排名越靠前,我们应该用 zrevrank。

第四个需要掌握的命令是:返回指定排名范围内的 member。

  • zrange/zrevrange key start end [withscores] 返回指定排名范围内的 member

这个命令就很关键了。

zrange 是按照 score 从低到高返回指定排名范围内的 member。

zrevrange 是按照 score 从高到低返回指定排名范围内的 member。

在这里,我只演示 zrevrange 的命令。

比如我要获取步数排名前三的 member:

  • zrevrange sport:ranking:20210227 0 2


image.png


这个命令有个可选参数:withscores

当带上这个参数之后,会返回对应 member 的 score:


image.png


你想,这不就是排行榜 top N 的场景吗?

假设我现在要获取所有用户的排名,怎么写呢?

如下:

  • zrevrange sport:ranking:20210227 0 -1


image.png


这就是当前的微信步数排行榜,jay 步数最多,mx 步数最少。

咦,怎么回事,排行榜好久就出来了呢?

你想想,讲完几个 API 操作,好像功能就实现了呢?

是的,确实是这样的,甚至我们只需要这两个 API 就能完成排行榜的需求:

  • zadd key score member [score member ...] 添加 member
  • zrange/zrevrange key start end [withscores] 返回指定排名范围内的 member

好了,如果大家喜欢的话,感谢大家一键三连。本次的文章就到这里了...


image.png


那是不可能的。

索然无味的 API 文章多没有意思啊。

虽然前面的部分我们已经可以基于 Redis 的有序集合加上几个简单的命令,就可以实现排行榜需求了。

但是前面只是铺垫,接下来,好戏才刚刚开始。


再次审视排行榜


上面的微信步数排行榜有个问题,你发现了吗?

就上面这个场景而言,所有人来看,看到的都是这样的排序:


image.png


而真实情况是,每个人看见的数据排行数据来源自己的微信好友,而微信好友各不相同,所以看到的排行榜也各不相同。

这个特性,我们并没有体现出来。

我们上面的场景更加类似于游戏排行榜,所有的人看到的全服排行榜都是一样的。

那么怎么保证我们每个人看到的各不相同呢?

你思考一下,该从什么角度去解决这个问题呢?


image.png


有序集合的 key 不同,就获取到不同的 value 集合。

我们当前的 key 是 sport:ranking:20210227,里面只包含了某一天的信息。

只要我们在 key 里面加上用户的属性就可以了,假设我的微信号是 why。

那么 key 可以设计为这样 sport:ranking:why:20210227。

这样,由于 key 里面多了用户信息,每个人的 key 都各不相同,就像这样的:


image.png


对应的命令如下:

  • zadd sport:ranking:why:20210227 10026 why 10158 mx 30169 les 48858 skr 66079 jay
  • zadd sport:ranking:mx:20210227 7688 赵四 9688 刘能 10026 why 10158 mx 54367 大脚

why 和 mx 看到的都是各自好友某一天的微信步数排行榜。

只要把 key 设计好了,这个问题就迎刃而解了。

但是你仔细思考一下,真的就迎刃而解了吗?

这个问题,我在写第一版的时候可能是被猪油蒙蔽了双眼,没发现。

有种“只缘身在此山中”的味道,一心想着 Redis 了。

你想,如果每个用户都有在redis有一个自己的排行榜,一个用户的分数更新的时候就需要对所有好友的zset更新,这多大的代价啊,对吧?

当以用户为纬度做排行榜的时候,就会出现排行榜巨多的情况,导致维护成本升高。

Redis能做,但不是最佳方案。

那么用什么方案去做呢?

我提个思路吧:

每个用户看到的排行榜不一样,我们其实不用时时刻刻帮用户维护好排行榜。

维护好了,用户还不一定来看,出力不讨好的节奏。

所以还不如延迟到用户请求的阶段。

当用户请求查看排行榜的时候,再去根据用户的好友关系,循环获取好友的步数,生成排行榜。

具体方案,大家自己思考一下吧。

另外多说一嘴,前段时间不是微信支持了修改微信号吗,赢得一大片叫好声。

其实我当时认真的想了一下,从技术上的实现来说这个需求到底有多难。

我不知道有没有历史技术债务在里面。

但是就说当前这个场景,key 里面包含了微信号,注意是微信号,不是微信昵称。

因为在设计之初,产品打包票说:放心,微信号绝对全局唯一,一旦确定,不可变更。

结果呢,现在要变化了。

产品屁颠屁颠的说:怎么实现我不管,这个需求用户呼吁很大,赶紧上线。

你说,对这些类似场景的冲击有多大?

其实冲击也不算特别大,一个字段的变化而已。

但是,微信 14 亿用户啊。

一个简单的需求,涉及到这个体量之后,就一句话:

量变引起质变。

好了,好了,扯远了。说回来。image.png


当我把目光再次放到微信排行榜上的时候,我发现,其实我只是给了一个阉割版的排行榜。

是的,我们现在可以获取到 why 的当前步数是 1680 步,当前排名是 814 名。

比如还是沿用上面的例子,假设现在要获取我的微信好友 jay 的微信步数排行榜情况。

先获取 jay 的名次:

  • zrevrank sport:ranking:why:20210227 jay


image.png


名次为 0,程序里面可以对其进行加一操作。就是第一名了。

接着获取 jay 的今日步数:

  • zscore sport:ranking:why:20210227 jay


image.png


66079,步数也有了。

现在我们知道了:why 的好友 jay 今日运动步数 66079 步,在 why 的微信好友中排第一名。

但是你仔细看,这上面我还漏了两个字段:

  • 微信头像
  • 朋友点赞个数

两个字段应该怎么放呢?

放数据库里面当然可以,但是我们主要还是说一下 Redis 的解决方案。

这个时候其实我们想要存储的是 User 对象,对象里面有这几个字段:昵称、头像图片链接、点赞数、步数。

你说,这个用 Redis 的啥数据结构来存?

可不就得用 Hash 结构了吗。

Hash 结构同样涉及到 key 和 value,那么它们分别是什么呢?

key 就是我们的有序集合的 key 后面再加上好友昵称,比如这样的:


image.png



相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
7月前
|
设计模式 网络协议 Java
美团面试,问的贼细~
美团校招面试涵盖网络(HTTP/TCP/UDP)、框架(Spring的IoC/AOP)、设计模式(静态代理)、编程(手写静态代理)、MySQL(事务隔离级别)、Java基础(数据类型/Integer与int的区别)、HashMap等知识点。面试从自我介绍开始,深入到技术细节,如TCP的三次握手和四次挥手,GET与POST请求的区别,以及MySQL的不可重复读示例。了解更多详情可访问[www.javacn.site](https//www.javacn.site)。
104 1
美团面试,问的贼细~
|
SQL 消息中间件 搜索推荐
面试让人画正十七边形?面试官你长点心好不好?
面试让人画正十七边形?面试官你长点心好不好?
|
人工智能 算法 Java
拼多多2019春招编程题答案
拼多多2019春招编程题答案
|
算法 架构师 中间件
怎样写出让面试官眼前一亮的简历
怎样写出让面试官眼前一亮的简历
165 0
|
XML JSON 网络协议
上月成功拿到字节跳动offer,全靠我啃烂了这份最新面试题
前言 不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备,所谓不打无准备的仗就是这个道理,以下为大家,描述了从面试准备到最后的拿到offer提供了非常详细的目录,建议可以从头看是看几遍,如果基础不错的话也可以挑自己需要的章节查看。
上月成功拿到字节跳动offer,全靠我啃烂了这份最新面试题
|
NoSQL 算法 数据挖掘
凉了呀,面试官叫我设计一个排行榜。 (下)
凉了呀,面试官叫我设计一个排行榜。 (下)
377 0
凉了呀,面试官叫我设计一个排行榜。 (下)
|
SQL NoSQL 前端开发
凉了呀,面试官叫我设计一个排行榜。 (上)
凉了呀,面试官叫我设计一个排行榜。 (上)
194 0
凉了呀,面试官叫我设计一个排行榜。 (上)
再学一道算法题:奥运排行榜
再学一道算法题:奥运排行榜
|
前端开发 Java 开发工具
不吹牛,这样的面试官才牛逼!
不吹牛,这样的面试官才牛逼!
125 0
不吹牛,这样的面试官才牛逼!