推奨学習: Redis ビデオ チュートリアル
日々の開発では、ユーザー スコアなどを計算する必要に遭遇することがよくあります。たとえば、ゲームでは戦闘効率をランク付けする必要があり、チーム活動では各チームの貢献値をランク付けする必要があり、WeChat では各友達の歩数をランク付けする必要があります。この場合、一般に、Redis の順序が選択されます。コレクションには、ランキングのニーズを満たすためにユーザーのスコアが保存されます。ただし、シナリオごとのランキング方法も若干異なります。以下は、私の日々の開発に基づいた要約です。
要件: チーム アクティビティにおける各チームの貢献値をランク付けします。
Redis のソート セットは、文字列型の順序付きセットです。セットのメンバーは一意であるため、セット内に重複したデータが存在することはできません。
各要素は double 型のスコアに関連付けられます。 Redis はスコアを使用して、コレクションのメンバーを小さいものから大きいものまで並べ替えます。
順序付きセットのメンバーは一意ですが、スコアは繰り返すことができます。
同じポイントを持つ状況を無視して、ランキング リストを実装します:
// 准备数据,其中value为每个队伍的ID,score为队伍的贡献值 > zadd z1 5 a 6 b 1 c 2 d 10 e (integer) 5 // 分页查询排行榜所有的队伍和贡献值,要使用zrevrange,而不是zrange,贡献值越大越排在前面 > zrevrange z1 0 2 withscores 1) "e" 2) "10" 3) "b" 4) "6" 5) "a" 6) "5" // 增加某个队伍的贡献值 > zincrby z1 3 d "5" > zincrby z1 4 c "5" // 查询排行榜所有的队伍 > zrevrange z1 0 -1 withscores 1) "e" 2) "10" 3) "b" 4) "6" 5) "d" 6) "5" 7) "c" 8) "5" 9) "a" 10) "5" // 查询某个队伍的排名 > zrevrank z1 d (integer) 2
Redis のデフォルトの実装では、同じスコアを持つメンバーが辞書順 (09、AZ、上記で使用しているa~z)はzrevrangeなので逆順となり、同じスコアでのソートは時間優先でソートできません。
上記の実装では、2 つのチームの貢献値が同じ、つまりポイント値が同じである場合、時間に基づいてランク付けすることはできません。
したがって、 スコア = 貢献値のタイムスタンプ を設計する必要があります。スコアが高い人が最初にランクされます。最後に、スコアに基づいて貢献値を分析する必要があります。
スコア値の保存には整数を使用します。redis のスコア自体は double 型であり、正確に保存できる最大整数は 2^53=9007199254740992 (16 ビット) です。 )。ミリ秒まで正確なタイムスタンプには 13 桁が必要で、ストレージ寄与値には 3 桁のみが残ります。現在、時刻が秒まで正確な場合、必要なのは 10 桁のみで、寄与値には 6 桁が残ります。
全体的な設計: 上位 3 ビットは寄与値を表し、下位 13 ビットはタイムスタンプを表します。
単純にスコア構造をまとめると、貢献値 * 10^13 タイムスタンプ
になります。スコアが大きいほどスコアは近くなり、タイムスタンプが小さいほどスコアは近くなります。このように、2つの判定ルールは逆のものもあり、単純に両者を組み合わせてスコア化することはできません。
しかし、逆に考えて、同じ十分に大きな数値 Integer.MAX からタイムスタンプを減算することもできます。タイムスタンプが小さいほど、得られる差が大きくなるため、スコアの構造を変更できます。値に貢献します。 to: * 10^13 (Integer.MAX-timestamp)
これにより、ニーズを満たすことができます。
redisのスコア値はdouble型なので、整数部分に貢献値を格納し、小数部分にタイムスタンプを格納し、最大値を使用することができます。同じタイムスタンプの部分からそれを減算します。
このようにして、全体的な設計は次のようになります: スコア = 貢献値 (Integer.MAX-タイムスタンプ) * 10^-13
欠点: スコア値は変数を使用して計算されるため、チームに貢献値を追加する場合、以前のzincrbyを使用してスコア値を変更することはできず、同時条件でチームに貢献値を追加すると、スコアが不正確になります。価値観。
エラー状況シミュレーション:
チーム A の現在の貢献値が 10 であるとします。チーム A のプレイヤー X がチームに貢献値を 1 追加し、プログラムでスコアが計算されます。チーム A のプレーヤー Y はチームに貢献値 1 を追加し、スコアはプログラムで 11 になるように計算されます。yyy チーム A のプレーヤー X が redis の zadd コマンドを呼び出して、チームの貢献値を 11 に設定します。 .xxx チーム A のプレイヤー Y が redis の zadd コマンドを呼び出します。 チームの貢献値を 11.yyy に設定します。 最終的に、チーム A の貢献値は 11 と計算されます。貢献値を増やす操作のアトミック性は保証できません。
現時点では、lua スクリプトを使用して、貢献値の計算と設定の 2 つの操作のアトミック性を確保する必要があります。
// 其中KEYS[1]为排行榜key,KEYS[2]为队伍ID // 其中ARGV[1]为增加的贡献值,ARGV[2]为Integer.MAX-时间戳 local score = redis.call('zscore', KEYS[1], KEYS[2]) if not(score) then score=0 end score=math.floor(score) + tonumber(ARGV[1]) + tonumber(ARGV[2]) redis.call('zadd', KEYS[1], score, KEYS[2]) return 1
redis では time 関数を使用できないため、 (Integer.MAX - タイムスタンプ) * 10^-13
の部分は、スクリプトの外のプログラムによって計算されて渡されます。
ランキング リストのページングやチームのランキングのクエリなどの機能には、引き続き上記のコマンドを使用できます。
いわゆる同順位ランキングとは、順位状況が同じ順位のランキングです。
期待される結果は次のとおりです:
チーム ID | 貢献度 | ランキング |
---|---|---|
a | 100 | 1 |
99 | 2 | |
99 | 2 | |
88 | 4 | |
87 | 5 |