SpringBoot+Redission實現(xiàn)排行榜功能的示例代碼
SpringBoot+Redission實現(xiàn)排行榜功能
一、業(yè)務(wù)需求
實現(xiàn)一個排行榜,要求按照分數(shù)和達成這個分數(shù)的時間排序,即相同分數(shù)下,時間早的在上面
二、Redis中的zSet(有序集合)
1.簡介
Redis 的 zSet(也稱為有序集合)是一種特殊的數(shù)據(jù)結(jié)構(gòu),它同時包含了集合和有序列表的特性。在 zSet 中,每個成員都有一個分數(shù)(score)與之關(guān)聯(lián),這個分數(shù)可以是浮點數(shù),用于對集合中的元素進行排序。
2.特點
- 元素唯一:就像集合一樣,zSet 中不允許有重復(fù)成員。
- 有序性:集合中的元素按照其關(guān)聯(lián)的分數(shù)值進行升序排序。
- 操作豐富:支持添加、刪除成員,獲取指定范圍的成員,根據(jù)分數(shù)查詢成員,計算交集、并集、差集等操作。
3.常用命令
- ZADD:向有序集合中添加一個或多個成員,或者更新已存在成員的分數(shù)。
- ZRANGE:返回有序集合中指定區(qū)間內(nèi)的成員,通過索引位置來獲取,從0開始。
- ZRANGEBYSCORE:返回有序集合中指定分數(shù)區(qū)間的成員。
- ZCARD:獲取有序集合的成員數(shù)量。
- ZREM:移除有序集合中的一個或多個成員。
- ZREVRANGE:類似于 ZRANGE,但返回的是從高分到低分的成員。
- ZINCRBY:為有序集合中的成員的分數(shù)加上給定值。
- ZCOUNT:計算有序集合中指定分數(shù)區(qū)間的成員數(shù)量。
- 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.總結(jié)
zSet可以很好的實現(xiàn)分數(shù)排序,但是在相同的分數(shù)下,會按照成員的名稱進行排序,所以要在此基礎(chǔ)上增加時間
三、增加時間數(shù)據(jù)
為了增加完成時間,我們可以引進一個倒計時的概念,假設(shè)一共9秒
用戶A在獲得1分的時候在第2秒,那可以在Redis中存儲1*10+(9-2) => 18
用戶B在獲得1分的時候在第6秒,那可以在Redis中存儲1*10+(9-6) => 13
這樣我們在獲取分數(shù)的時候,可以倒推出分數(shù)和完成時間
四、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
# 數(shù)據(jù)庫索引
database: 0
# 密碼(如沒有密碼請注釋掉)
# password: # 連接超時時間
timeout: 10s
# 是否開啟ssl
ssl: false
--- # redisson配置
redisson:
# redis key前綴
keyPrefix: ${spring.application.name}
# 線程池數(shù)量
threads: 4
# Netty線程池數(shù)量
nettyThreads: 8
# 單節(jié)點配置
singleServerConfig:
# 客戶端名稱
clientName: ${spring.application.name}
# 最小空閑連接數(shù)
connectionMinimumIdleSize: 8
# 連接池大小
connectionPoolSize: 32
# 連接空閑超時,單位:毫秒
idleConnectionTimeout: 10000
# 命令等待超時,單位:毫秒
timeout: 3000
# 發(fā)布和訂閱連接池大小
subscriptionConnectionPoolSize: 50
3.Java代碼
Constant
/**
* @author Baisu
* @classname RankingConstant
* @description 排行榜常量數(shù)據(jù)
* @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;
}
/**
* 生成測試數(shù)據(jù)
*
* @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();
}
/**
* 獲取排行榜數(shù)據(jù)
*
* @param top 數(shù)量
* @return 排行榜數(shù)據(jù)
*/
@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("暫無排行榜數(shù)據(jù)");
}
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);
}
/**
* 增加成員分數(shù)值
*
* @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();
}
/**
* 獲取成員分數(shù)值
*
* @param member 成員
* @return 分數(shù)值
*/
@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;
/**
* 分數(shù)值
*/
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);
/**
* 向有序集合中添加指定分數(shù)的成員
*
* @param key 有序集索引
* @param score 分數(shù)
* @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);
}
/**
* 返回有序集中成員的分數(shù)值
*
* @param key 有序集索引
* @param member 成員
* @return 分數(shù)值(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 結(jié)束索引
* @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、接口文檔
以上就是SpringBoot+Redission實現(xiàn)排行榜功能的示例代碼的詳細內(nèi)容,更多關(guān)于SpringBoot Redission排行榜的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實現(xiàn)使用Websocket發(fā)送消息詳細代碼舉例
這篇文章主要給大家介紹了關(guān)于Java實現(xiàn)使用Websocket發(fā)送消息的相關(guān)資料,WebSocket是一種協(xié)議,用于在Web應(yīng)用程序和服務(wù)器之間建立實時、雙向的通信連接,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-05-05
實現(xiàn)一個基于Servlet的hello world程序詳解步驟
Java Servlet 是運行在 Web 服務(wù)器或應(yīng)用服務(wù)器上的程序,它是作為來自 Web 瀏覽器或其他 HTTP 客戶端的請求和 HTTP 服務(wù)器上的數(shù)據(jù)庫或應(yīng)用程序之間的中間層2022-02-02
java Executors工具類的相關(guān)方法使用創(chuàng)建
這篇文章主要為大家介紹了java Executors工具類的相關(guān)方法使用創(chuàng)建,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11
windows如何使用bat腳本后臺啟動/停止和重啟jar包服務(wù)
這篇文章主要介紹了windows使用bat腳本后臺啟動/停止和重啟jar包服務(wù)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-11-11
java實現(xiàn)建造者模式(Builder Pattern)
這篇文章主要為大家詳細介紹了java實現(xiàn)建造者模式Builder Pattern,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-10-10
Mybatis自定義攔截器實現(xiàn)權(quán)限功能
本文主要介紹了Mybatis自定義攔截器實現(xiàn)權(quán)限功能,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-12-12

