面试官:如何设计一个亿级用户排行榜?
创始人
2026-01-06 16:01:22

面试刷题网站:

面试时被问到"如何设计排行榜系统",可别以为是考简单的排序算法。

一旦场景升级到用户量破亿、每秒要处理 10 万次排名查询,同时数据还得保持分钟级更新时,你会发现这道题藏着分布式系统设计的几乎所有核心挑战。

今天牛哥就从需求拆解开始,一步步推导出能支撑亿级用户的排行榜架构。

明确需求

设计前不明确核心需求,就像航海没有指南针。亿级用户场景下,这四个指标直接决定系统成败,每个都要给出量化标准。

1. 实时性是关键

实时性怎么定义才合理?实测数据告诉我们:

  • 核心榜单(游戏战力榜)更新延迟超过5秒,用户投诉率会上升15%;

  • 非核心榜单(周销量榜)可放宽到分钟级,但必须在前端明确提示"5分钟更新一次"。

这里的关键是用户感知:哪怕数据是异步更新的,也要通过前端动效让用户觉得实时生效。

2. 准确性是底线

榜单的核心价值在于传递可信信息,要做到「最终一致 + 不丢数据」,需要满足以下要求:

  • 用户行为必须 100% 接入计算,确保数据无遗漏;

  • 分数更新需采用原子化处理,避免并发场景下的计数错误;

  • 系统发生故障时,数据恢复后仍能准确追溯完整记录;

  • 亿级数据规模下,针对分片间数据同步可能产生的短暂不一致,需设计数据对账机制,进行跨分片的分数总和校验。

3. 抗压力决定系统能否"活下来"

双11零点的销量榜、游戏版本更新后的战力榜,峰值QPS可能从日常的 1 万飙升到100万。这时候不仅要扛住,还要保证P99 延迟 < 200ms,一旦超过这个阈值,用户会明显感觉"卡顿"。

4. 灵活性关系到业务迭代

业务方经常提需求 "今天要日榜,明天加个周榜,后天还得支持按「销量+好评率」混合排序" 。如果每次调整都要改代码、重启服务,技术团队会被业务拖着走,

所以设计时要预留「 排序规则动态配置」能力。比如用JSON配置权重因子, 无需发版就能生效。

{"like":2,"comment":3,"share":5}

技术选型:"扛亿级"的工具组合

需求明确后,下一步是选对工具。很多同学一上来就说"用Redis ZSet",但真实场景的选型要复杂得多。这三个核心工具的组合,直接决定系统的性能天花板。

Redis ZSet:为实时榜单而生

Redis的 ZSet 为什么是实时排行榜的首选?看三个核心特性:

  • O(logN)的分数更新:ZINCRBY 命令能原子化更新分数,亿级数据量下依然高效;

  • 内置排序能力:自动按score排序,无需额外计算;

  • 丰富的范围查询:ZREVRANGE(查TOP N)、ZCOUNT(查分数区间人数)等命令完美匹配排行榜需求;

这里我们想知道,为什么不用数据库或搜索引擎呢?

看实际表现就很清楚:MySQL的ORDER BY在百万级数据时就会卡顿;Elasticsearch虽然支持排序,但写入延迟和资源消耗远高于 Redis

对于90%的实时榜单场景,ZSet的性价比无人能敌。

但ZSet有个致命缺点:单Key存储上限。

Redis 单 Key 的存储大小建议控制在 100MB 以内。但在实际场景中,如果每个用户的榜单数据约为 16字节,1 亿用户的 ZSet 总大小就约为 1.6GB,远超最佳阈值 ,会导致持久化慢、主从同步延迟等性能问题。

所以亿级场景必须配合 Redis Cluster 分片,将大Key拆分成多个小 Key, 存储在不同节点,每个分片只存100万用户,大小约16MB,符合Redis最佳实践。

定时任务+分布式计算:非实时榜单的最优解

不是所有榜单都需要实时更新。日销量榜、周热门榜这类"周期结算型"榜单,用定时任务预计算比实时计算节省 90% 资源。但亿级场景下,普通定时任务框架不够用,需要分布式计算引擎配合。

XXL-Job + Spark/Flink 怎么选?看数据量和计算复杂度:

  • XXL-Job + 分片执行:适合数据量中等(千万级)、计算逻辑简单的场景。比如全平台日销量榜,数据量不算很大。这种方案支持控制台暂停/恢复任务,失败重试策略也很完善。

  • Spark/Flink 批处理:适合亿级数据规模、计算逻辑复杂的场景。以内容热度周榜为例,计算过程中需要关联用户画像和时间衰减因子, Spark 的 DataFrame API 能够高效处理这类多表关联计算。

实际项目中建议 分层计算:核心日榜采用 XXL-Job,因其对实时性要求较高;历史月榜选用 Spark 批处理,依托凌晨时段进行计算,能有效降低资源成本。

多存储体系:冷热数据的分层存储

Redis适合存热数据,比如实时榜、近7天榜单,但历史数据如用户历史排名记录,需要长期存储,这时候要构建多级存储体系,降低成本。

数据类型

存储介质

存储周期

访问延迟

月成本(1亿条数据)

热数据

Redis Cluster

7天

<1ms

约5万元(100GB内存)

温数据

MySQL分表

3个月

<100ms

约5000元(100GB SSD)

冷数据

ClickHouse/Hive

永久

<1s

约500元(1TB HDD)

架构拆解:四层核心逻辑

工具选好了,接下来是搭架构,一个能抗住亿级用户的系统,一定是职责分明的。从用户行为产生到最终展示,排行榜系统可拆成 "数据接入层 - 计算排序层 - 存储层 - 展示层" 四层架构:

每层专注解决一类问题,这样即使流量翻10倍,也能通过分层扩容扛住。

数据接入层:消息队列 + 多活部署

用户的每次、点赞,都是榜单数据的源头。亿级场景下,数据接入层的目标是「 不丢数据、低延迟、高可用」,单Kafka集群不够用,需要 多活部署 + 异地容灾

比如电商平台的「全国商品销量榜」,用户广泛分布于华北、华东、华南三大核心区域,需在这三个地域分别部署 Kafka 集群,各区域用户行为数据直接写入本地 Kafka 集群;然后通过 Kafka MirrorMaker 工具,将各区域的商品销量数据跨地域同步;

数据接入层消费到 Kafka 中的行为消息后,按照特定规则计算出商品销量的实时变动。最后调用榜单系统的分数更新接口,实现榜单动态刷新。

为什么必须多活?设想一下:若仅依赖华北地区的单 Kafka 集群,一旦集群故障,华北用户的下单、加购数据将无法接入, 直接导致「全国商品销量榜」 缺失华北区域数据

而多活部署能确保任一地域集群出现问题时,其他地域集群仍能正常工作,RTO(恢复时间目标)可控制在 5 分钟内。

此外,电商数据接入层还需做好两大关键保障:

  • 流量控制:借助 Kafka 的配额机制(Quota)限制单个生产者或消费者的流量,例如设定单商家每秒最多发送 100 条商品点击数据,防范恶意刷流量的攻击;

  • 死信队列(DLQ):专门存储处理失败的消息,像订单 ID 不存在、用户账号已注销却产生下单数据等异常消息,都会被导入死信队列,后续可定期安排人工排查处理,避免数据丢失。

计算排序层:按场景选择架构

计算层是排行榜的"大脑",负责把原始分数转化为有序排名。亿级场景下,没有万能方案,只有按场景选择的混合架构。

实时计算:基于 Lua 脚本的即时计算

适合游戏战力榜、直播人气榜这类更新频率在秒级、数据量中等(千万级)的场景。当用户产生行为后,系统执行的命令示例如下:

1. 当用户产生行为后,通过 Lua 脚本直接操作 ZSet 完成原子化更新:

-- 原子化更新分数并返回最新排名(降序排名,战力高排第1)

localnewScore = redis.call('ZINCRBY', 'game_power_ranking:server1', 50, 'user:10086')

localrank = redis.call('ZREVRANK', 'game_power_ranking:server1', 'user:10086')

return{newScore, rank}

2. 同步更新本地缓存 Caffeine(Java 示例):

// 使用Caffeine更新本地缓存(用户排名与分数)

caffeineCache.put("user:10086:rank", rank);

caffeineCache.put("user:10086:score", newScore);

通过这种方式,既保证了 Redis 集群中分数更新的原子性,又通过 Caffeine 本地缓存提升了后续查询效率,让排名变化能实时反馈给用户。

批量处理:Spark + ClickHouse

适合内容热度周榜、电商月销量榜这类场景。更新频率不高,通常是小时级或天级,数据量却能达到亿级规模。

每天凌晨3点,Spark 从 Kafka 消费全量行为数据,结合用户画像、时间衰减因子等维度算出最终分数,之后批量写入 ClickHouse,再同步到 Redis ZSet 中供查询。

混合计算:Flink 实时处理 + 批处理

适合「实时+历史」双维度的榜单,比如综合热度榜,既关注当下的实时热度,也参考 7 天内的累计表现,其运作流程为:

用户刚产生的点赞、评论等即时行为,会由 Flink 实时捕捉并计算出对应的实时分;到了每天凌晨,Spark 会启动批处理任务专门核算历史分,例如 7 天前的互动数据影响力会减弱,权重调整为 0.5;

之后按照 “实时分占 70%、历史分占 30%” 的公式算出综合分,比如某内容实时分 80 分、历史分 60 分,综合分就是

80(实时分) × 0.7 + 60(历史分) × 0.3 = 74 分;

而 0.7 和 0.3 这样的权重比例会预先存在 Redis 的 Hash 结构中,方便灵活调整, 最终的综合分会写入 Redis ZSet,由它完成排序,既保证实时性又兼顾历史数据的影响。

存储层:Redis Cluster + 多级缓存 + 冷热分离

存储层是排行榜的"数据底座",在亿级用户的榜单场景下,存储层需要同时满足查询快和成本省的需求,核心靠 Redis Cluster 分片、多级缓存、冷热分离这三大策略搭配实现。

Redis Cluster分片:分而治之

面对亿级用户的榜单数据,我们按用户 ID 做哈希分片:

  • 把数据均匀分成 100 个分片,用「用户 ID 除以 100 取余数」的方式,确定每条数据该存入哪个分片;每个分片大约存 100 万用户的榜单数据,约 16 MB

  • 再把这些分片分散到 10 个 Redis 节点上,每个节点负责 10 个分片;

后续如果数据量增长,只需直接增加 Redis 节点,系统会自动迁移分片到新节点,轻松实现 “横向扩容”。

多级缓存:让查询层层加速

大多数用户查榜单,只看热门内容或自己的排名,没必要每次都查全量数据。所以我们用 「三级缓存」分层承载查询需求:

  • 本地缓存(Caffeine):直接存在应用服务器的内存里,专门缓存 TOP20 的热门榜单,1 分钟刷新一次。承载 90% 的首页榜单查询,延迟不到 1 ms,快得像读本地文件;

  • Redis Cluster:存储 TOP 1000 榜单数据 + 用户个人分数,5分钟刷新一次,承载 10% 的非首页查询

  • ClickHouse/MySQL:存储历史榜单 + 完整排名数据,按需查询,如用户主动查看"我的历史排名"数据

冷热分离:给 Redis 减负

Redis 用内存存储,速度快但成本高,而超过 7 天的榜单数据,用户查询频率会大幅下降。所以我们每周日凌晨做一次 “ 冷热数据搬家”。

将超过7天的实时榜数据从Redis Cluster 同步到 ClickHouse,同步完成后删除 Redis 中的历史数据,只留下索引方便后续快速定位。

迁移过程用「 双写一致性」保证:先写ClickHouse,成功后再删Redis,避免数据丢失。

展示层:CDN + API 网关 + 应用集群

展示层直接面对用户请求,核心目标是做到「 毫秒级响应、全球低延迟、扛住高并发。亿级场景下,单应用集群不够用,需要CDN加速、API网关限流、多地域应用集群三者配合。

CDN 加速静态榜单

针对首页 TOP20 这类访问频率极高的榜单,我们会先把数据生成静态 JSON 文件,再通过 CDN 分发到全球各地的节点。这样一来,不管用户在哪个地区,都能从离自己最近的 CDN 节点获取榜单数据,延迟控制在 50 毫秒以内,打开页面几乎秒加载。

为了保证数据不过时,我们给 CDN 缓存设置了 1 分钟的有效期,同时借助 API 网关的主动刷新(PURGE)机制,只要榜单数据更新,就能立刻触发 CDN 节点缓存同步,既兼顾了速度,又能让用户看到最新排名。

API 网关动态路由

全球用户的查榜请求,会先汇总到 API 网关。它主要做两件事:

  • 一是动态路由,根据用户所在地区,自动把请求转发到最近的应用集群,比如北美用户的请求直接分配到美东集群,进一步缩短跨地域访问的延迟;

  • 二是限流保护,给单个用户、单个 IP 设定访问上限,比如限制每个用户每秒最多查 5 次榜单,避免恶意刷量冲垮后端服务。

应用集群弹性扩容

应用集群基于 K8s 部署,搭配 HPA 机制,也就是水平 Pod 自动扩缩容,根据实际流量自动调整资源。

比如把 CPU 利用率 70% 设为阈值:当超过 70% 时,比如晚间 8-10 点用户查榜的高峰时段,集群会自动增加 Pod 数量,最多能扩展到 100 个;而当流量回落,CPU 利用率降低时,又会自动缩减 Pod,最低保留 8 个。

这样既保证了高峰期能扛住压力,又避免了低峰期的资源浪费。

关键实现:亿级场景的避坑指南

基础架构搭好后,系统可能能用,但未必扛得住亿级流量。这些关键实现细节,决定了系统从及格到优秀的差距。

跨分片查询:从慢合并到预计算加速

查询全服 TOP100 榜单时,需要从100个分片中各查 TOP100,得到100×100=10000 个候选结果后,再合并排序取 TOP100。

亿级场景下,这个过程异常耗时,显然无法满足用户对"秒开"的需求,需要优化。

1. 预计算候选集

虽然数据量增加10倍,但能 避免"分片内 TOP100 之外的用户,实际可能是全局 TOP100" 的情况

2. 分布式合并计算

在应用层,我们用 Java 的 PriorityQueue 也就是小顶堆来合并候选结果,把堆的大小固定为 100。遍历所有候选数据时,只要当前用户的分数高于堆顶分数,就替换堆顶元素,最终堆里剩下的就是全局 TOP100。

3. 终极优化 — 分层合并

终极解决方案是「分层合并」:先把 100 个分片按机架或可用区,分成 10 个组,每组包含 10 个分片。

第一步先在组内合并,每个组算出自己的 TOP1000;第二步再合并 10 个组的 TOP1000,得到最终的全局 TOP100。

组内合并可以直接在 Redis Proxy 层完成,这样应用层只需发起 10 次组查询,再做一次全局合并即可。优化后总耗时降到 80ms(10 次查询 ×5ms + 合并 30ms),完全满足亿级场景的响应要求。

数据一致性:从最终一致到可追溯

亿级场景下,绝对一致性无法实现,因为跨分片实时同步成本太高,但要保证「 最终一致+可追溯」。 具体通过三层策略实现:

实时数据一致性

主要通过保证单分片原子性,跨分片定期修复的策略实现:

  • 单分片内:用 Lua 脚本保证「查询状态 + 修改分数」的原子性,确保高并发下不会出现 “同一用户重复通关,导致通关积分重复增加” 的脏数据。

  • 跨分片间:允许短暂的不一致,但每天凌晨会启动定期对账,比对各分片的总分,发现偏差后自动修复。

历史数据一致性

主要通过「批处理对账 + 告警排查」实现。每天用 Spark 批处理计算用户的每日总分,再和 Redis Cluster 中存储的分数总和对比,允许误差控制在 0.1% 以内;如果超过这个阈值,就会触发告警,提醒工程师排查原因。

用户行为可追溯

每一次分数更新操作,都会记录详细日志,包括用户 ID、行为类型、分数增减量、操作时间戳、请求 ID 等。日志通过ELK存储,支持按用户ID/时间范围查询,当用户投诉分数异常时,工程师能通过日志快速定位问题根源。

监控告警体系:亿级下的可观测性

亿级系统"黑盒运行"等于裸奔,必须构建完善的监控告警体系,覆盖分片健康度、数据一致性、性能指标。

1. 分片健康度监控

监控每个 Redis 分片的QPS、内存使用率、响应时间,尤其是响应时间重点关注 P99、P999 分位值。一旦任一指标触及设定的阈值,比如 QPS 超过 1 万、内存使用率超过 80%、P99 响应时间超过 100ms,就会立刻发送告警;

同时监控分片迁移状态,要是迁移速度低于 10MB/s,同样会触发告警,以此避免迁移超时影响服务的可用性 。

2. 数据一致性监控

  • 实时监控:每分钟计算 Redis Cluster 的总分波动,若当前总分与 5 分钟前的差值超过 10 万,立即告警。

  • 离线对账:每天凌晨比对 Redis 和 ClickHouse 中的历史分数,误差超过 0.1% 就触发告警,确保历史数据不丢不错。

3. 用户体验监控

通过前端埋点收集榜单加载时间,P95>500ms 时触发告警,及时发现CDN缓存失效、应用集群过载等问题。

总结:亿级排行榜的设计心法

设计亿级用户排行榜,本质是对"实时性 - 准确性 - 成本 - 可用性"的四重权衡。记住这6个核心原则,无论面试还是实战都能游刃有余:

1. 大 Key 必须分片,小 Key 优化存储:单ZSet存不下亿级用户,用Redis Cluster按哈希分片;非热门数据启用ziplist编码

2. 实时用 Redis+Lua,批处理用 Spark/Flink

3. 跨分片查询分层合并:先组内合并再全局合并

4. 多级存储控成本:热数据、温数据、冷数据分层存储

5. 数据一致性可追溯:单分片原子操作+定期对账+行为日志,保证最终一致且问题可追溯

6. 监控容灾不可少:分片健康度、数据一致性、用户体验全链路监控,确保系统活下来

最后想说,面试时被问到这类问题,别再直接说"用Redis ZSet"了,先问清楚业务场景,再给出分层方案,这才是面试官想看到的系统设计能力。

---

相关内容

热门资讯

如果没有外卖,你还能过下去吗影... 如果没有外卖,你还能过下去吗影响不大。 外卖对我来说,只是不方便,时间紧,或想吃点新鲜东西的渠道,...
realme为何在此时回归OP... 1月7日,多家媒体报道,智能手机品牌realme将回归OPPO,成为旗下子品牌。 听潮TI已向OPP...
原创 场... 常规赛,奇才主场120-112战胜魔术。 这场比赛,奇才球星、34岁的老将CJ麦科勒姆打出了相当不错...
如何理解贝拉·塔尔电影中的时间... 1955年,贝拉·塔尔出生于匈牙利。1979年,贝拉·塔尔完成《居巢》,这是他的首部作品。他的代表作...