SpringBoot+Redission实现排行榜功能

SpringBoot+Redission实现排行榜功能

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

一、业务需求

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

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

1.简介

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

2.特点

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

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;  
  
}
Utils
/**  
 * @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)));  
    }  
}

五、接口文档

ranking_demo接口文档

六、参考文章

Redis常用命令对应到Redisson对象操作_redisson rscript中eval-CSDN博客

  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值