news 2026/4/3 1:02:52

排行榜设计实战: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/3/24 2:34:30

云原生网关监控告警完整实战指南:智能阈值配置与告警规则设置

云原生网关监控告警完整实战指南&#xff1a;智能阈值配置与告警规则设置 【免费下载链接】higress Next-generation Cloud Native Gateway | 下一代云原生网关 项目地址: https://gitcode.com/GitHub_Trending/hi/higress 还在为云原生网关的稳定性担忧&#xff1f;不知…

作者头像 李华
网站建设 2026/3/31 9:05:25

Stirling-PDF终极指南:5个简单步骤掌握本地PDF全能工具

Stirling-PDF终极指南&#xff1a;5个简单步骤掌握本地PDF全能工具 【免费下载链接】Stirling-PDF locally hosted web application that allows you to perform various operations on PDF files 项目地址: https://gitcode.com/gh_mirrors/st/Stirling-PDF 还在为PDF文…

作者头像 李华
网站建设 2026/3/24 1:13:10

Iced编译配置终极指南:从特性优化到跨平台部署提速

Iced编译配置终极指南&#xff1a;从特性优化到跨平台部署提速 【免费下载链接】iced A cross-platform GUI library for Rust, inspired by Elm 项目地址: https://gitcode.com/GitHub_Trending/ic/iced 作为受Elm启发的跨平台GUI库&#xff0c;Iced通过精细的编译配置…

作者头像 李华
网站建设 2026/3/8 21:11:28

Loxodon Framework完全实战指南:Unity MVVM框架高效开发方案

Loxodon Framework完全实战指南&#xff1a;Unity MVVM框架高效开发方案 【免费下载链接】loxodon-framework An MVVM & Databinding framework that can use C# and Lua to develop games 项目地址: https://gitcode.com/gh_mirrors/lo/loxodon-framework 在Unity游…

作者头像 李华
网站建设 2026/3/31 6:23:29

科学家破解二维半导体之谜

科学家破解二维半导体之谜都柏林圣三一学院团队的研究成果&#xff0c;揭示了解锁数十种新型二维半导体的方法。迄至今日&#xff0c;二维半导体材料的探寻与开发&#xff0c;在很大程度上仍依赖于反复试验的方式。当下&#xff0c;都柏林圣三一学院物理学院与AMBER&#xff08…

作者头像 李华