当前位置:首页>排行榜>Redis排行榜功能实战:从0到1落地,搞定高并发场景

Redis排行榜功能实战:从0到1落地,搞定高并发场景

  • 更新时间 2026-02-21 07:11:26
Redis排行榜功能实战:从0到1落地,搞定高并发场景

在日常开发中,排行榜场景几乎无处不在——电商的销量榜、短视频的点赞榜、游戏的战力榜、社区的积分榜,甚至是公司内部的业绩榜,都需要高效、实时地展示数据排序结果。

而提到“实时排行榜+高并发”,Redis绝对是首选方案。相比于数据库排序(慢查询、扛不住高并发)、本地缓存排序(集群不一致),Redis凭借其高效的Sorted Set(有序集合)结构,能轻松支撑百万级数据的实时排序,响应速度毫秒级,而且实现成本极低。

今天就带大家从实战出发,不搞虚的理论,一步步实现一个可直接上线的Redis排行榜功能,涵盖核心API用法、完整代码实现、高并发优化,以及新手最容易踩的坑,看完直接复制可用!

一、先搞懂:Redis为什么能搞定排行榜?

很多新手会疑惑,为什么不用MySQL的ORDER BY做排序?其实答案很简单——效率和并发扛不住

如果排行榜数据存在MySQL中,每次查询都要执行“SELECT * FROM xxx ORDER BY score DESC LIMIT 10”,一旦数据量达到10万+,每次排序都是全表扫描/索引扫描,响应时间会飙升到几百毫秒甚至几秒;更别说高并发场景下,频繁的排序查询会直接把数据库压垮。

而Redis的Sorted Set(有序集合),天生就是为排行榜设计的,核心优势有3点:

  1. 底层优化,排序无压力:Sorted Set底层采用“跳表+哈希表”结构,添加、删除、修改分数(score)、排序的时间复杂度都是O(logN),百万级数据操作也能毫秒级响应。
  2. 自带排序,无需额外处理:存入数据时,Redis会自动根据你设置的score(排序权重)排序,无需手动执行排序逻辑,查询时直接取TopN即可。
  3. 支持多维度,灵活适配场景:可根据score正序、倒序排序,支持获取某个元素的排名、分数,还能批量更新分数,适配销量榜(按销量)、积分榜(按积分)、热度榜(按综合分数)等各种场景。

核心结论:只要是“实时排序+高并发”的排行榜,Redis Sorted Set就是最优解,没有之一。

二、实战准备:环境与核心API梳理

在写代码之前,先梳理好必备环境和核心API,避免写代码时手忙脚乱,新手也能快速跟上。

1. 环境准备(通用版)

  • Redis:5.0+(推荐6.0+,稳定性更好,支持更多新特性)
  • 客户端:Spring Data Redis(简化Redis操作,无需手动写原生API)
  • 依赖(Maven示例):
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><scope>runtime</scope></dependency>

2. 核心API(重中之重,记牢这5个就够了)

Redis Sorted Set的API不多,实现排行榜只需要用到以下5个,直接对应排行榜的核心操作,建议收藏:

操作场景
Redis原生API
Spring Data Redis方法
说明
添加/更新排行榜数据
ZADD key score member
redisTemplate.opsForZSet().add(key, member, score)
member是唯一标识(如用户ID、商品ID),score是排序权重(如积分、销量);存在则更新score,不存在则新增
获取TopN排行榜(倒序)
ZREVRANGE key 0 n-1 WITHSCORES
redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, n-1)
倒序(score从大到小),获取前n名,WITHSCORES表示同时获取score
获取某个元素的排名
ZREVRANK key member
redisTemplate.opsForZSet().reverseRank(key, member)
倒序排名,返回值从0开始(第1名返回0,第2名返回1)
获取某个元素的score
ZSCORE key member
redisTemplate.opsForZSet().score(key, member)
返回该元素的排序权重,可用于展示具体分数/销量
删除排行榜元素
ZREM key member
redisTemplate.opsForZSet().remove(key, member)
适用于元素下架、用户注销等场景,删除对应排行榜数据

补充:如果需要正序排行榜(score从小到大),把reverse开头的方法换成普通方法即可(如rangeWithScores、rank)。

三、完整实战:实现一个用户积分排行榜

接下来,我们以“用户积分排行榜”为例,实现一个可直接上线的功能,涵盖:添加/更新积分、获取Top10排行榜、获取单个用户排名和积分、删除用户排行榜数据,全程实战,代码可直接复制。

1. 需求梳理(贴近真实业务)

  • 用户完成任务、签到、消费等操作,增加积分(更新排行榜score);
  • 首页展示积分Top10排行榜,显示用户名、积分、排名;
  • 用户个人中心,显示自己的积分和当前排名;
  • 用户注销后,删除其在排行榜中的数据;
  • 支持高并发,避免积分更新错乱、排行榜查询卡顿。

2. 代码实现(Spring Boot + Redis)

我们采用“分层架构”,分为Service层(核心业务逻辑)、Controller层(接口暴露),简化冗余代码,新手也能看懂。

(1)配置Redis(application.yml)

spring:redis:host:127.0.0.1# 你的Redis地址port:6379# Redis端口password:# 没有密码则留空database:0# 数据库索引(默认0)jedis:pool:max-active:100# 最大连接数max-idle:20# 最大空闲连接min-idle:5# 最小空闲连接timeout:1000ms# 连接超时时间

(2)核心Service层(实现排行榜逻辑)

这里封装了所有排行榜相关的操作,方法名清晰,注释详细,直接调用即可。

import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ZSetOperations;import org.springframework.stereotype.Service;import javax.annotation.Resource;import java.util.ArrayList;import java.util.List;import java.util.Set;/** * Redis用户积分排行榜Service */@Servicepublic classRedisRankingService{// 排行榜的Redis Key(统一命名,避免混淆)private static final String RANKING_KEY = "user:score:ranking";@Resourceprivate RedisTemplate<String, Object> redisTemplate;/**     * 1. 添加/更新用户积分(核心操作)     * @param userId 用户ID(member)     * @param score 积分(排序权重,可正数增加、负数减少)     */publicvoidupdateUserScore(Long userId, Double score){// ZADD:存在则更新score,不存在则新增        redisTemplate.opsForZSet().add(RANKING_KEY, userId, score);    }/**     * 2. 获取积分TopN排行榜(倒序,从高到低)     * @param topN 前N名(如10则返回Top10)     * @return 排行榜列表(包含用户ID、积分、排名)     */public List<RankingVO> getTopRanking(Integer topN){// ZREVRANGE WITHSCORES:倒序获取前topN名,包含score        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet()                .reverseRangeWithScores(RANKING_KEY, 0, topN - 1);        List<RankingVO> rankingVOList = new ArrayList<>();if (typedTuples == null || typedTuples.isEmpty()) {return rankingVOList;        }// 遍历结果,封装成VO(排名从1开始)int rank = 1;for (ZSetOperations.TypedTuple<Object> tuple : typedTuples) {            Long userId = (Long) tuple.getValue();            Double score = tuple.getScore();            RankingVO vo = new RankingVO(userId, score, rank);            rankingVOList.add(vo);            rank++;        }return rankingVOList;    }/**     * 3. 获取单个用户的排名和积分     * @param userId 用户ID     * @return RankingVO(无数据则返回null)     */public RankingVO getUserRanking(Long userId){// 获取用户积分        Double score = redisTemplate.opsForZSet().score(RANKING_KEY, userId);if (score == null) {return null// 该用户未参与排行榜        }// 获取用户排名(倒序,返回0开始,需+1)        Long rank = redisTemplate.opsForZSet().reverseRank(RANKING_KEY, userId);returnnew RankingVO(userId, score, rank.intValue() + 1);    }/**     * 4. 删除用户排行榜数据(用户注销等场景)     * @param userId 用户ID     */publicvoiddeleteUserFromRanking(Long userId){        redisTemplate.opsForZSet().remove(RANKING_KEY, userId);    }}

(3)VO类(封装返回结果)

用于向前端返回排行榜数据,避免直接返回Redis原生结果,规范接口返回格式。

import lombok.AllArgsConstructor;import lombok.Data;/** * 排行榜返回VO */@Data@AllArgsConstructorpublicclassRankingVO{// 用户IDprivate Long userId;// 用户积分private Double score;// 排名(从1开始)private Integer rank;}

(4)Controller层(暴露接口,供前端调用)

import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;import java.util.List;/** * 积分排行榜接口 */@RestController@RequestMapping("/api/ranking")public classRankingController{@Resourceprivate RedisRankingService redisRankingService;/**     * 更新用户积分     * @param userId 用户ID     * @param score 积分(正数增加,负数减少)     */@PostMapping("/updateScore")publicvoidupdateScore(@RequestParam Long userId, @RequestParam Double score){        redisRankingService.updateUserScore(userId, score);    }/**     * 获取TopN排行榜     * @param topN 前N名(默认10)     * @return 排行榜列表     */@GetMapping("/top")public List<RankingVO> getTopRanking(@RequestParam(required = false, defaultValue = "10") Integer topN) {return redisRankingService.getTopRanking(topN);    }/**     * 获取单个用户的排名和积分     * @param userId 用户ID     * @return 单个用户排行榜信息     */@GetMapping("/user")public RankingVO getUserRanking(@RequestParam Long userId){return redisRankingService.getUserRanking(userId);    }/**     * 删除用户排行榜数据     * @param userId 用户ID     */@DeleteMapping("/delete/{userId}")publicvoiddeleteUserRanking(@PathVariable Long userId){        redisRankingService.deleteUserFromRanking(userId);    }}

3. 接口测试(验证功能是否可用)

我们用Postman测试接口,确保每个功能都能正常运行,新手也可以跟着测:

  1. 更新用户积分:POST /api/ranking/updateScore?userId=1&score=100(用户1积分100);同理添加用户2(score=200)、用户3(score=150);
  2. 获取Top10排行榜:GET /api/ranking/top,返回结果应该是  user2(200分,第1名)→ user3(150分,第2名)→ user1(100分,第3名);
  3. 获取单个用户排名:GET /api/ranking/user?userId=3,返回 rank=2,score=150;
  4. 删除用户:DELETE /api/ranking/delete/1,再查询Top10,用户1会被移除。

测试通过后,就可以对接前端页面,一个简单的积分排行榜就实现完成了,直接上线无压力!

四、高并发优化:避开这3个坑,支撑百万级流量

上面的代码适用于中小流量场景,但如果是高并发场景(如电商大促、游戏活动,每秒上千次积分更新),直接用会出现问题,这里分享3个核心优化点,避开新手常踩的坑。

坑1:积分更新错乱(并发修改score)

问题:高并发下,多个请求同时更新同一个用户的积分(如同时给用户1加10分),会出现积分错乱(比如应该加20分,实际只加了10分)。

解决方案:用Redis的ZINCRBY命令,原子性更新score,替代ZADD(ZADD是非原子性的)。

// 优化后的更新积分方法(原子性操作)publicvoidupdateUserScore(Long userId, Double score){// ZINCRBY:原子性增加score,score可正可负(负数就是减少积分)    redisTemplate.opsForZSet().incrementScore(RANKING_KEY, userId, score);}

说明:ZINCRBY是原子性命令,Redis会保证同一时间只有一个请求能修改该用户的score,彻底解决并发错乱问题。

坑2:排行榜查询卡顿(高并发查询TopN)

问题:每秒上千次查询Top10排行榜,虽然Redis响应快,但频繁查询还是会增加Redis压力,导致卡顿。

解决方案:排行榜结果缓存+定时更新,减少Redis查询次数。

  • 用Redis的String结构,缓存Top10排行榜结果(序列化RankingVOList);
  • 用定时任务(如每10秒),重新查询Top10,更新缓存;
  • 前端查询时,直接查缓存,而非直接查Sorted Set。

补充:定时任务的间隔,根据业务需求调整(如积分榜10秒更新一次,销量榜1分钟更新一次),兼顾实时性和性能。

坑3:Redis内存溢出(海量数据堆积)

问题:如果排行榜有百万级用户,Sorted Set会占用大量Redis内存,长期堆积会导致内存溢出。

解决方案:设置过期时间+限制排行榜数量,清理无效数据。

// 1. 设置排行榜过期时间(如7天,根据业务调整)redisTemplate.expire(RANKING_KEY, 7, TimeUnit.DAYS);// 2. 限制排行榜数量(只保留前1000名,超出部分删除,减少内存占用)// 每次更新积分后,删除排名1000以后的用户redisTemplate.opsForZSet().removeRange(RANKING_KEY, 0, -1001);

说明:大部分业务场景下,用户只关心前几百名,保留前1000名完全足够,既节省内存,又不影响用户体验。

五、扩展场景:不止是简单排行榜

上面实现的是“单一维度排行榜”(按积分排序),但实际业务中,经常会遇到更复杂的场景,这里分享2个常见扩展,只需简单修改代码就能实现。

扩展1:多维度排行榜(如日榜、周榜、月榜)

需求:同时展示用户积分的日榜、周榜、月榜,各自独立排序。

解决方案:用不同的Redis Key区分不同榜单,如:

  • 日榜Key:user:score:ranking:day:20260220(日期拼接,每天一个新Key);
  • 周榜Key:user:score:ranking:week:202608(年份+周数);
  • 月榜Key:user:score:ranking:month:202602(年份+月份)。

然后在Service层,增加一个参数(榜单类型),根据类型选择对应的Key即可。

扩展2:带条件的排行榜(如指定用户群体)

需求:只展示“VIP用户”的积分排行榜,普通用户不参与排序。

解决方案:用Redis的Hash结构,存储用户类型(VIP/普通),更新积分时,先判断用户类型,只有VIP用户才加入排行榜。

六、总结:实战核心要点

Redis排行榜功能,核心就是“利用Sorted Set的有序特性”,实战中记住3个核心要点,就能应对大部分场景:

  1. 核心API:记住ZADD/ZINCRBY(更新)、ZREVRANGE(查TopN)、ZREVRANK(查排名),5个API搞定80%场景;
  2. 高并发优化:用ZINCRBY保证原子性,用缓存+定时任务减少查询压力,用过期时间+数量限制控制内存;
  3. 灵活扩展:多维度榜单用不同Key,带条件榜单结合Hash结构,无需重构核心代码。

📌 关注我,每天 5 分钟,带你从 Java 小白变身编程高手!

👉 点赞 + 关注,让更多小伙伴一起进步!

最新文章

随机文章