一、位图bitmaps的使用场景
bitmaps类型其实并非一种数据类型,而是支持对string类型的value进行二进制置位运算。
使用场景:活跃用户统计
二、主要命令
1、setbit
(1)作用:设置某个键的某位的值
(2)用法:setbit key offset value
如:
setbit logins 8 1
2、getbit
(1)作用:获取某个键的某位的值
(2)用法:getbit key offset
如:
getbit logins 8
3、bitop
(1)作用:对多个键进行位运算
(2)用法:bitop operation destkey key key1 [key2]
(3)参数说明:
- operation表示位算符,有AND,OR,NOT,XOR
- destkey 表示最终保存结果的键
- key key1 key2等表示用于运算的键
如:
setbit login:1 2 1
setbit login:2 3 1
bitop AND login-2 login:1 login:2
4、bitcount
(1)作用:统计某个键的有多少位上的值是1
(2)用法:bitcount key [start end]
如:
bitcount login:2
5、bitpos
(1)作用:获取某个键的位第一个是1或者0的位的位置
(2)用法:bitpos key bit [start end]
如:查看位的值是1的最开始的位数
bitpos login:1 1
查看位的值是0的最开始的位数:
bitpos login:1 0
三、使用bitmaps实现活跃用户统计
很多网站的活跃用户是指登录用户或者付费用户,这里以登录用户为例。
1、原理:假设我们把每天的登录情况存到一个以日期为键的元素中,它的每位代表一个用户的登录状态,1表示登录过,0表示未登录过,每一位对应一个userId,比如第一位就代表userId为1的用户(如果用户的userId不是以0开始的,我们可以把这个差值当成一个常量,如用户ID是以3000000开始的,那第一位就是userId为3000000的用户),统计某几天的活跃率,直接对这些天的登录情况进行位运算即可。
2、实现:(通过PHP+Redis来实现)
前提:要安装php的redis扩展
(1)每次登录的时候设置一个这个用户的登录情况,写入到redis
如userId为8的用户(可能这个userId为3000008,用户ID是以3000000开始的话),在2015年6月1日登录。
我们把键定义为login:20150601
$redis->setbit('login:20150601', 8, 1);
(2)统计某天的活跃用户数:
$redis->bitcount('login:20150601');
(3)统计某几天的活跃用户(只有这几天某天有过登录就是活跃用户,所以使用OR操作即可)
如:计算7天活跃
$weekStart = strtotime('-7 day');
$weekEnd = time();
for($i = $weekStart; $i<=$weekEnd ; $i += 86400) {
if($redis->get('destKey7')) {
$redis->bitop('OR', 'destKey7', 'destKey7', 'login:'.date('Ymd', $i));
}
else {
$redis->set('destKey7', $redis->get('login:'.date('Ymd', $i)));
}
}
$dataList[1] = $redis->bitcount('destKey7');
(4)计算连续登录的用户数
如:计算最近7天连续7天登录的用户数
$weekStart = strtotime('-7 day');
$weekEnd = time();
for($i = $weekStart; $i<=$weekEnd ; $i += 86400) {
if($redis->get('destKey7and')) {
$redis->bitop('AND', 'destKey7and', 'destKey7and', 'login:'.date('Ymd', $i));
}
else {
$redis->set('destKey7and', $redis->get('login:'.date('Ymd', $i)));
}
}
$dataList[2] = $redis->bitcount('destKey7and');
3 完整代码:
(1)、文件结构:
index.php:用于展示活跃用户的
login.php 用于模拟登录的
data.php:生成用户登录记录的
界面:
登录页:输入用户的userId
活跃用户显示页:
(2)、源码详细
- 登录页:
<?php
if (isset($_POST['submit'])) {
$userId = (int) $_POST['userId'];
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'login:'.date('Ymd');
if($userId){
$result = $redis->setbit($key, $userId, 1);
header('Location:index.php');
}
}
?>
- 显示页:
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = 'login:'.date('Ymd');
$dataList = $details = array();
//过去35天内每天的活跃用户数
$startTime = strtotime('-35 day');
$endTime = time();
for($start = $startTime ; $start <= $endTime; $start += 86400) {
$key = 'login:'.date('Ymd', $start);
$details[] = array('date'=>date('Y-m-d', $start), 'num'=>$redis->bitcount($key));
}
//今天登录的用户数
$dataList[0] = $redis->bitcount($key);
//7天活跃用户数
$weekStart = strtotime('-7 day');
$weekEnd = time();
for($i = $weekStart; $i<=$weekEnd ; $i += 86400) {
if($redis->get('destKey7')) {
$redis->bitop('OR', 'destKey7', 'destKey7', 'login:'.date('Ymd', $i));
}
else {
$redis->set('destKey7', $redis->get('login:'.date('Ymd', $i)));
}
if($redis->get('destKey7and')) {
$redis->bitop('AND', 'destKey7and', 'destKey7and', 'login:'.date('Ymd', $i));
}
else {
$redis->set('destKey7and', $redis->get('login:'.date('Ymd', $i)));
}
}
//14天活跃用户数
$halfMonthStart = strtotime('-14 day');
$halfMonthEnd = time();
for($i = $halfMonthStart; $i<=$halfMonthEnd ; $i += 86400) {
if($redis->get('destKey14')) {
$redis->bitop('OR', 'destKey14', 'destKey14', 'login:'.date('Ymd', $i));
}
else {
$redis->set('destKey14', $redis->get('login:'.date('Ymd', $i)));
}
if($redis->get('destKey14and')) {
$redis->bitop('AND', 'destKey14and', 'destKey14and', 'login:'.date('Ymd', $i));
}
else {
$redis->set('destKey14and', $redis->get('login:'.date('Ymd', $i)));
}
}
//30天活跃用户数
$monthStart = strtotime('-30 day');
$monthEnd = time();
for($i = $monthStart; $i<=$monthEnd ; $i += 86400) {
if($redis->get('destKey30')) {
$redis->bitop('OR', 'destKey30', 'destKey30', 'login:'.date('Ymd', $i));
}
else {
$redis->set('destKey30', $redis->get('login:'.date('Ymd', $i)));
}
if($redis->get('destKey30and')) {
$redis->bitop('AND', 'destKey30and', 'destKey30and', 'login:'.date('Ymd', $i));
}
else {
$redis->set('destKey30and', $redis->get('login:'.date('Ymd', $i)));
}
}
$dataList[1] = $redis->bitcount('destKey7');
$dataList[2] = $redis->bitcount('destKey7and');
$dataList[3] = $redis->bitcount('destKey14');
$dataList[4] = $redis->bitcount('destKey14and');
$dataList[5] = $redis->bitcount('destKey30');
$dataList[6] = $redis->bitcount('destKey30and');
?>
- 生成登录记录:
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$startTime = strtotime('-35 day');
$endTime = time();
for($start = $startTime ; $start <= $endTime; $start += 86400) {
$key = 'login:'.date('Ymd', $start);
for($userId = 301; $userId < 30000; $userId++) {
$value = array_rand(array(0, 1));
$redis->setbit($key, $userId, $value);
}
}
?>