使用redis的setbit和bitcount来进行区间统计的坑

以前听过一个bitmap做统计的分享,最近又看了文章“使用Redis bitmaps进行快速、简单、实时统计” 和

http://www.infoq.com/cn/articles/the-secret-of-bitmap/ 后对实现流程已经清楚了
正好项目中有需要统计一个用户最近7天,30天,90天的发帖量,想来用这种方法是效率最高占用资源最少的。

我在实际操作中确发现奇怪的问题,取全部BITCOUNT正常,取一个区间的会不正常
redis> BITCOUNT bits
(integer) 0
redis> SETBIT bits 1 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 才可以。代码如下:
 

 <?php
        $redis = new Redis();
        $redis->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 1,以此类推。

当要计算用户(uid=10086)总共以来的上线次数时,就使用BITCOUNT命令:执行BITCOUNT sign:10086,得出的结果就是用户(uid=10086)上线的总天数。

性能

以上线次数统计例子,即使运行10年,占用的空间也只是每个用户10*365比特位(bit),也即是每个
用户456字节。对于这种大小的数据来说,BITCOUNT的处理速度就像GET和INCR这种O(1)复杂度的
操作一样快。例子

[php]  view plain  copy
  1. <?php  
  2. // vim: set expandtab cindent tabstop=4 shiftwidth=4 fdm=marker:  
  3.    
  4. /** 
  5.  * @file     ISign.php 
  6.  * @version  1.0 
  7.  * @author   wade.zhan 
  8.  * @date     2014-12-21 21:01:04 
  9.  * @desc     基于Redis bitmap实现签到功能 
  10.  */  
  11. /** 
  12.  * 每当用户在某一天上线的时候,我们就使用SETBIT,以用户名作为key, 
  13.  * 将那天所代表的网站的上线日作为offset参数,并将这个offset上的为设置为1. 
  14.  * 比如,如果今天是网站上线的第100天,而用户$uid=10001在今天阅览过网站, 
  15.  * 那么执行命令SETBIT peter 100 1. 
  16.  * 如果明天$uid=10001也继续阅览网站,那么执行命令SETBIT peter 101 1 ,以此类推.  
  17.  * 当要计算$uid=10001总共以来的上线次数时,就使用BITCOUNT命令:  
  18.  * 执行BITCOUNT $uid=10001 ,得出的结果就是$uid=10001上线的总天数.  
  19.  * 签到后如果需要奖励判断可以另存key(uid:reward:day),里面可以存储对应的奖励及领奖标记位.  
  20.  */  
  21. class ISign {  
  22.     const START_TIMESTRAMP = 1419091200; // 首日签到时间 20141221  
  23.     private $redis = NULL;  
  24.     public function __construct($config) {  
  25.         $this->redis = new Redis();  
  26.         $this->redis->connect($config['host'], $config['port'], $config['timeout'], NULL);  
  27.     }  
  28.     public function getSignKey($uid) {  
  29.         return sprintf('sign:%d'$uid);  
  30.     }  
  31.     public function sign($uid$now = NULL) {  
  32.         if ($now === NULL) {  
  33.             $now = time();  
  34.         }  
  35.         $offset = intval(($now - self::START_TIMESTRAMP) / 86400) + 1;  
  36.         $signKey = $this->getSignKey($uid);  
  37.         return $this->redis->setBit($signKey$offset, 1);  
  38.     }  
  39.     public function getSign($uid$now = NULL) {  
  40.         if ($now === NULL) {  
  41.             $now = time();  
  42.         }  
  43.         $offset = intval(($now - self::START_TIMESTRAMP) / 86400) + 1;  
  44.         $signKey = $this->getSignKey($uid);  
  45.         return $this->redis->getBit($signKey$offset);  
  46.     }  
  47.     public function getSignCount($uid) {  
  48.         $signKey = $this->getSignKey($uid);  
  49.         return $this->redis->bitCount($signKey);  
  50.     }  
  51. }  
  52. /* 测试用例 */  
  53. $config = array(  
  54.     'host' => '127.0.0.1',  
  55.     'port' => 6379,  
  56.     'timeout' => 1,  
  57. );  
  58. $sign = new ISign($config);  
  59. $uid = 10086;  
  60. for ($i = 1; $i <= 15; $i ++) {  
  61.     $now = ISign::START_TIMESTRAMP + $i * 86400;  
  62.     $ret = $sign->sign($uid$now);  
  63.     echo 'sign:'.$ret.PHP_EOL;  
  64.     $getValue = $sign->getSign($uid$now);  
  65.     echo 'getSign:'.$getValue.PHP_EOL;  
  66. }  
  67. $count = $sign->getSignCount($uid);  
  68. var_dump($count);  































  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值