✅如何实现百万级排行榜功能?

典型回答

关于排行版,我们介绍过基于 ZSET 可以非常简单的实现:

✅基于Redis的zset实现秒级排行榜

但是,如果数据量很大呢?这个方案又会遇到哪些问题,以及该如何解决这些问题呢?

需要解决的问题

数据存储和管理(重要)

首先,第一个关键的问题,就是百万级用户参与排行榜的数据如何进行存储和管理,首先我们之前的文章介绍过,使用 Redis 的有序集合(ZSET)作为主要数据结构来存储排行榜数据是非常典型的做法,因为ZSET 提供了高效的插入、删除,尤其是按分数范围查询的功能

但是,这里面需要考虑一个关键问题,那就是 bigkey 问题

✅什么是大Key问题,如何解决?

对于 Set 类型的 Value 值,含有的成员数量为 10000 个就已经可以算作是大 key 了,如果有100万,那妥妥的大 key。

那么我们就需要解决这个使用 zset 存储带来的大 key 问题。

排行计算和更新

作为一个排行榜,我们需要实时、高效的更新排行榜的数据,那么,如何处理用户分数的实时更新和排名计算,同时保证系统的性能和实时性,是一个至关重要的问题。

数据分页和查询效率

我们的排行版,假如说要看比较靠后的排名,比如第50万到51万的排名的情况?(当然,一般很少有这种需求),那我们就要考虑当数据量庞大时,如何高效地进行排行榜数据的分页查询和排序,避免性能下降。

并发访问的问题(重要)

大量用户同时访问和更新排行榜数据可能会引发并发竞争和数据一致性问题。这个我们要如何解决?

容灾和数据一致性问题

如何保证系统容灾性,避免排行版数据丢失呢?如果用持久化存储,那么一致性问题又如何保障呢?

解决方案

数据分片和分区(重要)

为了解决大量用户存在一个 ZSET 中出现的 bigkey 问题,我们可以将采用分片存储的方式,将原来分布在一个 ZSET 的中的数据,拆分到多个 ZSET 中,按照一定的规则进行拆分路由即可,比如按照日期、地域等进行拆分。

比如如果我要做一个全国的排行榜,那么我不需要把全国的数据都放在一个 ZSET 中,我只需要按照省份,把不同的省份的数据单独放到一个 ZSET 中,排出每个省的排行榜,然后需要查询全国的排行榜的时候,在进行一次跨 ZSET 的排序就行了。

比如我要查询全国前10,那么我就查询出每个省的前10名,然后再基于这么多个省的数据,进行一次综合排名就行了。

甚至我可以直接维护一个全国前3400的排名(全国有34个省级行政区),这里面的数据就是定时从省级前100的排名中取出来放过来的。这样就可以快速的获取全国前100了。

这里的省份只是举例子,你可以不用这些信息,就直接基于用户 id 的范围划分也可以的。

异步&批量更新(重要)

为了提升整体的吞吐量,我们可以对于分数的变化的写入操作,做成异步化的方案,只在用户分数变动时进行更新操作,而不是每次用户操作都立即更新排行榜数据。这样可以降低直接写入 zset 的频率,减少并发写入时的竞争。

同时也可以使用MQ 或者定时任务来处理批量更新操作,,例如每隔一段时间重新计算一次排行榜,或者删除一些不再活跃的用户数据,以控制 zset 的大小和内存消耗。降低实时更新对系统性能的影响。

分页查询优化

为了支持大规模数据的分页查询,可以使用 Redis 的 ZRANGE 命令结合 WITHSCORES 参数,或者 ZREVRANGE 命令进行倒序查询,结合 LIMIT 参数来实现分页功能。这样可以避免一次性获取全部数据带来的性能和内存压力。

自动过期和预加载(重要)

为了保证排行版的实时性,以及降低存储量,我们还可以考虑使用 Redis 的自动过期功能或者手动刷新缓存来保持数据的实时性。

对于频繁查询的排行榜数据,可以考虑在应用层实现本地缓存,减少对 Redis 的直接查询压力,提升访问速度和用户体验。

当然,这可能会带来不一致的问题,需要考虑业务上是否能接受,以及能接受的时间延迟是多少。

Redis 分布式部署(重要)

为了提升排行版的容灾能力,我们需要考虑使用 Redis Cluster 或者将 Redis 部署在多个节点上,进行数据分片和负载均衡。这样可以提高系统的并发处理能力和容错能力。

持久化

定期备份 Redis 数据,比如同步到数据库中,以防止因为Redis 宕机导致的数据丢失。备份可以通过 Redis 的持久化功能(AOF+RDB)结合定时任务实现。

同时,配置监控系统来实时监视 Redis 的运行状态,包括内存使用情况、持久化状态、主从复制状态等,及时发现并解决潜在的问题。

原文: https://www.yuque.com/hollis666/xkm7k3/tym5ygcdfyg4tk1n