使用redis的setbit和bitcount来进行区间统计的坑
2017-03-11 17:45
585 查看
以前听过一个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 才可以。代码如下:
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
<?php
// 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也继续阅览网站,那么执行命令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/ 后对实现流程已经清楚了
正好项目中有需要统计一个用户最近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
<?php
// 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也继续阅览网站,那么执行命令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);
相关文章推荐
- 使用setbit、bitcount实现用户上线次数统计
- 使用Redis bitmaps进行快速、简单、实时统计
- redis的setbit,getbit,bitcount的使用详解
- redis的setbit这个bit怎么理解,配合bitcount使用?
- 使用Redis bitmaps进行快速、简单、实时统计
- 使用Redis bitmaps进行快速、简单、实时统计
- 使用Redis bitmaps进行快速、简单、实时统计
- redis中 SETBIT命令 和 BITCOUNT命令
- Scala中使用两种方式对单词进行次数统计(wordCount)
- mysql中BIT_COUNT的统计使用
- Redis中setbit和bitcount的注意细节
- mysql中使用bit_count来统计使用
- 使用redis的zset进行原子增及统计
- 使用C#和Excel进行报表开发(二)-操作统计图(Chart)
- 使用GROUP BY的时候如何统计记录条数 COUNT(*) DISTINCT
- 使用C#和Excel进行报表开发(二)-操作统计图(Chart)
- 使用C#和Excel进行报表开发(二)-操作统计图(Chart)
- CakePHP在Model中使用SUM/AVG等进行统计
- 使用存储过程对table,index进行统计, 优化查询计划
- 使用Awstats(for windows)进行网站分析统计