使用 Redis,使用排序集实现记分板很简单,但我不确定如何使用滚动时间窗口(即 30/60/90 天窗口)实现记分板。
由于排序集条目没有任何时间成分,实现基于时间的排行榜的最佳方式是什么?
最佳答案
您可以仔细考虑以下两种通用方法(即需要填写详细信息;):
1) 使用 Redis 的 ZSET(有序集)的小数特性来存储时间和用户的(?)分数(例如,整数部分可以是时间戳,小数部分是分数)。时间戳部分的范围可以给您时间效果,但为了排序,您必须重置纪元值的“LSB”。
2) 使用不同的排行榜,每个滚动窗口一个,并定期或在接触数据库时维护它们。
第一种方法更简单,但您可能会遇到极端情况(例如, float 据类型的限制、巨大的 ZSET...),因此如果这是一个问题,您应该考虑提前对您的排行榜进行分区。
编辑 - 更多示例:
假设您的排行榜的键是 k
并且您正在跟踪 term1。天真地,当 term1 命中时,你会做:
ZADD k <epoch> <epoch>:term1
这将为您提供每个术语命中 1 秒的解决方案,但您需要更多,并且以这种方式保持计数没有意义。所以,让我们假设 <epoch*>
总是凌晨 12 点。不要单独计算每个术语的命中率,而是像这样汇总它:
escore = ZSCORE k:<epoch*> term1
if (escore == nil):
escore = 0
ZADD k:<epoch*> escore+1 term1
在滚动窗口中聚合,例如30天,决定k:<epoch*>:30d
是 <epoch*>
之间所有日期的汇总和 <epoch*>
-30天。所以每一个 term1
点击,你会做类似的事情:
# initialize today's rolling window if it doesn't exist
if not(EXISTS k:<epoch*>:30d):
ZUNIONSTORE k:<epoch*>:30d 29 k:<epoch*>-1d ... k:<epoch*>-29d AGGREGATE SUM
rscore = ZSCORE k:<epoch*>:30d term1
if (rscore == nil):
rscore = 0
ZADD k:<epoch*>:30d rscore+1 term1
这实际上是我对第二种方法的意思,所以是的,您将保留 30 个 key (只要记住在不再需要它们时删除/使它们过期)。
第一种方法包括对所有内容使用单个 k ZSET。假设您的术语计数可以达到 10000,请考虑以下针对每个术语计数和汇总执行的伪操作:
escore = ZSCORE k <epoch*>:term1
rscore = ZSCORE k <epoch*>:30d:term1
if (escore == nil):
escore = <epoch*>
if (rscore == nil):
rscore = <epoch*>
for (i=1; i++; i<30):
rscore += fractional(ZSCORE k <epoch* - i*days>:term1)
ZADD k escore+1/10000 <epoch*> + ':term1'
ZADD k rscore+1/10000 <epoch*> + ':30d:term1'
此设计使用单个 k
存储所有汇总,其中分数的整数部分是纪元(允许您做范围),小数部分是计数器 (% 10000)。
关于redis - 如何使用 Redis 实现临时排行榜?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25706925/