2

Redis でリーダーボードを構築し、トップXスコアを取得して user のランクを取得できるようにしようとしていますY

Redis のソートされたリストは、1 つの問題を除いて簡単に適合するように見えます。実際のスコアだけでなく、日付でもスコアをソートする必要があります (そのため、以前に同じスコアを取得した人が一番上に表示されます)。SQL クエリは次のようになります。

select * from scores order by score desc, date asc

zrevrangeRedis でソート済みセットを実行するには、次のようなものを使用します。

select * from scores order by score desc, key desc

これにより、辞書編集的に大きなキーを持つユーザーが上に配置されます。

私が考えることができる 1 つの解決策は、スコアとタイムスタンプで構成される結合された数値を生成するために、並べ替えられたセット内のスコア フィールドを操作することです。

たとえば555、タイムスタンプ付きのスコアの場合111222333、最終的なスコアは、555.111222333新しいスコアを古いスコアの上に置くようなものになる可能性があります (正確には必要なものではありませんが、さらに調整することができます)。

これは機能しますが、ソートされたセットのスコアには有効数字が 16 桁しかないため、そのうちの 10 桁がタイムスタンプですぐに無駄になり、実際のスコアの余地があまり残らないため、小さな数字でのみ機能します。

ソートされたセットに正しい順序で値を配置する方法はありますか? そのようなセットを構築するためにいくつかの一時的な構造と並べ替えが必要な場合でも、最終結果を並べ替えられたセット (ユーザーのランクを簡単に取得するため) にしたいと思います。

4

4 に答える 4

1

実際、私の以前の回答はすべてひどいものです。以前の回答はすべて無視してください (ただし、他の人の利益のために残しておきます)。

これは、実際に行うべき方法です。

  • スコアのみを zset に保存する
  • プレーヤーがそのスコアを達成するたびにリストを個別に保存します。

例えば:

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, )))
于 2012-05-13T21:23:23.930 に答える
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使用します。DECRINCR

(これは、タイムスタンプを使用している場合にも当てはまります)

あるいは、プレーヤーの数が非常に心配な場合は、スコアごとのカウンターを使用できます。

score_key = <whatever unique key you want to use for this score>
score_number = redis('HINCR score-counter %s' %(score, ))
redis('ZADD sorted-scores %s.%s %s' %(score, score_number, score_key))
于 2012-05-13T21:15:22.873 に答える
0

これは完全な解決策ではありませんが、現在の時刻に近いカスタム エポックを作成すると、それを表すのに必要な桁数が少なくなります。

たとえば、エポックに 2012 年 1 月 1 日を使用する場合、(現在) タイムスタンプを表すのに 8 桁しか必要ありません。

ルビーでの例を次に示します。

(Time.new(2012,01,01,0,0,0)-Time.now).to_i

これにより、タイムスタンプに 9 桁が必要になるまでに約 3 年かかります。その時点で、メンテナンスを実行してカスタム エポックを再び進めることができます。

しかし、私はまったく同じ問題を抱えているので、誰かがより良いアイデアを持っているかどうか聞いてみたい.

于 2012-05-13T20:48:29.887 に答える
0

注:この答えはほぼ確実に最適ではありません。https://stackoverflow.com/a/10575370/71522を参照してください)

いくつかの考え:

  • タイムスタンプを小さくするために、タイムスタンプについていくつかの仮定を立てることができます。たとえば、Unix タイムスタンプを保存する代わりに、「2012 年 5 月 13 日からの分数」を保存できます (たとえば)。7 桁の有効数字と引き換えに、これにより次の 19 年間の時間を保存できます。
  • 同様に、スコアの有効桁数を減らすことができます。たとえば、スコアが 7 桁の範囲にあると予想される場合、並べ替えられたリストにスコアを格納するときにスコアを 10、100、または 1000 で割り、並べ替えられたリストの結果を使用して実際のスコアにアクセスします。アプリケーションレベルのもの。

たとえば、上記の両方を使用すると (潜在的にバグのある疑似コードで):

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()

この操作は、 EVALO(n) GETコマンドを使用して完全に Redis 内で実行することもできます (操作のネットワーク オーバーヘッドを回避します) (ただし、実装例を自信を持って提供するのに十分な Lua を知りません)。

最後に、非常に大きな範囲のスコアが予想される場合 (たとえば、10,000 未満のスコアが多数あり、1,000,000 を超えるスコアも同様に多数あると予想される場合)、2 つのソートされたセットを使用できます:scores-below-100000scores-above-100000.

于 2012-05-13T20:49:56.110 に答える