使用redis中的zset实现实时排行榜 您所在的位置:网站首页 景点人数实时排行榜 使用redis中的zset实现实时排行榜

使用redis中的zset实现实时排行榜

2024-05-25 10:11| 来源: 网络整理| 查看: 265

前言

redis在业务开发中会被频繁使用,zset是其中一种特殊用法,zset具排行榜的天然特性,我前几个月在一次开发中使用到了zset,就是因为涉及到要实现一个排行榜,那是我第一次用到zset,虽然之前都看过redis几种数据类型的数据结构及其使用方法,但是真正用起来的时候,还是有一些细节的东西要处理的。

 

产品需求

用户在某条赛道上的跑步数据排行榜,以用户完成赛道的最佳成绩(最快圈速)为排序,做出排行榜。

当时产品给的需求是排行榜要实时变更,之前我们app里面也有其他业务的排行榜,但都是月排行榜、周排行榜,实现方法就是先捞mysql的数据,然后放到redis中,过期时间就是一周或者一天(昨日最佳)。但是这一次产品要求的是要实时变更的,这时候如果实时取mysql,再以某个过期时间存储到redis,就不合适了,因为一放完redis,某个用户完赛后,如果要使排行榜数据是实时的,就得马上reset一下redis中的数据,同时也得操作一次数据库,这样就显得有点累赘了,过期时间也没有存在的必要了。退一万步讲,从redis拿出来的数据也不是有序的,我们还得重排序一次。

 

技术背景

作为公司的新手菜鸟自然不敢自己做技术选型,我当时接到这个需求就想到了zset可能是可以实现这个功能的,后来跟我们的技术老大说了我的想法,她也支持使用zset,于是乎,开了技术评审后,我使用了zset来实现。

 

zset简单介绍

  zset 和 set 很像,都是字符串的集合,都不允许重复的成员出现在一个 set 中。

他们的区别在于有序集合中每一个成员都有一个分数(score)与之关联,redis 正是通过分数来对集合里的成员进行从小到大的排序。尽管有序集合中的成员必须是惟一的,但是分数(score)却可以重复。

 

工具类 /** * 新增或者更新数据 * * @param sortedSetKey key(用于区分不同的排行榜) * @param member 排行榜对象 * @param score 排行依据(值) * 如果在key中,不存在该member,则新增,如果存在,则更新 */ public void addOrUpdate(String sortedSetKey, int member, double score) { redisTemplate.opsForZSet().add(sortedSetKey, member, score); } /** * 获取排行榜指定范围的内容 * * @param sortedSetKey key(用于区分不同的排行榜) * @param startIndex 起始值(从0开始) * @param endIndex 结束值 * @return */ public Set reverseRange(String sortedSetKey, int startIndex, int endIndex) { return redisTemplate.opsForZSet().reverseRange(sortedSetKey, startIndex, endIndex); } /** * 获取排行榜指定范围的内容,反序 * * @param sortedSetKey key(用于区分不同的排行榜) * @param startIndex 起始值(从0开始) * @param endIndex 结束值 * @return */ public Set range(String sortedSetKey, int startIndex, int endIndex) { return redisTemplate.opsForZSet().range(sortedSetKey, startIndex, endIndex); } /** * 获取排行榜前N的数据 * * @param sortedSetKey key(用于区分不同的排行榜) * @param number * @return */ public Set top(String sortedSetKey, long number) { return redisTemplate.opsForZSet().reverseRange(sortedSetKey, 0L, number); } /** * 获取排行榜start到end的value以及他的score * * @param sortedSetKey key(用于区分不同的排行榜) * @param start 从0开始算起 * @param end * @return */ public Set rangeWithScore(String sortedSetKey, int start, int end) { return redisTemplate.opsForZSet().rangeWithScores(sortedSetKey, start, end); } /** * 获取排名 * * @param sortedSetKey key(用于区分不同的排行榜) * @param member 排行榜对象 * @return */ public Long getRankNum(String sortedSetKey, int member) { return redisTemplate.opsForZSet().rank(sortedSetKey, member); } /** * 获取score * * @param sortedSetKey * @param member * @return */ public Double getScore(String sortedSetKey, Object member) { return redisTemplate.opsForZSet().score(sortedSetKey, member); } /** * 批量新增/更新数据 * * @param sortedSetKey key(用于区分不同的排行榜) * @param tuples Set tuples = new HashSet(); * long start = System.currentTimeMillis(); * for (int i = 0; i < 100000; i++) { * DefaultTypedTuple tuple = new DefaultTypedTuple("张三" + i, 1D + i); * tuples.add(tuple); * } * 注:这个方法,我没测过 */ public Long addOrUpdateSet(String sortedSetKey, Set tuples) { if (null != tuples && !tuples.isEmpty()) { return redisTemplate.opsForZSet().add(sortedSetKey, tuples); } return 0L; } /** * 删除数据 * * @param sortedSetKey (用于区分不同的排行榜) * @param member 排行榜对象 * @return */ public Long remove(String sortedSetKey, Object member) { return redisTemplate.opsForZSet().remove(sortedSetKey, member); } /** * 查询,范围内的数据 * * @param sortedSetKey key(用于区分不同的排行榜) * @param startIndex 起始值(从0开始) * @param endIndex 结束值 * @return */ public String getRangeData(String sortedSetKey, Long startIndex, Long endIndex) { Set rangeWithScores = redisTemplate.opsForZSet().reverseRangeWithScores(sortedSetKey, startIndex, endIndex); return JSON.toJSONString(rangeWithScores); } /** * 增量添加score * * @param sortedSetKey key(用于区分不同的排行榜) * @param member 排行榜对象 * @param addScore 添加的分数 */ public void incrementScore(String sortedSetKey, String member, double addScore) { redisTemplate.opsForZSet().incrementScore(sortedSetKey, member, addScore); } /** * 获取当前redis中排名的数据的总个数 * * @param sortedSetKey * @return */ public Long getSize(String sortedSetKey) { return redisTemplate.opsForZSet().size(sortedSetKey); } /** * 获取排行榜所有的数据 * * @param sortedSetKey key(用于区分不同的排行榜) * @return 注意:数量多,容易卡死,不建议使用 */ public Set getAll(String sortedSetKey) { Long size = redisTemplate.opsForZSet().size(sortedSetKey); if (size != null) { return redisTemplate.opsForZSet().reverseRange(sortedSetKey, 0L, size); } return Collections.emptySet(); }

 

实现过程

相信看了上面我列出来的文档里面的几个api和上面的工具类,大家对用法也清楚了,其实就是取的时候用rangebyscore,存的时候用zadd,zadd之后zset会自己在内部自动排序,拿出来的结果就是排好序的,不过有个需要注意的地方就是,当score分数一样时,zset会按照unicode编码的自然顺序来排序(这个我就被坑过),以我之前那个需求为例子的话,当score分数一样,要以用户上传跑步数据的时间作升序来排的,就是说如果分数一样,就看谁先完成,谁先完成就排前面,我当时是用分数拼接时间戳的方法。

double timeRank = Double.parseDouble(vo.getFastSpeed() + "." + vo.getLastUploadTime().getTime());

取出来的时候可以直接用Int来接收,就直接去掉了后面的小数点了。如果业务需求是时间越大排越前面,就需要用一个最大时间戳99999999减去业务时间戳,再拼接。

 

深入了解

如果想了解下zset具体的数据结构,可以看下这篇文章https://www.jianshu.com/p/fb7547369655

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有