前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis排行榜的设计与实现

Redis排行榜的设计与实现

作者头像
疯狂的KK
修改2020-10-12 09:35:42
1.7K0
修改2020-10-12 09:35:42
举报
文章被收录于专栏:Java项目实战Java项目实战

排行榜zset的经典实现,现在的思路全都是查库的操作,由于业务原因,有些是异步操作,难免存在已经计分,但分数还没有入库,这时去查库,导致与实际的分数不一致的情况,通常排行榜本身的操作不是很频繁,但计分的操作很频繁,但也不排除有些业务场景有些"分数怪"刷分的情况,比如王者荣耀实时排列等。

代码语言:txt
复制
  在实现之前先要了解下zset的原理,说原理之前不得不提下redis的数据结构。

Redis有几种数据类型? 九种

90%的人知道Redis 5种最基本的数据结构;

只有不到10%的人知道8种基本数据结构,

代码语言:txt
复制
--->5种基本+bitmap+GeoHash+HyperLogLog;

只有不到5%的人知道9种基本数据结构,5.0最新版本数据结构Streams;

只有不到1%的人掌握了所有9种基本数据结构以及8种内部编码;

那跟zset有啥关系?

zset对应的数据结构是ziplist,也就是底层是跳跃表的结构,跳表,简单记忆就是链表头上还举个牌子,来人找位置,先看看头上的牌子,如果满足向下查询,不满足直接找别的牌子,然后在牌子与牌子之间寻找,相当于MySQL的查询limit,或者干脆就直接等等于ConcurrentSkipListSet,底层查找是二分查找算法。说了半天你说那么多数据结构干啥?

Geo本身不是一种数据结构,它本质上还是借助于Sorted Set(ZSET),并且使用GeoHash技术进行填充,在使用Redis进行Geo查询时,其内部对应的操作其实就是zset(skiplist)的操作,这回有关系了吧。

排行榜的设计

谈不上设计哈,因为各个业务是不一样的,还是那句话排行榜没什么难的,排行榜的计分才是难得。

当下的计分方式是将数据放入redis进行修改操作,所以说排行榜本就应该用redis来做,但是现在的排行榜计分是直接查库人为算的,这个避免不了。

那么计分的操作是存redis的,会出现计分不准的情况吗?

,如果你取分是从数据库取的,那很会~,因为我现在是将入库操作方队列的,不能保证取分时,队列已经消费到此条数据的。所以才考虑的redis,也算是优化方案吧。另外不建议将计分操作直接放入数据库,对数据库的io过于频繁了,会造成意想不到的后果。

代码语言:txt
复制
1、计分存入redis,利用命令
代码语言:txt
复制
ZINCRBY 命令:为分数值加上增量
代码语言:txt
复制
redisService.incrScoreZset("point",likedUserId.toString(),1);
代码语言:txt
复制
 那减分怎么办?
代码语言:txt
复制
根据实际业务,我这里直接就加分这里-1操作,因为我这肯定是有分数的,不用担心出现负数带来排名不正确的情况。

2、解释下参数

代码语言:txt
复制
redisService.incrScoreZset("排行榜名称",计分人,分数);
代码语言:txt
复制
3、取分计算时一定要取redis的分,不要直接取数据库的。

排行榜的实现

先说个人业务,业务原因,其中一条计分项是需要审核通过后才计分的,机审还好吧,人工审核就会出现什么情况?出现A通过加了10分,但现在人工去忙了,不一会回来给B通过加了2分,这样的情况A可能排在B前,也可能相反,那怎么做?

代码语言:txt
复制
1,定时任务去拉分,审核后存redis
代码语言:txt
复制
2,通过后直接存redis,其实不需要定时任务
代码语言:txt
复制
但现在接口和管理分开的项目啊,都不在我这端,当然redis保证共用一个的话,方案二可行的,大不了去指定redis拉数据么。

计分实现 要有加分减分的闭环操作

代码语言:txt
复制
public Object countPoint(Long pointId,String ponitList, Long userId) {
        String key = RedisConstants.get(pointId.toString());
        String userIdStr = userId.toString();
        boolean flag = redisService.isMember(key, userIdStr);
        DetailVo vo = DetailVo.builder()
                .pointId(pointId)
                .likeCreateTime(new Date())
                .likeUserId(userId)
                .likedUserId(likedUserId)
                .build();
        Map resultMap = new HashMap();
        if (flag) {
            redisService.srem(key, userIdStr);
            vo.setType(2);
            //减分
            redisService.incrScoreZset(ponitList,likedUserId.toString(),-1);
            resultMap.put("no", 0);
        } else {
            redisService.sadd(key, userIdStr);
            //加分
            redisService.incrScoreZset(ponitList,likedUserId.toString(),1);
            vo.setType(1);
            resultMap.put("yes", 1);
        }
        //发送消息
        mQProducer.sendUpdateUp(vo);
        resultMap.put("Number", redisService.scard(key));
        return resultMap;
    } 
    

排行榜实现

网络效果图

排行榜功能

  1. 分数从大到小排列,显示头像,昵称,排行
  2. 排行人数超过10人需要分页
  3. 显示当前用户的分数,排行
  4. 以上数据下拉刷新

这样一来,避免了现在的去数据库查询所有的人数,只要有分数一定在redis里面,直接通过命令即可查询出分数,排行,redis的排序下标从0开始需要加1。

代码语言:txt
复制
1.分数从大到小
代码语言:txt
复制
所有既是0,-1
代码语言:txt
复制
Set<TypedTuple<V>> reverseRangeWithScores(K key, long start, long end);

2.获取指定元素的分数及排行

代码语言:txt
复制
//获取我的分数Double point = redisService.zScoreMe("point", customerId.toString());//我的排名Long myRank = redisService.myRank("point", customerId.toString())+1;
代码语言:txt
复制
3.分页,可以逻辑代码对set分页,然后过滤分数为0的

手动造了点测试数据

本地redis 两个10分的,一个1分,一个2分

10分的用户看下,程序debug

排名 分数

因为别人比我先入列,所以别人排名比我靠前。

即使多条件排序zset也能满足。

代码语言:txt
复制
**话外篇 如何快速过滤手机号黑名单...**

思路:手机号尾号后两位进行分库,既00-99,100个库,redis判断尾号是否存在,存在即是黑名单,比如500亿电话号,分100个库,每个库一亿去查一个数,速度是很快的。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 赵KK日常技术记录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档