SpringBoot整合Redisson实现高性能实时排行榜,
SpringBoot整合Redisson实现高性能实时排行榜,
目录
- 为什么选择Redisson
- Spring Boot如何整合Redisson
- 实现实时排行榜的关键逻辑
- 性能优化与实战坑点
- 扩展:多维度排行榜
- 结语
- 方法补充
在当今的互联网应用中,排行榜功能几乎无处不在!无论是电商平台的销量榜单、游戏中的玩家战力排行,还是社交媒体的热度榜单,实时、高效的排行榜系统都是提升用户体验的关键。那么问题来了:如何用Spring Boot和Redisson快速搭建一个高性能的实时排行榜?今天我们就来详细聊聊这个技术方案!
为什么选择Redisson
首先,Redisson是一个基于Redis的Java客户端,它不仅封装了Redis的基本操作,还提供了分布式锁、布隆过滤器、排行榜等高级功能。相比直接操作Redis,Redisson的API更符合Java开发者的习惯,而且性能优化得非常好。举个例子,它的RLexSortedSet和RScoredSortedSet可以直接用来实现排行榜,支持毫秒级的实时更新!
// 示例:初始化Redisson客户端 Config config = new Config(); config.useSingleServer().setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config); // 获取一个带分数的有序集合 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;
是不是很简单?但别急,这只是开始!接下来才是真正的核心逻辑。
实现实时排行榜的关键逻辑
排行榜的核心功能通常包括:
- 更新分数:比如用户完成一笔订单后增加积分。
- 获取排名:查询某个用户的当前排名。
- 获取Top N:展示前100名的榜单。
用Redisson实现这些功能非常直观:
// 更新用户分数 ranking.addScore("user1", 100); // 获取用户排名(从高到低) int rank = ranking.rank("user1"); // 获取Top 10 Collection<String> top10 = ranking.valueRangeReversed(0, 9);
这里有个小技巧:Redisson的valueRangeReversed方法可以直接返回倒序结果,避免了手动排序的开销!
性能优化与实战坑点
在实际项目中,排行榜可能会面临高并发更新的问题。比如双十一期间,电商平台的销量榜单每秒钟要更新成千上万次!这时候就需要考虑以下几点:
- 批量操作:Redisson支持管道(Pipeline)和批量命令,能显著减少网络开销。
- 分布式锁:如果涉及复杂的计算(比如积分加权),可以用RLock避免并发问题。
- 内存优化:Redis的zset默认用ziplist存储少量数据,但数据量大时会自动转为skiplist,这时候要注意内存占用。
// 使用管道批量更新 RBatch batch = redisson.createBatch(); batch.getScoredSortedSet("ranking").addScoreAsync("user1", 10); batch.getScoredSortedSet("ranking").addScoreAsync("user2", 20); batch.execute();
扩展:多维度排行榜
有时候我们需要多维度的排名,比如“周榜”和“总榜”并存。这时候可以用Redisson的RScoredSortedSet配合不同的Key来实现:
RScoredSortedSet<String> weeklyRanking = redisson.getScoredSortedSet("ranking:weekly"); RScoredSortedSet<String> totalRanking = redisson.getScoredSortedSet("ranking:total"); // 每周清零周榜 weeklyRanking.clear();
结语
通过Spring Boot和Redisson,我们能够轻松实现一个高性能的实时排行榜系统。从基础的功能到性能优化,Redisson都提供了简洁而强大的API。如果你正在面临类似的需求,不妨动手试试吧!遇到问题也别慌,多查文档、多交流,技术成长就是这么一步步来的。
方法补充
SpringBoot+Redission实现排行榜功能
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))); } }
到此这篇关于SpringBoot整合Redisson实现高性能实时排行榜的文章就介绍到这了,更多相关SpringBoot Redisson实时排行榜内容请搜索3672js教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持3672js教程!
您可能感兴趣的文章:- 使用Redis实现实时排行榜功能
- Spring Boot与Redisson实时排行榜功能
- 使用Redis实现实时排行榜的示例
用户点评