排行榜服务
内容总结自《亿级流量系统架构设计与实战》
一、概述
1、概述
排行榜场景:游戏排行榜、商品排行榜、视频排行榜、社交排行榜
排行榜特点:曝光量大、竞争激烈、实时变化、周期滚动
使用关系型数据库存储会遇到性能瓶颈、磁盘IO问题,所以排行榜的实现不太适合关系型数据库。可以使用Redis的ZSET数据结构。ZSET是一种有序集合形式,该集合由Member组成,每个Member都有一个Score(积分),集合会按照Score自动排序。所以,目前Redis的ZSET便成为实现排行榜的首选。
二、精确排行榜
1、基础实现
使用一个ZSET对象代表一个具体的排行榜,其中存储了用户ID和用户积分
- Key:排行榜的名称,用于区分不同的排行榜
- Member,存储用户ID,作为排行实体
- Score,存储用户积分,便于实现按照积分排序的功能
以run_hero游戏排行榜为例:
1)给用户ID为999的用户增加10积分的排行因子
ZINCRBY run_hero 10 999
2)获取整个游戏排行榜
ZREVRANGE run_hero 0 -1 WITHSCORES
3)获取排行榜第101~150的排名
ZREVRANGE run_hero 100 149 WITHSCORES
4)获取用户ID为999用户的积分
ZSCORE run_hero 999
2、同积分处理
对于相同Score的Member,ZSET的排序规则是按照默认的用户ID字段进行排序的。如果在业务上需要按照先到达该积分的用户排在前面,按照如上排行榜的设计,无法满足业务诉求
虽然ZSET不支持按照更新时间排序,但是由于ZSET中的Score是浮点数类型的,我们可以让Scor 的整数部分存储积分,让小数部分存储积分更新时间戳。这样一来,积分更高的用户依然排名更高,而积分相同的用户可以进一步按照更新时间排序。为了实现当积分相同时按照时间先后排序,我们可以预设一个未来时间作为基淮值(比如2050年1月1日0点整),将基准值减去更新排行榜时的系统当前时间戳的值作为小数部分的更新时间戳。如
ZINCRBY run_hero 10 999.123
在后续的业务操作时,就需要对积分进行额外的逻辑处理,将Socre拆分为两部分处理。如果担心事务问题,可以考虑使用Lua脚本完成所有的逻辑
3、大Key处理
如果排行榜用户量巨大,会被视为大Key后。此时针对该Key的操作,需要极为小心。
可能遇到的问题:
- 读取时:分页查询
- 删除时:惰性删除
- 热点Key:将单个排行榜按照某种特点规则进行实例拆分,再分别进行排序。当遇到需要读取排名前100用户时,就分别读取每个实例中的前100,再在内存中进行排序
三、粗估排行榜
如果担心大Key会对Redis的性能产生影响,所以你所在公司的Redis维护团队可能会强制限制在使用Redis时,单个ZSET的大小不能超过10000或者其他阈值。这是确实没有办法使用一个ZSET实现百万人、千万人的排行榜,只能另辟蹊径。
我们可以让前N名用户使用ZSET精确排名,其他用户粗估排名。现在的问题就可以聚焦到如何实现海量用户的粗估排名。这里可以借助二叉树的思想,使用线段树,对我们的积分进行分段排行。
1、结构设计
线段树结构:
![](https://img-blog.csdnimg.cn/img_convert/a0786684cf8d0d85ab1ca245fad12f4d.jpeg)
线段树映射为Redis的Hash结构:
Field | Value |
---|---|
1~100 | 83 |
101~200 | 55 |
201~300 | 31 |
301~400 | 49 |
存在问题:
- 排名不准确:线段树假设一个分段内的用户积分是均匀分布的,所以为精确排名带来了不确定性(这也就是为什么说只能是粗估排名),且分段越大,不确定性越强
- 不支持获取排名列表:线段树只存储每个分段的用户数量,它可以高效支持已知积分用户的粗估排名,但是并不支持获取排行榜有哪些用户参与,以及每个用户的积分
2、更新操作
如果用户当前积分为80分,需要增加50积分。Hash Key调整如下:
- 1~100分段-1
- 101~200分段+1
3、获取积分
如果想要获取粗估排行榜中用户的积分,可以借助计数器服务来实现。使用这种方式的好处是,可以使用很小的内存空间,就记录下用户的积分信息。
三、精确排名与粗估排名结合
完成一个排行榜仅需要三个数据结构:
- 一个ZSET:精确排名一定位次的用户
- 一个HASH:线段树,用于粗估排名
- 一个INT(一个HASH):记录粗估排名中用户的具体积分
1、积分更新流程图
![](https://img-blog.csdnimg.cn/img_convert/2e9655240b7eb7142ccade4f524f3268.jpeg)
2、积分查询流程图
![](https://img-blog.csdnimg.cn/img_convert/7a4b9eae186afa4cc20b532a27930069.jpeg)