使用Redis,实现具有排序集的记分板是微不足道的,但我不确定如何使用滚动时间窗口(即30/60/90天窗口)实现记分板。
由于排序集条目没有任何时间组件,实现基于时间的排行榜的最佳方法是什么?
答案 0 :(得分:2)
以下是您可以考虑的两种常规方法(即需要填写的详细信息;)
1)使用Redis的ZSET(有序集)的分数性质来存储时间和用户的(?)分数(例如,整数部分可以是时间戳和分数的分数)。时间戳部分的范围可以为您提供时间效果,但是对于排序,您必须重置历元值的“LSB”。
2)使用不同的引线板,每个滚动窗口一个,定期或在触摸数据库时保持它们。
第一种方法更简单但你可能会遇到极端情况(例如浮点数据类型的限制,巨大的ZSET ......),所以如果这是一个问题,你应该考虑提前对排行榜进行分区。
编辑 - 更多示例:
我们假设您的排行榜的密钥是k
,并且您正在跟踪term1。天真地,当第1项命中时,你会这样做:
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个键(只需记住在不再需要它们时删除/过期)。
第一种方法包括使用单个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)。