排行榜的设计

最近工作需求中有遇到实时排行榜相关的场景,这里也总结和梳理下

存储的选择

存储主要是两种,mysql与redis。但是根据业务场景,对于排行榜的使用包含排行信息查看和排行更新两个功能。这里还有两个需要关注的地方:

  1. 实时。意味着随着数据不断更新,排行榜也是需要实时同步的
  2. 数据量级。需求中的排行榜针对的全体用户,数据量级相对还是比较大的

若使用mysql,可以直接使用order by简单粗暴的就完成了,但是考虑到实际中的使用其效率是非常低的。所以考虑使用redis,并且sorted set完全满足所有需要的场景,命令zadd用于更新排行榜的数据,命令zrank可以查看相应的排名,命令zrangebyscore/zrevrangebyscore可以查询排行榜数据。

同分排行处理

对于同分情况,默认会根据member的字典序排序,但是可能这并不是理想的情况,以下主要讨论同分不同名同分同名两种场景

同分不同名

对于同分并且不要求同名的情况,一般采用实际分数和时间戳相结合得到最终的分数,当然根据需求还可以引入别的因素进行排行。

一个int类型最大值是2^31-1(包含±),2,147,483,647,大约是10个十进制位。
一个double类型最大值是2^63-1(包含±),9,223,372,036,854,775,807,大约是19个十进制位。
而ZSet的分数是 64位的有符号长整型,也就是约19个十进制位。

所以去掉毫秒级别13个十进制位的时间戳,还剩6个十进制位,能存储999999个数据。

有个小问题就是分数是越大排名越靠前,但是时间戳得越小排名越靠前,这里可以采用“实际分数 + 13个9 - 13位时间戳”作为分数。

同分同名

对于要求同分同名的场景,这时仅靠一个zset已经是无法满足需求了。需要考虑多个数据结构一起使用

  • A: zset/hash。记录排名信息(member-score)
  • B: zset。放不重复的分数排名,其中member为A中的score值
  • C: hash。hahs记录score以及该分数成员的数量

不过将数据分散在多个key进行存储,需要考虑跨key原子操作,如果更新中某个操作失败了,可能导致排行榜信息错乱了。