欢迎来到「我是真的狗杂谈世界」,关注不迷路
背景
最近需要将遇到的几个排行需求点抽出来做一个独立的通用排行组件,整理记录一下。
核心需求
- 能获得连续的部分的榜单:比如前100名、第300~400名的用户以及分值
- 能获得任意单用户信息:比如用户A的分值与当前排名
- 能操作榜单单用户分值:比如为用户A增加3分
- 上述功能要求实时
- 分值相同时,要求按照达到分值时间的先后排序,先达到分值的用户排在前面
- 分值是整数(或类似金额这种有限位小数,也可以看为整数),业务上一般高精度小数无场景意义
思考过程
说白了就是排序问题,相关的算法和结构有很多了,可问题是总不能自己从零开始实现吧(也不是不可以)~~
那么在现有的存储介质上很容易想到Redis的ZSet数据类型(MySQL不是今天的主题,假装想不到咳咳)
Redis ZSet
底层结构与操作过程
ZSet数据类型的主要数据结构是跳表,具有多层级结构(因此对内存要求稍稍高一点),具体的结构和操作过程在「Tool 1.Redis捣腾系列」中继续捣腾。
相关功能情况
现在只关注上述需求用到的几个操作是否支持以及性能开销情况:
- ZADD/ZINCRBY: O(log(N))
- ZSCORE: O(1)
- ZCARD: O(1)
- ZRANGE/ZREVRANGE: O(log(N)+M);
N
为有序集的基数,而M
为结果集的基数 - ZRANK/ZREVRANK: O(log(N))
O(log(N))的时间复杂度理论上完全可以扛住上亿数据的并发查询(当然并不建议真的在生产环境这么干)。
O(log(N)+M)需要特别注意,M是线性增长的,需要严格控制上限
一些特点
ZSet在使用上是member->score结构:
- member: 被排序的标识,也是默认的第二排序维度(score相同时,redis以member的字典序排列)
- score: 被排序的分值,存储类型是double
双维度问题
如果直接按照上述用法进行使用,那么只有第一排序维度score是我需要的,虽然有第二排序维度member,而需要的第二排序维度是时间。那怎么办呢?