SpringBoot整合Redisson實現(xiàn)高性能實時排行榜
在當今的互聯(lián)網(wǎng)應用中,排行榜功能幾乎無處不在!無論是電商平臺的銷量榜單、游戲中的玩家戰(zhàn)力排行,還是社交媒體的熱度榜單,實時、高效的排行榜系統(tǒng)都是提升用戶體驗的關鍵。那么問題來了:如何用Spring Boot和Redisson快速搭建一個高性能的實時排行榜?今天我們就來詳細聊聊這個技術方案!
為什么選擇Redisson
首先,Redisson是一個基于Redis的Java客戶端,它不僅封裝了Redis的基本操作,還提供了分布式鎖、布隆過濾器、排行榜等高級功能。相比直接操作Redis,Redisson的API更符合Java開發(fā)者的習慣,而且性能優(yōu)化得非常好。舉個例子,它的RLexSortedSet和RScoredSortedSet可以直接用來實現(xiàn)排行榜,支持毫秒級的實時更新!
// 示例:初始化Redisson客戶端 Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); // 獲取一個帶分數(shù)的有序集合 RScoredSortedSet<String> ranking = redisson.getScoredSortedSet("user_ranking");
Spring Boot如何整合Redisson
整合過程非常簡單!只需要幾步:
添加依賴:在pom.xml中加入Redisson的Spring Boot Starter。
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.16.8</version> </dependency>
配置Redis連接:在application.yml中填寫Redis的地址和密碼。
spring: redis: host: 127.0.0.1 port: 6379
注入RedissonClient:直接通過@Autowired就能使用Redisson的功能。
@Autowired private RedissonClient redissonClient;
是不是很簡單?但別急,這只是開始!接下來才是真正的核心邏輯。
實現(xiàn)實時排行榜的關鍵邏輯
排行榜的核心功能通常包括:
- 更新分數(shù):比如用戶完成一筆訂單后增加積分。
- 獲取排名:查詢某個用戶的當前排名。
- 獲取Top N:展示前100名的榜單。
用Redisson實現(xiàn)這些功能非常直觀:
// 更新用戶分數(shù) ranking.addScore("user1", 100); // 獲取用戶排名(從高到低) int rank = ranking.rank("user1"); // 獲取Top 10 Collection<String> top10 = ranking.valueRangeReversed(0, 9);
這里有個小技巧:Redisson的valueRangeReversed方法可以直接返回倒序結果,避免了手動排序的開銷!
性能優(yōu)化與實戰(zhàn)坑點
在實際項目中,排行榜可能會面臨高并發(fā)更新的問題。比如雙十一期間,電商平臺的銷量榜單每秒鐘要更新成千上萬次!這時候就需要考慮以下幾點:
- 批量操作:Redisson支持管道(Pipeline)和批量命令,能顯著減少網(wǎng)絡開銷。
- 分布式鎖:如果涉及復雜的計算(比如積分加權),可以用RLock避免并發(fā)問題。
- 內(nèi)存優(yōu)化:Redis的zset默認用ziplist存儲少量數(shù)據(jù),但數(shù)據(jù)量大時會自動轉為skiplist,這時候要注意內(nèi)存占用。
// 使用管道批量更新 RBatch batch = redisson.createBatch(); batch.getScoredSortedSet("ranking").addScoreAsync("user1", 10); batch.getScoredSortedSet("ranking").addScoreAsync("user2", 20); batch.execute();
擴展:多維度排行榜
有時候我們需要多維度的排名,比如“周榜”和“總榜”并存。這時候可以用Redisson的RScoredSortedSet配合不同的Key來實現(xiàn):
RScoredSortedSet<String> weeklyRanking = redisson.getScoredSortedSet("ranking:weekly"); RScoredSortedSet<String> totalRanking = redisson.getScoredSortedSet("ranking:total"); ???????// 每周清零周榜 weeklyRanking.clear();
結語
通過Spring Boot和Redisson,我們能夠輕松實現(xiàn)一個高性能的實時排行榜系統(tǒng)。從基礎的功能到性能優(yōu)化,Redisson都提供了簡潔而強大的API。如果你正在面臨類似的需求,不妨動手試試吧!遇到問題也別慌,多查文檔、多交流,技術成長就是這么一步步來的。
方法補充
SpringBoot+Redission實現(xiàn)排行榜功能
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; }
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); /** * 向有序集合中添加指定分數(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 結束索引 * @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))); } }
到此這篇關于SpringBoot整合Redisson實現(xiàn)高性能實時排行榜的文章就介紹到這了,更多相關SpringBoot Redisson實時排行榜內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
MyBatis-Plus與PageHelper依賴的jsqlparser庫沖突
在升級SpringBoot到3.x版本的同時,升級MyBatis-Plus后發(fā)現(xiàn)PageHelper無法使用,原因是MyBatis-Plus和PageHelper都依賴jsqlparser庫,且PageHelper要求特定版本的jsqlparser,解決方法是在項目中排除這兩個庫的jsqlparser依賴,直接引用jsqlparser4.7版本2024-10-10Java 在volatile內(nèi)部調(diào)用接口的方法
在Java中,volatile?關鍵字通常用于確保變量的可見性和有序性,而不是用來修飾接口或方法調(diào)用的,這篇文章主要介紹了Java 在volatile內(nèi)部調(diào)用接口的方法,需要的朋友可以參考下2024-07-07java.lang.NoClassDefFoundError錯誤的原因及解決方法
這篇文章主要給大家介紹了關于java.lang.NoClassDefFoundError錯誤的原因及解決的相關資料,java.lang.NoClassDefFoundError是Java虛擬機在運行時無法找到特定類的錯誤,需要的朋友可以參考下2023-10-10Java實現(xiàn)的可選擇及拖拽圖片的面板功能【基于swing組件】
這篇文章主要介紹了Java實現(xiàn)的可選擇及拖拽圖片的面板功能,涉及java基于swing組件選擇與操作圖片元素的相關實現(xiàn)技巧,需要的朋友可以參考下2018-01-01java使用MulticastSocket實現(xiàn)組播
這篇文章主要為大家詳細介紹了java使用MulticastSocket實現(xiàn)組播,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-01-01