游戏排行榜系统的架构设计:从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/7/24 17:11:11 阅读更多 →
猫抓浏览器扩展:3大核心功能助您高效获取网页媒体资源

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

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

2026/7/24 17:11:11 阅读更多 →
全球显示器支架底座市场发展战略规划及 现状调研报告2026年版

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

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

2026/7/24 17:11:11 阅读更多 →

最新新闻

基于YOLOv5的草莓品质检测系统开发与实践

基于YOLOv5的草莓品质检测系统开发与实践

1. 项目背景与核心价值去年帮农科院做草莓品质检测时,发现传统人工分拣效率低且误差率高。一个熟练工每小时最多处理200颗草莓,而基于深度学习的视觉系统能轻松突破2000颗/小时。这个毕设项目正是瞄准农业生产中的真实痛点——如何快速准确地识别草莓并判…

2026/7/24 17:20:15 阅读更多 →
【单片机毕业设计推荐】基于 STM32 的智能人体安全监测报警装置设计与实现 ,基于 STM32 的老年人跌倒防护与定位预警系统设计(013503)

【单片机毕业设计推荐】基于 STM32 的智能人体安全监测报警装置设计与实现 ,基于 STM32 的老年人跌倒防护与定位预警系统设计(013503)

文章目录20 个相关毕业设计备选题目项目研究背景摘要总体方案核心功能技术路线项目演示关于我们项目案例源码获取博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金…

2026/7/24 17:20:15 阅读更多 →
Unity 2D游戏智能寻路:NavMeshPlus集成与实战指南

Unity 2D游戏智能寻路:NavMeshPlus集成与实战指南

1. 项目概述:为什么2D游戏也需要“智能寻路”? 在开发2D游戏,尤其是俯视角、横版卷轴或者策略类游戏时,我们经常会遇到一个核心需求:如何让游戏中的角色(NPC、敌人、我方单位)能够智能地绕过障碍…

2026/7/24 17:20:15 阅读更多 →
1.4 扣子平台简介

1.4 扣子平台简介

《扣子编程:从零开始搭建智能体 卢欣欣 清华大学出版社》【摘要 书评 试读】- 京东图书 《扣子编程:从零开始搭建智能体》全书案例分享-CSDN博客 扣子(Coze)是字节跳动推出的以智能体开发为核心、同时覆盖完整 AI 应用开发与运营…

2026/7/24 17:20:15 阅读更多 →
GPT-SOL-5.6:国产数学推理大模型在IMO测试获满分的突破

GPT-SOL-5.6:国产数学推理大模型在IMO测试获满分的突破

这次我们来看一个在数学推理领域取得突破性进展的国产模型——GPT-SOL-5.6。这个模型在IMO 2026测试中获得了满分成绩,并且解题速度惊人,最快仅需14分58秒就能完成复杂数学问题的求解。GPT-SOL-5.6作为国产数学推理大模型的最新成果,展现了在…

2026/7/24 17:20:15 阅读更多 →
Django毕业设计-基于协同过滤算法的 Django 电影个性化推荐系统设计与实现 融合协同过滤的影视智能推荐 Web 平台设计(源码+LW+部署文档+全bao+远程调试+代码讲解等)

Django毕业设计-基于协同过滤算法的 Django 电影个性化推荐系统设计与实现 融合协同过滤的影视智能推荐 Web 平台设计(源码+LW+部署文档+全bao+远程调试+代码讲解等)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/24 17:19:14 阅读更多 →

日新闻

用Highcharts 创建可拖拽三维散点立方体3D图表

用Highcharts 创建可拖拽三维散点立方体3D图表

该案例基于Highcharts scatter3d 三维散点图实现空间立方体散点可视化,核心特色:三维 X/Y/Z 三轴空间,所有散点分布在 0~10 立方体空间内;散点使用径向渐变实现立体 3D 圆球质感;支持鼠标 / 触屏拖拽画布,…

2026/7/24 0:00:29 阅读更多 →
AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口 AppCertDlls 位于 HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls。本文的程序功能是只读列出这个键在 64 位和 32 位注册表视图中的全部值,并显示每条值的来源、名称、类型和可安全显示的数…

2026/7/24 0:00:29 阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:29 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 3:59:20 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 1:23:39 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 17:49:47 阅读更多 →

月新闻