redis排行榜 排序方案

637 阅读3分钟

在涉及复杂或者实时要求比较高的排行榜业务场景中,我们通常会选择使用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)相同分数排名相同
此种情形下可采用游标遍历取出集合元素,代码中维护排名大小,
根据遍历元素分数大小来更新排名值,逻辑不复杂,在此不写示例了。