redisのsetbitとbitcountを使用して区間統計を行うピット
以前bitmapの統計共有を聞いたことがありますが、最近は「Redis bitmapsを使って高速、簡単、リアルタイム統計を行う」という記事や
http://www.infoq.com/cn/articles/the-secret-of-bitmap/その後、実装プロセスについて、ちょうどプロジェクトに1人のユーザーが最近7日、30日、90日の投稿量を統計する必要があることが明らかになった.この方法では、効率が最も高く、リソースを最小限に抑えることができると考えられる.私は実際の操作の中で確かに奇妙な問題を発見して、すべてBITCOUNTを取って正常で、1つの区間のを取って正常ではありませんredis>BITCOUNT bits(integer)0 redis>SETBIT bits 1 (integer)0 redis>SETBIT bits 2 1(integer)0 redis>BITCOUNT bits(integer)2 redis>BITCOUNT bits 2-1(integer)0なぜbitcountのstartを設定した後に値が取れないのですか?最後にhttp://www.cnphp6.com/archives/83725 「redisのsetbitはbit位置を修正し、bitcountはbyte位置をチェックし、両者の差は8の倍数」という答えを見つけました.ドキュメントを見ると確かにこの説明がありますが、あまりにも明らかではないのでsetbitの前にoffset*8を置くといいです.コードは次のとおりです.
Bitmapはいくつかの特定のタイプの計算に非常に有効である.
例えば、ユーザーAが何日オンラインになったか、ユーザーBが
ラインは何日か、など、データとして、どのユーザーがbetaテストなどのアクティビティに参加するかを決定します.このモードはSETBITとBITCOUNTを使用して実現できます.
たとえば,ユーザがある日オンラインになるたびにSETBITを用い,ユーザ名をkeyとし,その日に代表されるサイトのオンライン日をoffsetパラメータとし,このoffset上のを1に設定する.
例えば、今日がウェブサイトのオンライン化100日目であり、ユーザ(uid=10086)が今日ウェブサイトを閲覧した場合、SETBIT sign:10086 100 1を実行する.明日もユーザー(uid=10086)がウェブサイトを閲覧し続ける場合は、SETBIT sign:10086 101のコマンドを実行します.
ユーザ(uid=10086)の合計開始回数を計算する場合は、BITCOUNTコマンドを使用します.BITCOUNT sign:10086を実行すると、ユーザ(uid=10086)が開始する合計日数になります.
パフォーマンス
オンライン回数統計の例では、10年間実行しても、占有される空間は、ユーザ1人当たり10*365ビット(bit)、すなわちユーザ1人当たり456バイトにすぎない.このようなサイズのデータではBITCOUNTの処理速度はGETやINCRのようなO(1)複雑度の動作と同様に速い.例
[php] view plain
copy
// vim: set expandtab cindent tabstop=4 shiftwidth=4 fdm=marker:
/**
* @file ISign.php
* @version 1.0
* @author wade.zhan
* @date 2014-12-21 21:01:04
* @desc Redisベース bitmapで署名機能を実現
*/
/**
* ユーザーがいつかオンラインになるたびにSETBITを使用してユーザー名をkeyとし、
* その日に代表するウェブサイトのオンライン日をoffsetパラメータとし、このoffset上のを1に設定する.
* 例えば、今日がウェブサイトのオンライン化100日目で、ユーザー$uid=10001が今日ウェブサイトを閲覧したことがある場合、
* では、コマンドSETBITを実行する peter 100 1.
* 明日も$uid=10001でWebサイトを閲覧し続ける場合は、SETBITコマンドを実行します. peter 101 1 ,これを類推する
* $uid=10001の合計開始回数を計算する場合は、BITCOUNTコマンドを使用します.
* BITCOUNTの実行 $uid=10001 ,結果として$uid=10001オンラインの合計日数が得られる.
* 署名後、奨励判断が必要な場合はkey(uid:reward:day)を別途保存し、対応する奨励および受賞マークビットを格納することができる.
*/
class ISign {
const START_TIMESTRAMP = 1419091200; // 初日のチェックイン時間 20141221
private $redis = NULL;
public function __construct($config) {
$this->redis = new Redis();
$this->redis->connect($config['host'], $config['port'], $config['timeout'], NULL);
}
public function getSignKey($uid) {
return sprintf('sign:%d', $uid);
}
public function sign($uid, $now = NULL) {
if ($now === NULL) {
$now = time();
}
$offset = intval(($now - self::START_TIMESTRAMP) / 86400) + 1;
$signKey = $this->getSignKey($uid);
return $this->redis->setBit($signKey, $offset, 1);
}
public function getSign($uid, $now = NULL) {
if ($now === NULL) {
$now = time();
}
$offset = intval(($now - self::START_TIMESTRAMP) / 86400) + 1;
$signKey = $this->getSignKey($uid);
return $this->redis->getBit($signKey, $offset);
}
public function getSignCount($uid) {
$signKey = $this->getSignKey($uid);
return $this->redis->bitCount($signKey);
}
}
/* テストケース */
$config = array(
'host' => '127.0.0.1',
'port' => 6379,
'timeout' => 1,
);
$sign = new ISign($config);
$uid = 10086;
for ($i = 1; $i <= 15; $i ++) {
$now = ISign::START_TIMESTRAMP + $i * 86400;
$ret = $sign->sign($uid, $now);
echo 'sign:'.$ret.PHP_EOL;
$getValue = $sign->getSign($uid, $now);
echo 'getSign:'.$getValue.PHP_EOL;
}
$count = $sign->getSignCount($uid);
var_dump($count);
http://www.infoq.com/cn/articles/the-secret-of-bitmap/その後、実装プロセスについて、ちょうどプロジェクトに1人のユーザーが最近7日、30日、90日の投稿量を統計する必要があることが明らかになった.この方法では、効率が最も高く、リソースを最小限に抑えることができると考えられる.私は実際の操作の中で確かに奇妙な問題を発見して、すべてBITCOUNTを取って正常で、1つの区間のを取って正常ではありませんredis>BITCOUNT bits(integer)0 redis>SETBIT bits 1 (integer)0 redis>SETBIT bits 2 1(integer)0 redis>BITCOUNT bits(integer)2 redis>BITCOUNT bits 2-1(integer)0なぜbitcountのstartを設定した後に値が取れないのですか?最後にhttp://www.cnphp6.com/archives/83725 「redisのsetbitはbit位置を修正し、bitcountはbyte位置をチェックし、両者の差は8の倍数」という答えを見つけました.ドキュメントを見ると確かにこの説明がありますが、あまりにも明らかではないのでsetbitの前にoffset*8を置くといいです.コードは次のとおりです.
connect('127.0.0.1', 6379, 10);
// 8 bit
$start = 1;
$offset = $start * 8;
$redis->setBit('bit', $offset, 1);
$count = $redis->bitCount('bit', $start, -1);
var_dump($count);
Bitmapはいくつかの特定のタイプの計算に非常に有効である.
例えば、ユーザーAが何日オンラインになったか、ユーザーBが
ラインは何日か、など、データとして、どのユーザーがbetaテストなどのアクティビティに参加するかを決定します.このモードはSETBITとBITCOUNTを使用して実現できます.
たとえば,ユーザがある日オンラインになるたびにSETBITを用い,ユーザ名をkeyとし,その日に代表されるサイトのオンライン日をoffsetパラメータとし,このoffset上のを1に設定する.
例えば、今日がウェブサイトのオンライン化100日目であり、ユーザ(uid=10086)が今日ウェブサイトを閲覧した場合、SETBIT sign:10086 100 1を実行する.明日もユーザー(uid=10086)がウェブサイトを閲覧し続ける場合は、SETBIT sign:10086 101のコマンドを実行します.
ユーザ(uid=10086)の合計開始回数を計算する場合は、BITCOUNTコマンドを使用します.BITCOUNT sign:10086を実行すると、ユーザ(uid=10086)が開始する合計日数になります.
パフォーマンス
オンライン回数統計の例では、10年間実行しても、占有される空間は、ユーザ1人当たり10*365ビット(bit)、すなわちユーザ1人当たり456バイトにすぎない.このようなサイズのデータではBITCOUNTの処理速度はGETやINCRのようなO(1)複雑度の動作と同様に速い.例
[php] view plain
copy
// vim: set expandtab cindent tabstop=4 shiftwidth=4 fdm=marker:
/**
* @file ISign.php
* @version 1.0
* @author wade.zhan
* @date 2014-12-21 21:01:04
* @desc Redisベース bitmapで署名機能を実現
*/
/**
* ユーザーがいつかオンラインになるたびにSETBITを使用してユーザー名をkeyとし、
* その日に代表するウェブサイトのオンライン日をoffsetパラメータとし、このoffset上のを1に設定する.
* 例えば、今日がウェブサイトのオンライン化100日目で、ユーザー$uid=10001が今日ウェブサイトを閲覧したことがある場合、
* では、コマンドSETBITを実行する peter 100 1.
* 明日も$uid=10001でWebサイトを閲覧し続ける場合は、SETBITコマンドを実行します. peter 101 1 ,これを類推する
* $uid=10001の合計開始回数を計算する場合は、BITCOUNTコマンドを使用します.
* BITCOUNTの実行 $uid=10001 ,結果として$uid=10001オンラインの合計日数が得られる.
* 署名後、奨励判断が必要な場合はkey(uid:reward:day)を別途保存し、対応する奨励および受賞マークビットを格納することができる.
*/
class ISign {
const START_TIMESTRAMP = 1419091200; // 初日のチェックイン時間 20141221
private $redis = NULL;
public function __construct($config) {
$this->redis = new Redis();
$this->redis->connect($config['host'], $config['port'], $config['timeout'], NULL);
}
public function getSignKey($uid) {
return sprintf('sign:%d', $uid);
}
public function sign($uid, $now = NULL) {
if ($now === NULL) {
$now = time();
}
$offset = intval(($now - self::START_TIMESTRAMP) / 86400) + 1;
$signKey = $this->getSignKey($uid);
return $this->redis->setBit($signKey, $offset, 1);
}
public function getSign($uid, $now = NULL) {
if ($now === NULL) {
$now = time();
}
$offset = intval(($now - self::START_TIMESTRAMP) / 86400) + 1;
$signKey = $this->getSignKey($uid);
return $this->redis->getBit($signKey, $offset);
}
public function getSignCount($uid) {
$signKey = $this->getSignKey($uid);
return $this->redis->bitCount($signKey);
}
}
/* テストケース */
$config = array(
'host' => '127.0.0.1',
'port' => 6379,
'timeout' => 1,
);
$sign = new ISign($config);
$uid = 10086;
for ($i = 1; $i <= 15; $i ++) {
$now = ISign::START_TIMESTRAMP + $i * 86400;
$ret = $sign->sign($uid, $now);
echo 'sign:'.$ret.PHP_EOL;
$getValue = $sign->getSign($uid, $now);
echo 'getSign:'.$getValue.PHP_EOL;
}
$count = $sign->getSignCount($uid);
var_dump($count);