news 2026/4/23 12:46:29

排行榜设计实战:Redis ZSet 遇到“千万级玩家”积分实时更新,该如何优化?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
排行榜设计实战:Redis ZSet 遇到“千万级玩家”积分实时更新,该如何优化?

🎮 前言:当 1000 万玩家同时冲榜

场景还原:
某款 MOBA 手游开启“全服天梯赛”,预计活跃玩家 1000 万。
策划要求:“积分变化要实时反映在排行榜上,我就要看到那个排名跳动的爽感!”

作为后端开发,你自信地掏出了 Redis 的ZSet

ZADD rank_global1050"Player_A"

结果活动开启第 10 分钟,Redis CPU 飙升 100%,网络带宽被打满,客户端请求全部超时。运维查监控发现,rank_global这个 Key 的大小已经超过了 500MB。

恭喜你,制造了一个标准的“BigKey”事故。

在千万级数据下,单纯依赖一个 ZSet 是死路一条。今天,我们就来聊聊如何设计一个打不死的实时排行榜系统。


💀 瓶颈分析:ZSet 为什么会挂?

Redis 的ZSet底层使用的是跳表 (SkipList)哈希表
虽然它的增删查复杂度是O(log⁡N)O(\log N)O(logN),但在千万级数据下存在两大硬伤:

  1. BigKey 问题:一个 Key 包含 1000 万个元素,进行ZRANGEZREVRANGE时,如果页大小设置不当,会造成大量的网络 IO。更有甚者,如果这个 Key 需要迁移(集群 Rebalance),会阻塞 Redis 很久。
  2. 写并发瓶颈:Redis 是单线程写。如果全服玩家同时拿分,TPS 达到 10 万+,单机 Redis 根本扛不住高频的ZADD更新。

⚔️ 优化方案一:分治法 —— ZSet 分桶 (Sharding)

既然一个 ZSet 装不下,那我们就把它拆成 128 个,甚至 1024 个。

核心思路:

  1. 入榜:根据UserID % N将玩家分散到rank_0rank_127这 128 个小 ZSet 中。
  2. 查询 Top N:这是难点。如果我要查全服 Top 10,我必须从这 128 个 ZSet 中,分别取出各自的 Top 10,然后在应用内存中进行“归并排序”,选出最终的 Top 10。

架构图解:

Redis 集群
Hash 取模
UserID % 3 == 0
UserID % 3 == 1
UserID % 3 == 2
并行读取
并行读取
并行读取
返回各自 Top 10
返回各自 Top 10
返回各自 Top 10
内存归并排序
Key: rank_part_0
应用层分发
Key: rank_part_1
Key: rank_part_2
海量玩家写请求
查询全服 Top 10
应用层
最终 Top 10
  • 优点:彻底解决了 BigKey 问题,写入性能线性扩展。
  • 缺点:查询 Top N 变复杂了。但通常排行榜只看前 100 名,归并 128 * 100 个数据的开销完全可以接受。

🚀 优化方案二:降维打击 —— 头部实时,尾部离线

策划说要“实时”,但真的所有 1000 万人都关心自己是第 9,999,998 名吗?
显然不是。只有头部玩家(前 1000 名)在乎毫秒级排名,普通玩家只要知道自己大概在前 50% 就行了。

设计策略:

  1. Top K 实时榜:Redis 中只保留积分最高的Top 5000用户。
    • 当玩家积分变化时,如果积分 > 第 5000 名的积分,则ZADD进 Redis。
    • 如果 Redis 元素超过 5000,则定期移除末尾用户。
  2. 全量离线榜:所有用户的完整积分数据存入 MySQL 或 HBase。
    • 普通用户的排名,通过定时任务(每 5 分钟)或者查询数据库统计得出。
    • 或者直接显示“未上榜”。

代码逻辑示例 (Lua 脚本保障原子性):

-- ARGV[1]: 玩家 ID-- ARGV[2]: 新积分-- ARGV[3]: 排行榜容量 (如 5000)localkey=KEYS[1]localscore=tonumber(ARGV[2])-- 1. 更新分数redis.call('ZADD',key,score,ARGV[1])-- 2. 检查是否超出容量localcount=redis.call('ZCARD',key)ifcount>tonumber(ARGV[3])then-- 移除分数最低的那个人 (排名在 0 到 count-limit 之间的人)redis.call('ZREMRANGEBYRANK',key,0,count-tonumber(ARGV[3])-1)end

🛡️ 优化方案三:写缓冲 (Write-Back)

在直播间刷礼物场景下,土豪手速极快,一秒钟送出 100 个“666”。
如果每次送礼都请求一次 Redis,网络开销太大。

策略:本地聚合

  1. 在应用服务器(Java/Go)内存中维护一个ConcurrentHashMap<UserId, Score>
  2. 收到加分请求时,只更新内存。
  3. 每隔1 秒,或者累积满 100 个操作,批量将变化刷入 Redis (ZINCRBY)。

这样可以将 Redis 的写 QPS 降低一个数量级(10w -> 几千)。虽然有 1 秒的数据延迟,但在用户体验和系统稳定性之间,这是极佳的权衡。


📊 总结

设计千万级排行榜,不要试图用一个 ZSet 解决所有问题。

  1. 要分片:避免 BigKey 阻塞 Redis。
  2. 要截断:只存头部数据,尾部数据走数据库。
  3. 要缓冲:合并高频写入,保护 Redis 带宽。

没有完美的技术,只有最适合业务场景的架构。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 7:28:07

27、设计与实现 SNMP MIB:从基础到实践

设计与实现 SNMP MIB:从基础到实践 1. 设计 SNMP MIB 之报警表 报警表是 MIB 的核心部分,用于展示区域和报警状态。以下是在 MIB 中定义该表的详细信息: -- ----------------------------------------- -- LAD Alarm Table -- ----------------------------------------…

作者头像 李华
网站建设 2026/4/20 20:57:25

34、Linux 系统中帧缓冲设备驱动配置与数据库到文件实用工具详解

Linux 系统中帧缓冲设备驱动配置与数据库到文件实用工具详解 在 Linux 系统中,正确配置帧缓冲设备驱动以及合理使用数据库到文件的实用工具,对于系统的正常运行和高效管理至关重要。以下将详细介绍相关内容。 帧缓冲设备驱动配置 vesafb 驱动配置 驱动特性 :vesafb 驱动…

作者头像 李华
网站建设 2026/4/21 11:40:39

像素字体创作革命:Fusion Pixel Font 深度创作指南

像素字体创作革命&#xff1a;Fusion Pixel Font 深度创作指南 【免费下载链接】fusion-pixel-font 开源像素字体。支持 8、10 和 12 像素。 项目地址: https://gitcode.com/gh_mirrors/fu/fusion-pixel-font 在数字设计的世界中&#xff0c;像素字体正经历着一场真正的…

作者头像 李华
网站建设 2026/4/18 13:33:22

HoRain云--Java HTTP请求全攻略:从入门到精通

&#x1f3ac; HoRain 云小助手&#xff1a;个人主页 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个超棒的服务器购买网站&#xff0c;性价比超高&#xff0c;大内存超划算&#xff01;忍不住分享一下给大家。点击跳转到网站。 目录 ⛳️ 推荐 …

作者头像 李华
网站建设 2026/4/21 6:52:11

Access数据库引擎64位版本深度解析与实战指南

Access数据库引擎64位版本深度解析与实战指南 【免费下载链接】AccessDatabaseEngine_X64下载与安装指南 本仓库提供了一个名为 AccessDatabaseEngine_X64.zip 的资源文件&#xff0c;该文件用于解决在开发过程中遇到的“Microsoft.Jet.OLEDB.4.0”提供程序未在本地计算机上注册…

作者头像 李华