Redis - 通过SortedSet实现排行榜功能(解决同score下按照时间顺序排名)

背景

618活动需要设计一个用户排行榜的功能,考虑到redis有SortedSet数据结构(由跳表 + 字典实现),比较适合实现排行榜。

遇坑

需求的场景是,如果两个用户的订单数量相同,那么先到达该订单数量的用户排在前面
一开始我先考虑的问题是:
在SortedSet中,如果score相同,是怎么排序的呢?
后来知道,如果score相同是按照member的字典顺序,即a排在b的前面,1排在2的前面。

那我是不是可以把时间戳加到SortedSet的member中,如 时间戳_用户id。为了让时间戳小的排在前面,我们可以把memebr的设计改成 (Long.MAX_VALUE - 时间戳)_用户id。
这样以后,感觉解决问题了。当时间戳越小,对应的member字典序就会越大,排在前面

殊不知,我们知道,SortedSet中的元素是唯一的(即SortSet中的member是唯一的),而我们的设计中,时间戳是会随着订单变化的。如果往SortedSet新增一个元素,会出现重复的两个。
如图:
在这里插入图片描述
同一个用户的数据会出现两条。
这里有两个问题:
1、在修改数据的时候,需要将原来的member删除,再新增
2、因为时间戳是会变化的,需要记录uid与member的映射关系

对于redis的操作次数都是很多的,虽然能够实现,但是非常绕。对redis不友好,不利于支持大数据量的排行榜。

解决方案

为了处理同score的用户的排名问题,可以把时间戳考虑到score里面。具体思路:

1、假设用户a的订单量为 40 单。最后一笔的订单下单时间戳是:1655567999
2、定义一个基准时间,可以是2050年(2539180799)、2100年这样。
3、给订单量 加上 (基准时间 - 下单时间)/ 基准时间。(基准时间 - 下单时间)/ 基准时间一定是小于1的。

在score的设计上:

    /**
     * 计算score,通过一个基准时间,可以是2100或2050年,减去lastOrderTime再除以基准时间,可以获得一个小于1的小数,
     * 在获取真正score的时候,只要舍去小数位即可
     * @param orderNum
     * @param lastOrderTime
     * @return
     */
    private double getOrderNum(int orderNum, long lastOrderTime) {
        return orderNum + (BASE_TIME - lastOrderTime) * 1.0 / BASE_TIME;
    }

4、在真正取score的时候,取整数位即可。

代码示例

    /**
     * 更新排行榜数据
     * @param ownerUid
     * @param lastOrderTime
     * @param orderNum
     */
    private void doUpdateCommunityRankingList(Long ownerUid, long lastOrderTime, int orderNum) {
        // 插入排行榜信息,对于zset,如果已经包含member,add的时候返回就是false
        redisTemplate.opsForZSet().add(ZSET_KEY, ownerUid.toString(),
                getOrderNum(orderNum, lastOrderTime));
    }

    /**
     * 计算score,通过一个基准时间,可以是2100或2050年,减去lastOrderTime再除以基准时间,可以获得一个小于1的小数,
     * 在获取真正score的时候,只要舍去小数位即可
     * @param orderNum
     * @param lastOrderTime
     * @return
     */
    private double getOrderNum(int orderNum, long lastOrderTime) {
        return orderNum + (BASE_TIME - lastOrderTime) * 1.0 / BASE_TIME;
    }

取出值的时候通过强转取整:

  long value =(long) t.getScore().doubleValue();
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值