构建百万级实时排行榜:Redis Sorted Set 与 Java 实战指南
构建百万级实时排行榜Redis Sorted Set 与 Java 实战指南在互联网应用中实时排行榜是一个常见且极具挑战性的功能。无论是游戏中的玩家积分榜、直播平台的礼物榜还是电商的热销商品榜都需要在百万级用户量下实现毫秒级更新和查询。本文将深入讲解如何利用 Redis 的 Sorted Set有序集合 结合 JavaSpring Boot 框架 构建一个高性能、可扩展的实时排行榜系统。## 为什么选择 Redis Sorted SetRedis 的 Sorted Set 是一种基于跳表Skip List和哈希表实现的数据结构它具备以下核心优势-天然排序每个成员member关联一个分数score自动按分数升序排列。-原子操作通过ZADD、ZINCRBY等命令实现 O(log(N)) 的插入与更新。-范围查询ZREVRANGE支持倒序获取 Top NZRANK可快速查询排名。-内存高效百万级数据仅占几百 MB 内存结合持久化RDB/AOF可保证数据安全。## 架构设计概览我们将构建一个游戏积分排行榜系统架构如下1.数据层Redis Sorted Set 存储用户IDmember和积分score。2.业务层Java Spring Boot 服务提供 REST API封装排行榜操作。3.缓存层本地缓存Caffeine配合 Redis 减少热点查询压力。4.持久化定期将 Redis 数据同步到 MySQL 用于历史分析。## 实战一核心排行榜操作实现首先我们需要一个 Spring Boot 项目引入 Redis 依赖Spring Data Redis。以下是排行榜服务的核心代码包含积分更新、排名查询和 Top N 获取。javaimport org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ZSetOperations;import org.springframework.stereotype.Service;import java.util.Set;import java.util.stream.Collectors;/** * 排行榜服务类 * 使用 Redis Sorted Set 实现实时积分排行 */Servicepublic class LeaderboardService { Autowired private RedisTemplateString, Object redisTemplate; private static final String LEADERBOARD_KEY game:leaderboard; /** * 更新用户积分原子递增 * param userId 用户ID * param increment 增加的积分值 */ public void updateScore(Long userId, double increment) { // ZINCRBY 命令如果用户不存在则自动创建否则累加积分 redisTemplate.opsForZSet().incrementScore(LEADERBOARD_KEY, userId, increment); System.out.println(用户 userId 积分增加 increment); } /** * 获取用户当前排名从0开始0表示第一名 * param userId 用户ID * return 排名-1表示用户不存在 */ public Long getRank(Long userId) { // ZREVRANK 返回倒序排名分数越高排名越前 Long rank redisTemplate.opsForZSet().reverseRank(LEADERBOARD_KEY, userId); if (rank null) { return -1L; } // 通常用户期望排名从1开始所以加1 return rank 1; } /** * 获取排行榜前N名用户及其分数 * param topN 要获取的名次数量 * return 包含用户ID和分数的集合 */ public SetZSetOperations.TypedTupleObject getTopN(int topN) { // ZREVRANGE 带分数返回按分数从高到低获取 return redisTemplate.opsForZSet().reverseRangeWithScores(LEADERBOARD_KEY, 0, topN - 1); } /** * 获取用户当前积分 * param userId 用户ID * return 用户积分0表示不存在 */ public Double getUserScore(Long userId) { Double score redisTemplate.opsForZSet().score(LEADERBOARD_KEY, userId); return score ! null ? score : 0.0; }}代码注释说明-incrementScore使用ZINCRBY命令确保并发下积分更新原子性。-reverseRank返回升序排名0为最低分因此配合reverseRange获取降序排名。-reverseRangeWithScores同时返回成员和分数避免二次查询。## 实战二百万级数据模拟与性能测试为了验证系统在百万级数据下的表现我们编写一个模拟程序批量插入用户并测试查询性能。以下代码使用 JUnit 测试类进行压力模拟。javaimport org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.core.ZSetOperations;import java.util.Random;import java.util.Set;/** * 百万级排行榜模拟测试 */SpringBootTestpublic class LeaderboardSimulationTest { Autowired private LeaderboardService leaderboardService; Autowired private RedisTemplateString, Object redisTemplate; private static final String TEST_KEY test:leaderboard:1m; Test public void simulateMillionUsers() { // 1. 生成100万用户每个用户随机初始化积分1-10000 Random random new Random(); int userCount 1_000_000; // 100万用户 long startTime System.currentTimeMillis(); // 使用管道批量插入提升性能避免逐条发送 redisTemplate.executePipelined((connection) - { for (long i 1; i userCount; i) { double score random.nextDouble() * 10000; // 随机积分 // 注意pipelined 中需使用原始连接操作 connection.zAdd(TEST_KEY.getBytes(), score, String.valueOf(i).getBytes()); } return null; }); long endTime System.currentTimeMillis(); System.out.println(插入 userCount 条数据耗时: (endTime - startTime) ms); // 2. 测试查询性能获取排名前10的用户 long queryStart System.currentTimeMillis(); SetZSetOperations.TypedTupleObject top10 leaderboardService.getTopN(10); long queryEnd System.currentTimeMillis(); System.out.println(查询Top10耗时: (queryEnd - queryStart) ms); top10.forEach(tuple - System.out.println(用户ID: tuple.getValue() 积分: tuple.getScore())); // 3. 测试更新性能对随机1000个用户增加积分 long updateStart System.currentTimeMillis(); for (int i 0; i 1000; i) { long userId random.nextInt(userCount) 1; leaderboardService.updateScore(userId, random.nextDouble() * 100); } long updateEnd System.currentTimeMillis(); System.out.println(更新1000个用户积分耗时: (updateEnd - updateStart) ms); // 4. 测试排名查询查询用户100000的排名 long rankStart System.currentTimeMillis(); long rank leaderboardService.getRank(100000L); long rankEnd System.currentTimeMillis(); System.out.println(查询用户100000排名耗时: (rankEnd - rankStart) ms排名: rank); }}测试结果预期基于本地 Redis 实例- 插入100万条数据约 2-3 秒使用管道。- 查询 Top10 1 毫秒。- 更新1000个用户约 10 毫秒。- 查询单个排名 1 毫秒。关键优化点1.管道Pipeline批量操作减少网络往返插入性能提升10倍以上。2.批量更新对于高频积分变动可以合并为ZINCRBY批量操作如每秒一次。3.本地缓存将 Top N 结果缓存到 Caffeine 中设置过期时间如5秒减少 Redis 查询压力。## 进阶优化与最佳实践### 1. 防止分数相同导致的排名抖动当多个用户分数相同时Sorted Set 会按字典序排列可能导致排名不稳定。解决方案- 使用ZADD时附加唯一时间戳作为排序因子例如score 实际积分 1/时间戳确保分数唯一。- 或者在后端排序时按“积分相同则先到者优先”的规则处理。### 2. 数据分片与持久化-分片如果数据量超过单机内存如10亿用户可以使用 Redis Cluster 将数据分片到多个节点。-持久化使用 RDB 快照每小时 AOF 日志每秒刷盘防止数据丢失。同时定期同步到 MySQL 用于历史查询。### 3. 本地缓存降级当 Redis 瞬时故障时本地缓存可以提供服务兜底javaCacheable(value topN, key #topN, unless #result null)public SetZSetOperations.TypedTupleObject getTopNWithCache(int topN) { return getTopN(topN);}### 4. 排行榜分页查询对于需要分页展示的场景如“我的排名前后10名”可以使用ZRANK获取用户排名再使用ZREVRANGE获取附近区间javapublic SetZSetOperations.TypedTupleObject getNeighbors(Long userId, int range) { Long rank getRank(userId); if (rank -1) return Collections.emptySet(); long start Math.max(0, rank - range); long end rank range; return redisTemplate.opsForZSet().reverseRangeWithScores(LEADERBOARD_KEY, start, end);}## 总结本文通过完整的 Java 代码示例展示了如何利用 Redis Sorted Set 构建一个可处理百万级用户的实时排行榜系统。核心要点包括1.数据结构优势Sorted Set 的 O(log(N)) 操作使其天然适合排行榜场景。2.原子操作ZINCRBY和ZREVRANGE保证了并发安全和高性能。3.批量优化管道和本地缓存是应对大规模数据的关键手段。4.扩展性通过分片和持久化策略系统可以轻松支撑亿级用户。在实际生产环境中你还可以结合消息队列如 Kafka解耦积分更新与排行榜写入进一步降低延迟。希望本文能帮助你快速构建属于自己的高性能实时排行榜。

相关新闻

导师严选!盘点2026年口碑爆棚的AI论文工具

导师严选!盘点2026年口碑爆棚的AI论文工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年AI论文工具强势来袭,实测提速超50%,覆盖选题构思、文献整理、降重润色、格式排版全流程,高效搞定论文不再是梦想。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

2026/10/12 10:50:19 阅读更多 →
C++菱形继承问题解析:虚继承原理与多继承设计实践

C++菱形继承问题解析:虚继承原理与多继承设计实践

1. 菱形继承:C多继承中的经典“陷阱” 在C的面向对象编程世界里,多继承是一个强大但充满争议的特性。它允许一个派生类同时从多个基类继承属性和行为,为复杂系统的建模提供了极大的灵活性。然而,这份强大背后隐藏着一个著名的“陷…

2026/10/2 7:51:47 阅读更多 →
冠亚投票系统设计与实现:Spring Boot+Vue技术方案详解

冠亚投票系统设计与实现:Spring Boot+Vue技术方案详解

在技术项目开发过程中,阶段性评审和决策是确保项目方向正确、团队共识一致的关键环节。冠亚投票作为一种常见的决策机制,常用于项目里程碑评审、技术方案选型、优先级排序等场景,帮助团队从多个候选方案中选出最优解。本文将围绕如何设计并实…

2026/10/12 11:49:51 阅读更多 →

最新新闻

环形链表 II:快慢指针找入环点的数学推导与代码实现

环形链表 II:快慢指针找入环点的数学推导与代码实现

要搞懂这道“环形链表Ⅱ”,光会判断有没有环远远不够。很多人在 LeetCode-141 里用快慢指针跑得飞起,一遇到 142 要找入环点就直接懵住。LeetCode-142 真正考的不是“会不会用双指针”,而是“能不能讲清楚快指针和慢指针相遇之后,…

2026/10/13 12:04:52 阅读更多 →
Excel多源数据清洗与高级筛选:从零搭建可复用动态看板

Excel多源数据清洗与高级筛选:从零搭建可复用动态看板

半年前我在处理一份四十万行销售流水时,连续加了三个夜班。表面问题是Excel卡顿,深层问题是数据源太杂:系统导出的订单表、手工维护的客户表、供应商提供的分类表,字段名五花八门,同一含义写成“商品名称”“产品名”“…

2026/10/13 12:04:52 阅读更多 →
SolidWorks Routing界面英文怎么办?三步汉化及库文件属性修改指南

SolidWorks Routing界面英文怎么办?三步汉化及库文件属性修改指南

简介:面向Solidworks Routing用户的界面汉化操作指南,专门解决管路设计模块默认显示英文、切换中文失效等常见问题。文档从中文语言包安装状态检查讲起,依次说明系统选项、文件位置及CHS文件夹指向等关键配置步骤,同时补充Routing…

2026/10/13 12:04:52 阅读更多 →
罗兰贝格PPT模板实战:436页页型库与咨询图表改造指南

罗兰贝格PPT模板实战:436页页型库与咨询图表改造指南

简介:这套罗兰贝格PPT模板共收录436个版式页面,按商务咨询场景常见表达方式分类,适合需要快速产出专业演示文稿的产品经理、咨询顾问与职场汇报者使用。模板覆盖标准幻灯片、列表(1/2)、矩阵对比、流程步骤与因素互连图…

2026/10/13 12:04:52 阅读更多 →
2017年CRM客户管理PRD实战:字段、状态机与权限落地

2017年CRM客户管理PRD实战:字段、状态机与权限落地

简介:这份PDF文档是蜂网供应链管理(上海)有限公司于2017年8月发布的《客户关系管理CRM_客户管理_软件需求说明书_V1.0》,面向产品经理、需求分析师及CRM系统开发人员,用于指导客户管理模块的设计、开发与测试。文档由周…

2026/10/13 12:04:52 阅读更多 →
MATLAB摄像头调试实战:webcam与videoinput全解析

MATLAB摄像头调试实战:webcam与videoinput全解析

简介:计算机视觉项目中,第一步往往是从摄像头获取图像帧。面向MATLAB初学者的摄像头采集与图像二值化示例程序,可直接运行调试,解决实时视频帧读取、灰度转换和自动阈值分割等基础问题。压缩包内仅含1个.m源文件,整体大…

2026/10/13 12:03:51 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →