redisのsetbitとbitcountを使用して区間統計を行うピット

4708 ワード

以前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を置くといいです.コードは次のとおりです. 
 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);