読者です 読者をやめる 読者になる 読者になる

ダメプログラマの技術メモ

プログラミングの技術メモや駄文など

phpredisを使用してランキング集計を行う

PHP Redis

Redisでリアルタイムランキング集計するっていうのが周りで流行っているので自分なりにまとめてみました。

phpredisを使用して自分の前後のランキングと同率順位のランキングを集計してみます。

とりあえずphpredisのクラス・メソッドを使用してみる

  • Redisのランキング機能を利用するためにはソート済みセットというデータ型を利用します。
  • ソート済みセットを操作するコマンド(メソッド)は名前が z から始まります。
<?php

// Redis接続
$redis = new Redis();
$redis->connect("localhost", 6379);

// 指定したキーを削除する。
$redis->delete('ranking');

// メンバーを追加する。
// $redis->zAdd(key, score, member);
$redis->zAdd('ranking', 1,    'user1');
$redis->zAdd('ranking', 5,    'user5');
$redis->zAdd('ranking', 10,   'user10_1');
$redis->zAdd('ranking', 10,   'user10_2');
$redis->zAdd('ranking', 10,   'user10_3');
$redis->zAdd('ranking', 50,   'user50');
$redis->zAdd('ranking', 100,  'user100');
$redis->zAdd('ranking', 500,  'user500');
$redis->zAdd('ranking', 1000, 'user1000');

// メンバーを削除する。
// $redis->zRem(key, member);
$redis->zRem('ranking', 'user10_3');

// 昇順スコアランキングのランクを取得する。
// ランクは0から始まる。
// $redis->zRank(key, member)
$result = $redis->zRank('ranking', 'user10_2');
var_dump($result);
/*
int(3)
*/

// 降順スコアランキングのランクを取得する。
// ランクは0から始まる。
// $redis->zRevRank(key, member)
$result = $redis->zRevRank('ranking', 'user10_2');
var_dump($result);
/*
int(4)
*/

// 昇順スコアランキングの指定したランクのメンバーを取得する。
// startとendは0から始まるランクを指定する。
// WITHSCORESにtrueを指定した場合はスコアも取得する。
// $redis->zRange(key, start, end, [WITHSCORES])
$result = $redis->zRange('ranking', 0, 4);
var_dump($result);
/*
array(5) {
  [0]=>
  string(5) "user1"
  [1]=>
  string(5) "user5"
  [2]=>
  string(8) "user10_1"
  [3]=>
  string(8) "user10_2"
  [4]=>
  string(6) "user50"
}
*/

// 降順スコアランキングの指定したランクのメンバーを取得する。
// startとendは0から始まるランクを指定する。
// WITHSCORESにtrueを指定した場合はスコアも取得する。
// $redis->zRevRange(key, start, end, [WITHSCORES])
$result = $redis->zRevRange('ranking', 0, 4);
var_dump($result);
/*
array(5) {
  [0]=>
  string(8) "user1000"
  [1]=>
  string(7) "user500"
  [2]=>
  string(7) "user100"
  [3]=>
  string(6) "user50"
  [4]=>
  string(8) "user10_2"
}
*/

// 指定したスコア内にいるメンバーの数を取得する。
// $redis->zCount(key, min, max)
$result = $redis->zCount('ranking', 1, 10);
var_dump($result);
/*
int(4)
*/

自分の前後のランキング集計

<?php

// Redis接続
$redis = new Redis();
$redis->connect("localhost", 6379);

// 指定したキーを削除する。
$redis->delete('ranking');

// メンバーを追加する。
for($i = 1; $i <= 100; $i++) {
    $redis->zAdd('ranking', $i, 'user' . $i);
}

// メンバーuser30を中心とする。
$self = "user30";

// メンバーuser30のランクを取得する。
$selfRank = $redis->zRevRank('ranking', $self);

// メンバーuser30を中心として前後5人のメンバーとそのスコアを取得する。
$start = ($selfRank < 5) ? 0 : $selfRank - 5;
// $endはソート済みセット内の要素数を超えていてもエラーにならない。
$end = $selfRank + 5;
$result = $redis->zRevRange('ranking', $start, $end, true);

// ランキング結果を画面に表示する。
foreach ($result as $member => $score) {
    echo "member=" . $member . ", score=" . $score . "<br />";
}
/*
member=user35, score=35
member=user34, score=34
member=user33, score=33
member=user32, score=32
member=user31, score=31
member=user30, score=30
member=user29, score=29
member=user28, score=28
member=user27, score=27
member=user26, score=26
member=user25, score=25
*/

同率順位のランキング集計

  • zRangeやzRevRangeだとスコアが同じでもランクが異なる(0から始まる連番になる)ので、ロジックで同率順位づけする必要があります。
<?php

// Redis接続
$redis = new Redis();
$redis->connect("localhost", 6379);

// 指定したキーを削除する。
$redis->delete('ranking');

// メンバーを追加する。
$redis->zAdd('ranking', 1,    'user1');
$redis->zAdd('ranking', 5,    'user5');
$redis->zAdd('ranking', 10,   'user10_1');
$redis->zAdd('ranking', 10,   'user10_2');
$redis->zAdd('ranking', 10,   'user10_3');
$redis->zAdd('ranking', 50,   'user50_1');
$redis->zAdd('ranking', 50,   'user50_2');
$redis->zAdd('ranking', 100,  'user100');

// 全メンバーのランクを取得する。
// zRevRangeの第3引数(end)に-1を指定するとソート済みセットの末尾の要素まで取り出す。
$result = $redis->zRevRange('ranking', 0, -1, true);

// ランキング結果を画面に表示する。
foreach ($result as $member => $score) {
    // 自分よりスコアが大きいメンバーの数を数える。
    // zCountの第3引数(max)の+infは無限大のこと(-infで無限小になる)
    $rank = $redis->zCount("ranking", $score + 1, "+inf");
    // $rankは0から始まってしまうので1加算して表示する。
    echo "member=" . $member . ", score=" . $score . ", rank=" . ($rank + 1) . "<br />";
}
/*
member=user100, score=100, rank=1
member=user50_2, score=50, rank=2
member=user50_1, score=50, rank=2
member=user10_3, score=10, rank=4
member=user10_2, score=10, rank=4
member=user10_1, score=10, rank=4
member=user5, score=5, rank=7
member=user1, score=1, rank=8
*/