在涉及复杂或者实时要求比较高的排行榜业务场景中,我们通常会选择使用redis的zset来实现排行榜的功能 首先我们来看一下zset的一些特性:
1、zset中的成员根据关联的分数score进行排序,分数越小,在zset中的位置越靠
2、zset的成员唯一存在,score可以重复
3、若score相同,根据成员的值value排序,value越小,在zset的位置越靠前
4、zset基于hash表实现,添加删除操作时间复杂度都为O(1),最大成员数为 2^32 - 1,
每个集合可存储40多亿个成员
其次来看一下zset的一些基本操作:
zset add key score1 value1 [score2 value2]
向有序集合添加一个或多个成员,或者更新已存在成员的分数
zcard key
获取有序集合的成员数
zcount key min max
获取有序集合中指定分数区间的成员数 zcount myZset 1 3
zlexcount key min max
获取指定成员区间的数量 例如: zlexcount myZset [a [c ([a: 成员值为a )
zrange key start stop withscores
获取指定索引下标区间的成员+分数
zrangebylex key min max
获取指定成员区间的数量
zrangebyscore key min max withscores
通过分数返回有序集合指定区间内的成员+分数
zrem key value1 [value2]
移除集合中的一个或者多个成员
zrev...
zrev+[range...,lex...,count... ...] 倒置然后获取数据,集合本身是按分数从小到大排序
倒置后,分数从大到小排序,然后取出
zscan key cursor [MACTH pattern][COUNT count]
通过游标迭代集合中的元素
完整参考:
www.runoob.com/redis/redis…
最后我们整理下使用zset实现排行榜功能的思路:
排行榜命名:
根据实际业务场景,例如:排行榜若分为集团、部门,可以固定前缀+部门id命名zset集合
排行榜更新:
添加相关成员或者相关成员变化,更新排行榜zadd key ...
相同分数处理:
相同分数情况下,根据业务逻辑,通常需要不同的处理逻辑
1)相同分数下,按姓名拼音排序
根据姓名拼音计算一个不影响分数的分数干扰因子,例如实际分数保留为2位小数,
则干扰因子可为小于三位或者更小的小数,下边给出一个计算示例:
private static final int MAX = 4;
public static Double scoreSalt(String userName){
if(StringUtils.isNullOrEmpty(userName)){
return 0.0;
}
//toUpperCase 为了降低char的数量级 大写的char在100内
char[] chars = userName.toUpperCase().toCharArray();
Double total = 0.0;
for(int i=0; i<chars.length; i++){
//尽量扩大不同索引位置处的差异 避免相加结果影响排序
total += (chars[i] / (Math.pow(10.0, i+1))) * Math.pow(10, (MAX-i));
}
return 1/total;
}
即存入zset的score为真实score+干扰因子
2)相同分数排名相同
此种情形下可采用游标遍历取出集合元素,代码中维护排名大小,
根据遍历元素分数大小来更新排名值,逻辑不复杂,在此不写示例了。