目录
  • SpringBoot+Redission实现排行榜功能
    • 一、业务需求
    • 二、Redis中的zSet(有序集合)
      • 1.简介
      • 2.特点
      • 3.常用命令
      • 4.测试
      • 5.总结
    • 三、增加时间数据
      • 四、SpringBoot代码
        • 1.引入Redis和Redission依赖
        • 2.application.yml配置
        • 3.Java代码
      • 4、接口文档

      SpringBoot+Redission实现排行榜功能

      demo地址:ranking-demo: 排行榜DEMO (gitee.com)

      一、业务需求

      实现一个排行榜,要求按照分数和达成这个分数的时间排序,即相同分数下,时间早的在上面

      二、Redis中的zSet(有序集合)

      1.简介

      Redis 的 zSet(也称为有序集合)是一种特殊的数据结构,它同时包含了集合和有序列表的特性。在 zSet 中,每个成员都有一个分数(score)与之关联,这个分数可以是浮点数,用于对集合中的元素进行排序。

      2.特点

      • 元素唯一:就像集合一样,zSet 中不允许有重复成员。
      • 有序性:集合中的元素按照其关联的分数值进行升序排序。
      • 操作丰富:支持添加、删除成员,获取指定范围的成员,根据分数查询成员,计算交集、并集、差集等操作。

      3.常用命令

      • ZADD:向有序集合中添加一个或多个成员,或者更新已存在成员的分数。
      • ZRANGE:返回有序集合中指定区间内的成员,通过索引位置来获取,从0开始。
      • ZRANGEBYSCORE:返回有序集合中指定分数区间的成员。
      • ZCARD:获取有序集合的成员数量。
      • ZREM:移除有序集合中的一个或多个成员。
      • ZREVRANGE:类似于 ZRANGE,但返回的是从高分到低分的成员。
      • ZINCRBY:为有序集合中的成员的分数加上给定值。
      • ZCOUNT:计算有序集合中指定分数区间的成员数量。
      • ZRANK/ZREVRANK:获取成员在有序集合中的排名,ZRANK 是从低分到高分,ZREVRANK 是从高分到低分。

      4.测试

      > ZADD zsetkey 1 member1
      1
      > ZADD zsetkey 1 member2
      1
      > ZADD zsetkey 1 member9
      1
      > ZADD zsetkey 1 member5
      1
      > ZREVRANGE zsetkey 0 10 WITHSCORES
      member9
      1
      member5
      1
      member2
      1
      member1
      1
      

      5.总结

      zSet可以很好的实现分数排序,但是在相同的分数下,会按照成员的名称进行排序,所以要在此基础上增加时间

      三、增加时间数据

      为了增加完成时间,我们可以引进一个倒计时的概念,假设一共9秒

      用户A在获得1分的时候在第2秒,那可以在Redis中存储1*10+(9-2) => 18

      用户B在获得1分的时候在第6秒,那可以在Redis中存储1*10+(9-6) => 13

      这样我们在获取分数的时候,可以倒推出分数和完成时间

      四、SpringBoot代码

      1.引入Redis和Redission依赖

      <!-- redis -->  
      <dependency>  
          <groupId>org.springframework.boot</groupId>  
          <artifactId>spring-boot-starter-data-redis</artifactId>  
      </dependency>  
        
      <!-- redisson -->  
      <dependency>  
          <groupId>org.redisson</groupId>  
          <artifactId>redisson-spring-boot-starter</artifactId>  
          <version>3.20.1</version>  
      </dependency>
      

      2.application.yml配置

      --- # redis配置  
      spring:  
        redis:  
          # 地址  
          host: localhost  
          # 端口,默认为6379  
          port: 6379  
          # 数据库索引  
          database: 0  
          # 密码(如没有密码请注释掉)  
          # password:    # 连接超时时间  
          timeout: 10s  
          # 是否开启ssl  
          ssl: false  
        
      --- # redisson配置  
      redisson:  
        # redis key前缀  
        keyPrefix: ${spring.application.name}  
        # 线程池数量  
        threads: 4  
        # Netty线程池数量  
        nettyThreads: 8  
        # 单节点配置  
        singleServerConfig:  
          # 客户端名称  
          clientName: ${spring.application.name}  
          # 最小空闲连接数  
          connectionMinimumIdleSize: 8  
          # 连接池大小  
          connectionPoolSize: 32  
          # 连接空闲超时,单位:毫秒  
          idleConnectionTimeout: 10000  
          # 命令等待超时,单位:毫秒  
          timeout: 3000  
          # 发布和订阅连接池大小  
          subscriptionConnectionPoolSize: 50
      

      3.Java代码

      Constant

      /**  
       * @author Baisu  
       * @classname RankingConstant  
       * @description 排行榜常量数据  
       * @since 2024/5/6  
       */
      public class RankingConstant {  
        
          public static final Long BASIC_QUANTITY = 10000000000000L;  
        
          public static final Long MAXIMUM_TIME_TIMIT = 29991231235959L;  
      }
      

      Controller

      import cn.hutool.core.date.DateTime;  
      import cn.hutool.core.date.DateUtil;  
      import com.ranking.demo.common.R;  
      import com.ranking.demo.common.constant.RankingConstant;  
      import com.ranking.demo.demain.RankingVo;  
      import com.ranking.demo.utils.RankingUtil;  
      import com.ranking.demo.utils.RedisKey;  
      import com.ranking.demo.utils.RedisUtil;  
      import org.redisson.client.protocol.ScoredEntry;  
      import org.springframework.beans.factory.annotation.Value;  
      import org.springframework.web.bind.annotation.*;  
        
      import java.util.ArrayList;  
      import java.util.Collection;  
      import java.util.List;  
        
      /**  
       * @author Baisu  
       * @since 2024/4/28  
       */
      @RestController  
      public class DemoRankingController {  
        
          @Value("${spring.application.name}")  
          private String applicationName;  
        
          /**  
           * 项目启动测试方法  
           *  
           * @return applicationName  
           */    
          @GetMapping("")  
          public String demo() {  
              return applicationName;  
          }  
        
          /**  
           * 生成测试数据  
           *  
           * @return ok  
           */    
      	@GetMapping("/generate_test_data")  
          public R<Object> generateTestData() {  
              RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), 1L, "10001");  
              RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), 2L, "10002");  
              RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), 3L, "10003");  
              RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), 4L, "10004");  
              RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), 5L, "10005");  
              return R.ok();  
          }  
        
          /**  
           * 获取排行榜数据  
           *  
           * @param top 数量  
           * @return 排行榜数据  
           */  
          @GetMapping("/get_ranking")  
          public R<Object> getRanking(@RequestParam("top") Integer top) {  
              Collection<ScoredEntry<Object>> ranking = RedisUtil.getRanking(RedisKey.getRankingDemoKey(), 0, top - 1);  
              if (ranking.size() == 0) {  
                  return R.fail("暂无排行榜数据");  
              }  
              List<RankingVo> list = new ArrayList<>();  
              for (ScoredEntry<Object> entry : ranking) {  
                  RankingVo vo = new RankingVo();  
                  vo.setMember(entry.getValue().toString());  
                  vo.setScore(RankingUtil.getScore(entry.getScore()));  
                  vo.setTime(RankingUtil.getTimeStr(entry.getScore()));  
                  list.add(vo);  
              }  
              return R.ok(list);  
          }  
        
          /**  
           * 增加成员分数值  
           *  
           * @param member 成员  
           * @return 是否增加成功  
           */  
          @GetMapping("/add_score_by_member")  
          public R<Object> addScoreByMember(@RequestParam("member") String member) {  
              Double scoreByMember = RedisUtil.getScoreByMember(RedisKey.getRankingDemoKey(), member);  
              if (scoreByMember == null) {  
                  scoreByMember = 0.0;  
              }  
              RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), RankingUtil.getScore(scoreByMember) + 1, member);  
              return R.ok();  
          }  
        
          /**  
           * 获取成员分数值  
           *  
           * @param member 成员  
           * @return 分数值  
           */  
          @GetMapping("/get_score_by_member")  
          public R<Object> getScoreByMember(@RequestParam("member") String member) {  
              Double scoreByMember = RedisUtil.getScoreByMember(RedisKey.getRankingDemoKey(), member);  
              if (scoreByMember == null) {  
                  return R.fail("该成员不存在");  
              }  
              RankingVo vo = new RankingVo();  
              vo.setMember(member);  
              vo.setScore(RankingUtil.getScore(scoreByMember));  
              vo.setTime(RankingUtil.getTimeStr(scoreByMember));  
              return R.ok(vo);  
          }  
        
      }
      

      Domain

      import lombok.Data;  
        
      /**  
       * @author Baisu  
       * @classname RankingVo  
       * @description 排行榜展示类  
       * @since 2024/5/6  
       */
      @Data  
      public class RankingVo {  
        
          /**  
           * 成员  
           */  
          private String member;  
          /**  
           * 分数值  
           */  
          private Long score;  
          /**  
           * 时间  
           */  
          private String time;  
        
      }
      
      /**  
       * @author Baisu  
       * @classname RedisKey  
       * @description Redis索引  
       * @since 2024/5/6  
       */
      public class RedisKey {  
        
          private static final String RANKING_DEMO_KEY = "ranking_demo";  
        
          public static String getRankingDemoKey() {  
              return RANKING_DEMO_KEY;  
          }  
      }
      
      import cn.hutool.core.date.DateTime;  
      import cn.hutool.core.date.DateUtil;  
      import cn.hutool.extra.spring.SpringUtil;  
      import com.ranking.demo.common.constant.RankingConstant;  
      import org.redisson.api.RScoredSortedSet;  
      import org.redisson.api.RedissonClient;  
      import org.redisson.client.protocol.ScoredEntry;  
        
      import java.util.Collection;  
        
      /**  
       * @author Baisu  
       * @classname RedisUtil  
       * @description Redis工具类  
       * @since 2024/5/6  
       */
      public class RedisUtil {  
        
          private static final RedissonClient REDISSON_CLIENT = SpringUtil.getBean(RedissonClient.class);  
        
          /**  
           * 向有序集合中添加指定分数的成员  
           *  
           * @param key    有序集索引  
           * @param score  分数  
           * @param member 成员  
           * @return 是否成功  
           */  
          public static boolean addScoreByMember(String key, Long score, String member) {  
              RScoredSortedSet<String> rScoredSortedSet = REDISSON_CLIENT.getScoredSortedSet(key);  
              double v = score * RankingConstant.BASIC_QUANTITY + (RankingConstant.MAXIMUM_TIME_TIMIT - Long.parseLong(DateUtil.format(DateTime.now(), RankingUtil.FORMAT)));  
              return rScoredSortedSet.add(v, member);  
          }  
        
          /**  
           * 返回有序集中成员的分数值  
           *  
           * @param key    有序集索引  
           * @param member 成员  
           * @return 分数值(Double)  
           */    
           public static Double getScoreByMember(String key, String member) {  
              RScoredSortedSet<Object> scoredSortedSet = REDISSON_CLIENT.getScoredSortedSet(key);  
              return scoredSortedSet.getScore(member);  
          }  
        
          /**  
           * 返回有序集中指定位置的成员集合  
           *  
           * @param key   有序集索引  
           * @param start 开始索引  
           * @param end   结束索引  
           * @return 成员集合  
           */  
          public static Collection<ScoredEntry<Object>> getRanking(String key, int start, int end) {  
              RScoredSortedSet<Object> rScoredSortedSet = REDISSON_CLIENT.getScoredSortedSet(key);  
              return rScoredSortedSet.entryRangeReversed(start, end);  
          }  
        
      }
      
      import cn.hutool.core.date.DateUtil;  
      import com.ranking.demo.common.constant.RankingConstant;  
        
      /**  
       * @author Baisu  
       * @classname RankingUtil  
       * @description 排行榜工具类  
       * @since 2024/5/7  
       */
       public class RankingUtil {  
        
          public static final String FORMAT = "yyyyMMddHHmmss";  
        
          public static Long getScore(Double score) {  
              return Math.round(Math.floor(score / RankingConstant.BASIC_QUANTITY));  
          }  
        
          public static String getTimeStr(Double score) {  
              return String.valueOf(DateUtil.parse(String.valueOf(RankingConstant.MAXIMUM_TIME_TIMIT - Math.round(Math.floor(score)) % RankingConstant.BASIC_QUANTITY)));  
          }  
      }
      

      4、接口文档

      ranking_demo接口文档

      以上就是SpringBoot+Redission实现排行榜功能的示例代码的详细内容,更多关于SpringBoot Redission排行榜的资料请关注其它相关文章!

      声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。