单火线用Redis搞出个特别的滚动榜,效果真挺新鲜,定制化那块特别带感
- 问答
- 2026-01-16 09:16:04
- 3
行,直接开整,这事儿得从一个挺“疼”的需求说起。
我们当时有个活动,用户的各种行为能赚“热度”,然后要做一个实时更新的滚动榜,这榜吧,还不是普通的日榜、周榜,它要求特别“花里胡哨”:榜单一共就显示10个人,但你想啊,用户几千万,谁不想看看自己排第几?所以得支持查询任意用户在总榜里的实时排名,这还不算完,最要命的是这个“滚动”效果——它不像普通榜单到点清零,而是像传送带一样,最早那天的数据会悄咪咪地掉出去,最新一天的数据会加进来,榜单始终保持着固定天数(比如7天)的热度总和。

你一听可能头就大了,用MySQL?实时计算千万用户的7天滚动总和?数据库怕是直接跪了,用普通的Redis ZSet?它倒是能做实时排名,但它是处理一个总分的,咱这分数是每天都会变,而且还要“滚动”淘汰老数据,ZSet自己可没这功能。
这时候,就得“盘”一下Redis了,不能把它当个简单的缓存,得让它当个精密的计算引擎,我们搞出的方案,说起来也简单,化整为零,分而治之”。

第一招:每天都是一个独立的“账本”
我们给每一天都创建了一个独立的Redis ZSet,key的名字里带上日期,rank:20231027,用户今天获得了热度,就直接塞进当天的ZSet里,这样,处理当天的数据就非常轻快,就是单纯的ZSet操作。
第二招:搞个“总裁办”,汇总全局 光有每天的账本不行,我们还得有个看总账的地方,于是我们搞了一个“总榜ZSet”,但这个总榜ZSet里的分数,它不是直接存的热度值,那怎么存呢?这里就是关键了。

我们耍了个“小花招”:这个总榜的分数,我们不信任任何一个外部系统来计算,因为一旦服务器重启或者出问题,数据就对不上了,我们让Redis自己当会计,每当用户在某一天的热度有变化时,我们不止更新当天的ZSet,还会给总榜ZSet发送一个指令:“喂,把用户A的分数,加上这个差值(delta)。” 这个操作是用Redis的 ZINCRBY 命令完成的,这样一来,总榜ZSet里的分数,永远都是通过精确的增量计算出来的最新总和,绝对可靠,查询任意用户的实时排名,直接对这个总榜ZSet用 ZREVRANK 命令,瞬间出结果。
第三招:让榜单“滚动”起来 最核心的“滚动”效果怎么实现?我们搞了个定时任务,每天凌晨跑一次,它的任务特别清晰:
- 找出7天前那个“账本”(ZSet),比如今天是27号,就找出20号的ZSet。
- 把这个老账本里所有用户的热度值,从我们的“总榜ZSet”里减掉,怎么减?也是用
ZINCRBY,只不过分数是负的,比如用户B在20号有5分,我们就执行ZINCRBY 总榜 B -5。 - 减完之后,这个7天前的“账本”就可以删掉了,节省空间。
- 创建一个全新的、代表今天日期的“账本”ZSet,准备记录新一天的数据。
这个过程,就像是一个无限循环的传送带,总榜始终反映的是最近7天的热度总和,老数据到期自动“下车”,新数据不断“上车”,整个计算过程都在Redis内部完成,又快又准。
“定制化特别带感”体现在哪儿? 这套玩法妙就妙在它的灵活性。
- 榜单调皮: 你说不想看7天榜,想看3天榜?简单,改一下定时任务里的“天数”参数,让它去减掉4天前的数据就行了,代码核心逻辑完全不用动。
- 多榜并行: 如果我们想同时搞个“新人榜”(只统计注册3天内的用户),完全没问题,只需要在给每日ZSet和总榜ZSet起key名的时候,加个前缀比如
newcomer:rank:20231027,就轻松复制出了一套独立的榜单体系,和原榜单纯属两套账本,互不干扰。 - 效率与准确兼得: 日常的用户热度更新,都是O(log(N))的ZSet操作,速度飞起,滚动淘汰是每天一次性的批量操作,虽然要遍历一个ZSet,但放在低峰期执行,对服务基本没影响,最关键的是,数据一致性由Redis的原子操作保证,不会出现分数算错的情况。
你看,没用啥高深莫测的新技术,就是把Redis最基本的ZSet和增量计算的思想玩出了花,它不像个死板的工具,更像是一堆乐高积木,我们根据脑子里那个“滚动榜”的怪想法,亲手把它搭了出来,最后看到榜单丝滑滚动,各种定制需求都能快速满足的时候,那种感觉,确实挺带劲的。
本文由盈壮于2026-01-16发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:http://www.haoid.cn/wenda/81707.html
