游戏排行榜系统的架构设计:从Redis Sorted Set到分布式Top-K方案
游戏排行榜系统的架构设计从Redis Sorted Set到分布式Top-K方案一、排行榜的业务特征与技术挑战排行榜是游戏中最具社交属性的系统之一。它不只是展示谁是第一更是驱动玩家活跃和付费的核心杠杆——段位排名、赛季结算、好友比拼、全服竞速每一种排行榜都在制造竞争感和目标感。从技术视角拆解游戏排行榜有几个显著特征写多读也多。以千万DAU的游戏为例假设每个玩家平均每小时产生3次分数变更事件那么写入TPS在800010000之间而排行榜的读取查看排名、翻页、查看好友排名可能达到写入的510倍。实时性要求分级。全服排行榜可以接受秒级延迟但好友榜的更新需要在百毫秒内生效否则会出现我刚打了最高分为什么好友看不到的客诉。维度爆炸。同一个玩家可能出现在战力榜、竞技榜、公会榜、赛季榜等十几个榜单中每个榜单的排序规则和更新频率都不同。二、Redis Sorted Set的极限与超越Redis Sorted Set是排行榜的标准答案——ZADD更新分数O(log N)ZREVRANGE查询Top-K也是O(log N K)。对于百万级用户的全服榜单个Redis实例足以承载。但当用户量达到千万级别时问题开始浮现内存膨胀。Sorted Set的底层是跳表字典每个元素的meta开销约80字节。一亿条记录memberscore大约需要8-12GB内存这让纯内存方案的成本变得不可接受。写入热点。所有写入操作集中在一个实例上Redis单线程模型下ZADD的TPS上限约为8-12万取决于value大小但在游戏高峰期可能成为瓶颈。跨分片排名不准。最简单的分片方式是按玩家ID哈希分布到多个Redis实例但这导致无法直接获取全局排名——玩家在分片内排名第3但全局可能排第1000。解决方案是分片排名定期合并的混合架构public class DistributedLeaderboard { private final ListRedisClient shards; private final int shardCount; private final String leaderboardKey; // 全局Top-1000缓存每30秒重建 private volatile ListRankEntry globalTopCache; private final ScheduledExecutorService mergeExecutor; public DistributedLeaderboard(ListRedisClient shards, String leaderboardKey) { this.shards shards; this.shardCount shards.size(); this.leaderboardKey leaderboardKey; this.mergeExecutor Executors.newSingleThreadScheduledExecutor(); // 定期合并 this.mergeExecutor.scheduleAtFixedRate( this::rebuildGlobalTopCache, 5, 30, TimeUnit.SECONDS); } public void updateScore(String playerId, double score) { int shardIndex Math.abs(playerId.hashCode()) % shardCount; shards.get(shardIndex).zadd(leaderboardKey, score, playerId); } public ListRankEntry getGlobalTopK(int k) { if (k globalTopCache.size()) { return globalTopCache.subList(0, Math.min(k, globalTopCache.size())); } // 超出缓存范围的请求走实时合并 return mergeTopKFromAllShards(k); } public long getPlayerGlobalRank(String playerId) { int playerShard Math.abs(playerId.hashCode()) % shardCount; Double playerScore shards.get(playerShard).zscore(leaderboardKey, playerId); if (playerScore null) return -1; // 汇总所有分片中分数高于该玩家的数量 long rank 0; for (int i 0; i shardCount; i) { if (i playerShard) { rank shards.get(i).zcount(leaderboardKey, Range.greaterThan(playerScore)); } else { rank shards.get(i).zcount(leaderboardKey, Range.greaterThan(playerScore)); } } return rank 1; // rank从1开始 } private void rebuildGlobalTopCache() { // 从每个分片拉取Top-K归并排序 ListCompletableFutureListRankEntry futures new ArrayList(); for (RedisClient shard : shards) { futures.add(CompletableFuture.supplyAsync(() - shard.zrevrangeWithScores(leaderboardKey, 0, 999) )); } this.globalTopCache futures.stream() .map(CompletableFuture::join) .flatMap(List::stream) .sorted(Comparator.comparingDouble(RankEntry::getScore).reversed()) .limit(1000) .collect(Collectors.toList()); } }这种方案下Top-K查询K≤1000走本地缓存延迟1ms玩家个人排名查询需要跨分片汇总延迟约10-30ms取决于分片数量写入延迟始终是单个分片的O(log N)没有扩大。三、赛季切换的数据归档与重置赛季制排行榜引入了一个独特挑战赛季结束时当前榜单数据需要归档同时为新赛季创建空榜单。这个过程必须在维护窗口内完成且不能丢失任何赛季末最后一刻的分数更新。设计上采用双写快照切换策略public class SeasonRotationManager { public void rotateSeason(String leaderboardKey, int oldSeasonId, int newSeasonId) { String archiveKey leaderboardKey :season: oldSeasonId; String newSeasonKey leaderboardKey :season: newSeasonId; // Step 1: RENAME原子操作旧榜单变为归档 // RENAME是原子操作不存在切换间隙 redis.rename(leaderboardKey, archiveKey); // Step 2: 归档数据压缩存储 byte[] compressed compressLeaderboardData(redis.dump(archiveKey)); // Step 3: 持久化到对象存储用于后续赛季回顾功能 ossClient.putObject(leaderboard-archive/season- oldSeasonId .dat, compressed); // Step 4: 设置归档数据的TTLRedis中保留30天热数据 redis.expire(archiveKey, 30, TimeUnit.DAYS); // Step 5: 新赛季榜单已就绪空Sorted Set由RENAME自动创建 // 此时新的写入会进入新赛季榜单 } }关键点Redis的RENAME命令是原子操作不会出现既没有旧榜单也没有新榜单的中间状态。如果写入请求恰好在RENAME之前到达分数会随旧榜单一起归档如果恰好在之后到达会写入新榜单。不会丢失数据。四、好友排行榜的实时推送好友排行榜的数据量小通常几十到几百人但实时性要求极高。方案是将好友榜数据加载到本地内存通过事件驱动保证一致性Service public class FriendLeaderboardService { // 每个玩家的好友榜缓存在本地内存 private final CacheLong, ListFriendRankEntry friendRankCache Caffeine.newBuilder() .maximumSize(500_000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(); KafkaListener(topics player-score-changed) public void onScoreChanged(ScoreChangeEvent event) { // 找到该玩家的所有好友失效他们的好友榜缓存 ListLong friendIds friendGraphService.getFriendIds(event.getPlayerId()); friendIds.forEach(friendRankCache::invalidate); // 如果好友在线主动推送排名变化 friendIds.stream() .filter(onlinePlayerRegistry::isOnline) .forEach(fid - pushGateway.push(fid, new RankChangeNotification(event.getPlayerId(), event.getNewScore(), event.getRankDelta()))); } }五、总结排行榜系统看似简单——一个Sorted Set就能跑起来——但在千万级用户的游戏场景下需要解决的问题远不止数据结构的选型。分片排名的近似查询、赛季切换的原子性保证、好友榜单的实时推送、多维榜单的存储爆炸每一个都是需要仔细权衡的工程决策。核心经验是不要试图用一套方案解决所有榜单需求。全服榜追求全局一致性适合定期合并的分片方案好友榜追求实时性适合内存缓存事件驱动赛季榜追求历史可追溯适合归档到对象存储。针对不同的访问模式和一致性要求选择不同的架构才是排行榜系统设计的正确姿势。

相关新闻

AI工具组合不是拼图游戏:资深架构师手把手教你构建抗衰减、可审计、易迭代的智能工作栈

AI工具组合不是拼图游戏:资深架构师手把手教你构建抗衰减、可审计、易迭代的智能工作栈

更多请点击: https://intelliparadigm.com 第一章:AI工具组合不是拼图游戏:资深架构师手把手教你构建抗衰减、可审计、易迭代的智能工作栈 真正的智能工作栈不是把Copilot、LangChain、LlamaIndex和VectorDB简单堆叠——它是一套具备生命周期…

2026/10/14 6:40:29 阅读更多 →
猫抓浏览器扩展:3大核心功能助您高效获取网页媒体资源

猫抓浏览器扩展:3大核心功能助您高效获取网页媒体资源

猫抓浏览器扩展:3大核心功能助您高效获取网页媒体资源 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 您是否经常遇到网页视频无法下载…

2026/10/14 6:40:46 阅读更多 →
全球显示器支架底座市场发展战略规划及 现状调研报告2026年版

全球显示器支架底座市场发展战略规划及 现状调研报告2026年版

全球显示器支架底座市场发展战略规划及 现状调研报告2026年版显示器支架底座是为显示设备提供稳定支撑、固定与调节接口的核心基础部件,通常采用金属制成,搭配配重、夹持或穿孔结构实现可靠固定。产品兼容 VESA 标准接口,可匹配单屏、双屏及多…

2026/10/11 10:15:24 阅读更多 →

最新新闻

C#调用WebService获取PDF转图片:选型与踩坑实战

C#调用WebService获取PDF转图片:选型与踩坑实战

做C#开发这几年,遇到“调用WebService获取PDF文件再转换为图片”这种需求不算少。可能是一个审批系统需要预览合同,也可能是一个报表平台需要把服务端生成的文件转成缩略图展示。总之核心工作就两件事:把远程文件拿下来,再在本地转…

2026/10/14 6:40:29 阅读更多 →
Unity Spark实战:在浏览器中用AI自然语言快速做游戏原型

Unity Spark实战:在浏览器中用AI自然语言快速做游戏原型

看到“Unity Spark 正式发布”这条消息的时候,我第一反应是:又来了。这年头但凡和 AI 沾边的工具都在喊“原生支持 AI”,口号早就被用烂了。但等我真正在浏览器里打开这个基于 AI 的游戏开发环境,亲手生成一个场景、跑通一次跳跃、…

2026/10/14 6:40:29 阅读更多 →
仓颉语言属性(prop)完全指南:getter/setter 定义、抽象属性与继承覆盖实战

仓颉语言属性(prop)完全指南:getter/setter 定义、抽象属性与继承覆盖实战

文档编程语言 【免费下载链接】cangjie_docs 仓颉编程语言开发者文档。 项目地址: https://gitcode.com/Cangjie/cangjie_docs 点击查看 免费下载 属性(Properties)是仓颉语言中用于间接获取和设置值的语言机制:它通过一个 gette…

2026/10/14 6:40:29 阅读更多 →
从Node到Bun:JavaScript运行时与工具链的工程效率革命

从Node到Bun:JavaScript运行时与工具链的工程效率革命

1. 为什么我把重心从 Node 切到了 Bun先说结论:Bun 不是又一个跑得更快的 Node,它是一整套重新设计过的 JavaScript 运行时和工具链。我最初对它的态度是观望,因为这类"下一代运行时"的宣发见过太多次了,但真正在几个中…

2026/10/14 6:40:29 阅读更多 →
C#对接WebService拉取PDF并转图片:从接口调到部署避坑

C#对接WebService拉取PDF并转图片:从接口调到部署避坑

前一阵接手一个对接任务,对方是个运行了十多年的老系统,只对外暴露SOAP风格的WebService接口。业务要求很直接:通过这个接口把PDF文件拉到我们这边,再转成图片,放到Web端做在线预览和打印存档。这类需求在C#开发里非常…

2026/10/14 6:40:29 阅读更多 →
纯Python实现猜数字游戏:从零到避坑的完整指南

纯Python实现猜数字游戏:从零到避坑的完整指南

猜数字游戏大概是很多人的第一个编程项目。它不像计算器那样枯燥,也不需要复杂的界面,但一套完整流程走下来,变量、输入输出、条件判断、循环、异常处理这些编程入门核心概念全部能练到。我最近又把这个小项目从头写了一遍,用最基…

2026/10/14 6:39:29 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →