TairZset是阿里云自研的数据结构,可实现256维度的double类型的分值排序。
原生Zset痛点
原生Redis支持的排序结构Sorted Set(也称Zset)只支持1个double类型的分值排序,实现多维度排序时较为困难。例如通过IEEE 754结合拼接的方式实现多维度排序,此类方式存在实现复杂、精度下降、ZINCRBY命令无法使用等局限性。
TairZset介绍
借助阿里云自研的TairZset数据结构,可帮助您轻松实现多维度排序能力,相较于传统方案具有如下优势:
最大支持256维的double类型的分值排序(排序优先级为从左往右)。
对于多维score而言,左边的score优先级大于右边的score,以一个三维score为例:score1#score2#score3,TairZset在比较时,会先比较score1,只有score1相等时才会比较score2,否则就以score1的比较结果作为整个score的比较结果。同样,只有当score2相等时才会比较score3。若所有维度分数都相同,则会按照元素顺序(ascii顺序)进行排序。
为了方便理解,可以把#想象成小数点(.),例如0#99、99#90和99#99大小关系可以理解为0.99 < 99.90 < 99.99,即0#99 < 99#90 < 99#99。
支持EXZINCRBY命令,不再需要取回当前数据,在本地增加值后再拼接写回Tair。
支持和原生Zset相似的API。
提供 普通排行榜 和 分布式架构排行榜 的能力。
提供开源TairJedis客户端,无需任何编解码封装,您也可以参考开源自行实现封装其他语言版本。
关于本文中使用的TairZset相关命令,详细解释,请参见exZset。
应用场景
排序需求常见于各类游戏、应用、奖牌等排行榜中,通常业务对排序的需求如下:
支持增删改查和反向排序,可根据分数范围获取相应用户。
快速返回排序请求的结果。
具备扩展能力(即 分布式架构排行榜 ),在数据分片容量或计算能力不足时,可以将其扩展到其他数据分片。
实现奖牌榜
排名 | 参与方 | 金牌 | 银牌 | 铜牌 |
1 | A | 32 | 21 | 16 |
2 | B | 25 | 29 | 21 |
3 | C | 20 | 7 | 12 |
4 | D | 14 | 4 | 16 |
5 | E | 13 | 21 | 18 |
6 | F | 13 | 17 | 14 |
在奖牌榜中,从金、银、铜牌的维度对参与方进行排名,先按照金牌数量排序;如果金牌数量一致,再以银牌数量排序;如果银牌数量也一致,再按照铜牌数量排序。在上述数据中,参与方E和F的金牌数相等,但是银牌数参与方E大于F,因此参与方E排名靠前,通过TairZset的多维排序能力,您只需要使用简单的API即可完成该需求。
客户端设置依赖如下,采用阿里云的TairJedis-SDK。
<dependency>
<groupId>com.aliyun.tair</groupId>
<artifactId>alibabacloud-tairjedis-sdk</artifactId>
<version>1.6.0</version>
</dependency>
示例代码如下:
JedisPool jedisPool = new JedisPool();
// 创建排行榜
LeaderBoard lb = new LeaderBoard("leaderboard", jedisPool, 10, true, false);
// 如果金牌数相同,按照银牌数排序,否则继续按照铜牌
// 金牌 银牌 铜牌
lb.addMember("A", 32, 21, 16);
lb.addMember("D", 14, 4, 16);
lb.addMember("C", 20, 7, 12);
lb.addMember("B", 25, 29, 21);
lb.addMember("E", 13, 21, 18);
lb.addMember("F", 13, 17, 14);
// 获取 A 的排名
lb.rankFor("A"); // 1
// 获取top3
lb.top(3);
// [{"member":"A","score":"32#21#16","rank":1},
// {"member":"B","score":"25#29#21","rank":2},
// {"member":"C","score":"20#7#12","rank":3}]
// 获取整个排行榜
lb.allLeaders();
// [{"member":"A","score":"32#21#16","rank":1},
// {"member":"B","score":"25#29#21","rank":2},
// {"member":"C","score":"20#7#12","rank":3},
// {"member":"D","score":"14#4#16","rank":4},
// {"member":"E","score":"13#21#18","rank":5},
// {"member":"F","score":"13#17#14","rank":6}]
实现实时、小时、日、周和月维度的排行榜
该场景下的需求是实现月榜,那么这个Key就从月的维度进行索引。
利用TairZset的多级索引能力可以轻松实现不同时间范围的排行榜。本案例中,月度的所有数据存储在1个Key中(名称为julyZset),写入演示数据如下:
EXZINCRBY julyZset 7#2#6#16#22#100 7#2#6#16#22_user1
EXZINCRBY julyZset 7#2#6#16#22#50 7#2#6#16#22_user2
EXZINCRBY julyZset 7#2#6#16#23#70 7#2#6#16#23_user1
EXZINCRBY julyZset 7#2#6#16#23#80 7#2#6#16#23_user1
7#2#6#16#22#100
表示7月第2周6号16点22分,更新其分数为100。7#2#6#16#22_user1
表示此时间点更新的用户,用户名加入了具体时间前缀。
排行榜类型 | 具体实现的命令和返回结果 |
小时级别实时排行榜,即从当前时间往前推算一个小时(例如16:23~15:23)。 说明 如果访问非常频繁,可以将结果进行缓存。 | 查询命令:
返回结果:
|
固定1小时排行榜,例如查询16:00~17:00时间段的排行榜。 | 查询命令:
返回结果:
|
日排行榜,例如查询7月5号的日排行榜。 | 在查询前,插入一条7月5号的数据:
返回结果:
查询命令:
返回结果:
|
周排行榜,例如查询7月第2周的排行榜。 | 查询命令:
返回结果:
|
月排行榜,例如查询7月的月排行榜。 | 在查询前,插入一条7月20号的数据:
返回结果:
查询命令:
返回结果
|