Administrator
Administrator
发布于 2026-01-07 / 3 阅读
0
0

Redis 场景题:1000万人在线,如何设计实时排行榜?(面试复盘)

Redis 场景题:1000万人在线,如何设计实时排行榜?(面试复盘)

前言

ZSet肯定不行,但我当时也不知道怎么改,然后就被问穿了。现在整理了一下这道题的解题思路,想看看大家觉得这个方案有没有漏洞。


场景分析

  • 数据规模:1000 万用户(DAU)。
  • 并发量:假设活动高潮期,1000 万人中有 1% 的人同时操作,写 QPS 可能高达 10 万。
  • 核心需求:实时更新用户积分,并展示前 100 名。

Redis 单机极限:

类型 最高QPS
纯内存读(GET) 10万 ~ 15万 QPS
简单写(SET/INCR) 8万 ~ 12万 QPS
Lua 脚本 6万 ~ 10万 QPS
ZSet 写(ZADD/ZINCRBY) 5万 ~ 8万 QPS

阶段一:全量 ZSet

Bash

# 用户送礼
ZINCRBY rank:global 100 "user_id"
# 查榜单
ZREVRANGE rank:global 0 99 WITHSCORES
  1. 性能瓶颈:Redis 是单线程的。ZINCRBY 的时间复杂度是 ​O(\log N)。当 N=1000万时,虽然 ​\log N 不算大,但面对 10万 QPS 的写入,单机 CPU 肯定被打满(通常 Redis 单机写极限在 8w 左右,复杂指令更低)。
  2. 大 Key 问题:一个 Key 存 1000 万个元素,即使只有 ID 和 Score,也是一个巨大的 Big Key。在集群扩容、数据迁移或持久化(RDB/AOF)时,会阻塞主线程,风险极高。

阶段二:Hash + Top100 ZSet

设计思路:

  1. 全量数据:用 Redis Hash 存储,userId -> score。Hash 的操作是 ​O(1)
  2. 排序数据:ZSet 里永远只留 100 个人。

每次送礼流程:

  1. HINCRBY 更新 Hash 中积分 —— O(1)
  2. 判断:
    • 是否 ≥ 当前第100名的score
  3. 如果大于:
    • ZREM 删除第100名
    • ZADD 插入当前用户

复杂度近似:M × log(100)

自我反思:

不过这种单机是扛不住的,单机最多10万写,但是即使百分之一算,那也得10万QPS了

所以还是得redis集群,将ZSet分片,此时由于网络延迟,上面这个方案查top100最低分就不可接受了.

复杂度 M ×( log(100) + 网络延迟)


阶段三:集群分片写 + 内存缓冲 + 异步聚合

1. 写入分片

不要把 1000 万人塞进一个 Key。

  • 定义 100 个桶(Bucket),比如 rank:bucket:1rank:bucket:100
  • 用户送礼时,根据 hash(userId) % 100 决定去哪个桶。
  • 这样就把 10 万 QPS 分散到了 Redis 集群的多个节点上,每个节点只承载 1000 左右的 QPS,完全没压力。

2. 服务端缓冲—— 减少网络 IO

即使分片了,请求量还在。可以在**应用服务器(后端代码)**做一层缓冲:

  • 用户送礼先写到本地内存 Map<UserId, Score>
  • 每隔 500ms,或者攒够 100 个请求,再打包通过 Pipeline 批量发给 Redis。
  • 收益:把 Redis 的写 QPS 直接降低一个数量级。

3. 异步读 —— 解决读压力

  • 搞一个定时任务(比如每秒一次)。
  • 从 100 个分桶里,分别取出各自的 Top 100。
  • 在服务内存里把这 ​100 \times 100 条数据汇总,再算一次总的 Top 100。
  • 把结果塞入一个单独的 Key:rank:final:top100
  • 前端只查这个 Key。

评论