位图 bitmaps 的使用场景
bitmaps 类型其实并非一种数据类型,而是支持对 string 类型的 value 进行二进制置位运算。
使用场景:活跃用户统计
主要命令
setbit
-
作用:设置某个键的某位的值
-
用法:setbit key offset value
setbit logins 8 1
getbit
-
作用:获取某个键的某位的值
-
用法:getbit key offset
getbit logins 8
bitop
-
作用:对多个键进行位运算
-
用法:bitop operation destkey key key1 [key2]
参数说明:
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
bitcount
-
作用:统计某个键的有多少位上的值是1
-
用法:bitcount key [start end]
bitcount login:2
bitpos
-
作用:获取某个键的位第一个是1或者0的位的位置
-
用法:bitpos key bit [start end]
查看位的值是1的最开始的位数
bitpos login:1 1
查看位的值是0的最开始的位数:
bitpos login:1 0
使用bitmaps实现活跃用户统计
很多网站的活跃用户是指登录用户或者付费用户,这里以登录用户为例。
-
原理:假设我们把每天的登录情况存到一个以日期为键的元素中,它的每位代表一个用户的登录状态,1表示登录过,0表示未登录过,每一位对应一个userId,比如第一位就代表userId为1的用户(如果用户的userId不是以0开始的,我们可以把这个差值当成一个常量,如用户ID是以3000000开始的,那第一位就是userId为3000000的用户),统计某几天的活跃率,直接对这些天的登录情况进行位运算即可。
-
实现:(通过PHP+Redis来实现) 前提:要安装php的redis扩展
- 每次登录的时候设置一个这个用户的登录情况,写入到redis 如userId为8的用户(可能这个userId为3000008,用户ID是以3000000开始的话),在2015年6月1日登录。
我们把键定义为login:20150601
$redis->setbit('login:20150601', 8, 1);
-
统计某天的活跃用户数:
$redis->bitcount('login:20150601');
-
统计某几天的活跃用户(只有这几天某天有过登录就是活跃用户,所以使用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');
-
计算连续登录的用户数
如:计算最近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');
-
完整代码:
-文件结构: index.php:用于展示活跃用户的
login.php 用于模拟登录的
data.php:生成用户登录记录的
界面:
登录页:输入用户的userId
-
活跃用户显示页:
-
源码详细
登录页
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');
}
}
显示页
$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');
- 生成登录记录:
$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);
}
}