我正在尝试在Redis中构建排行榜,并且能够获得最高X
分数并检索用户Y
的排名。
Redis中的排序列表看起来很容易,除了一个问题 - 我需要的分数不仅要按实际分数排序,还要按日期排序(因此,之前获得相同分数的人将排在最前面)。 SQL查询将是:
select * from scores order by score desc, date asc
在Redis中的有序集上运行zrevrange
使用类似:
select * from scores order by score desc, key desc
这将使用户使用字典上更大的键。
我能想到的一个解决方案是使用排序集内的分数字段进行一些操作,以生成由分数和时间戳组成的组合数字。
例如对于带有时间戳555
的得分111222333
,最终得分可能类似于555.111222333
,这会使新得分高于旧版(不完全是我需要但可能是进一步调整)。
这样可行,但仅限于小数字,因为排序集中的得分只有16位有效数字,因此其中10位将立即浪费在时间戳上,留下实际得分的空间不足。
如何制作排序集的任何想法都按正确的顺序排列值?我真的希望最终结果是一个排序集(以便轻松检索用户的排名),即使它需要一些临时结构和排序来构建这样的集合。
答案 0 :(得分:1)
实际上,我以前的所有答案都很糟糕。 忽略我之前的所有答案(虽然我会为了别人的利益而离开他们)。
这就是实际这样做的方式:
例如:
score_key = <whatever unique key you want to use for this score>
redis('ZADD scores-sorted %s %s' %(score, score))
redis('RPUSH score-%s %s' %(score, score_key))
然后阅读分数:
top_score_keys = []
for score in redis('ZRANGE scores-sorted 0 10'):
for score_key in redis('LRANGE score-%s 0 -1' %(score, )):
top_score_keys.append(score_key)
显然你想在那里进行一些优化(例如,只读取score-
列表中的个体,而不是阅读整个事物。)
但这绝对是做到这一点的方法。
用户排名将是直截了当的:对于每个用户,跟踪他们的高分:
redis('SET highscores-%s %s' %(user_id, user_high_score))
然后使用以下方法确定他们的排名:
user_high_score = redis('GET highscores-%s' %(user_id, ))
score_rank = int(redis('ZSCORE scores-sorted %s' %(user_high_score, )))
score_rank += int(redis('LINDEX score-%s' %(user_high_score, )))
答案 1 :(得分:0)
这不是一个完美的解决方案,但是如果你制作一个更接近当前时间的自定义纪元,那么你需要更少的数字代表它。
例如,如果您使用2012年1月1日作为您的纪元,您(当前)只需要8位数来表示时间戳。
这是ruby中的一个例子:
(Time.new(2012,01,01,0,0,0)-Time.now).to_i
这样可以在时间戳需要9位数之前大约3年,此时您可以执行一些维护以再次向前移动自定义时期。
我很想听听是否有人有更好的主意,因为我有同样的问题。
答案 2 :(得分:0)
(注意:这个答案几乎肯定是次优的;请参阅https://stackoverflow.com/a/10575370/71522)
一些想法:
例如,使用上述两种方法(在潜在的错误伪代码中):
score_small = int(score / 1000)
time_small = int((time - 1336942269) / 60)
score_key = uuid()
redis('SET full-score-%s "%s %s"' %(score_key, score, time))
redis('ZADD sorted-scores %s.%s %s' %(score_small, time_small, score_key))
然后加载它们(大约):
top_scores = []
for score_key in redis('ZRANGE sorted-scores 0 10'):
score_str, time_str = redis('GET full-score-%s' %(score_key, )).split(" ")
top_scores.append((int(score_str), int(time_str))
top_scores.sort()
此操作甚至可以使用EVAL命令完全在Redis内部完成(避免O(n)
GET
操作的网络开销)(尽管我不太了解Lua提供示例实现)。
最后,如果您期望获得真正巨大的分数范围(例如,您预计会有大量分数低于10,000且同样大量的分数超过1,000,000),那么您可以使用两个有序集合: scores-below-100000
和scores-above-100000
。
答案 3 :(得分:0)
(注意:这个答案几乎肯定是次优的;请参阅https://stackoverflow.com/a/10575370/71522)
您可以使用全局计数器,而不是在分数中使用时间戳。例如:
score_key = <whatever unique key you want to use for this score>
score_number = redis('INCR global-score-counter')
redis('ZADD sorted-scores %s.%s %s' %(score, score_number, score_key)
要按降序对它们进行排序,请选择一个较高的分数(1<<24
,比如说),将其用作global-score-counter
的初始值,然后使用DECR
代替{{ 1}}。
(如果您使用时间戳,这也适用)
或者,如果你真的非常担心玩家的数量,你可以使用每分数计数器:
INCR