redis使用bitmap实现网站活跃用户的统计

一、位图bitmaps的使用场景

bitmaps类型其实并非一种数据类型,而是支持对string类型的value进行二进制置位运算。

使用场景:活跃用户统计

二、主要命令

1、setbit
  • (1)作用:设置某个键的某位的值

  • (2)用法:setbit key offset value

如:

  1. setbit logins 8 1
2、getbit
  • (1)作用:获取某个键的某位的值

  • (2)用法:getbit key offset

如:

  1. getbit logins 8
3、bitop
  • (1)作用:对多个键进行位运算

  • (2)用法:bitop operation destkey key key1 [key2]

  • (3)参数说明:

    • operation表示位算符,有AND,OR,NOT,XOR
    • destkey 表示最终保存结果的键
    • key key1 key2等表示用于运算的键

如:

  1. setbit login:1 2 1
  2. setbit login:2 3 1
  3. bitop AND login-2 login:1 login:2
4、bitcount
  • (1)作用:统计某个键的有多少位上的值是1

  • (2)用法:bitcount key [start end]

如:

  1. bitcount login:2
5、bitpos
  • (1)作用:获取某个键的位第一个是1或者0的位的位置

  • (2)用法:bitpos key bit [start end]

如:查看位的值是1的最开始的位数

  1. bitpos login:1 1

查看位的值是0的最开始的位数:

  1. 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

  1. $redis->setbit('login:20150601', 8, 1);
(2)统计某天的活跃用户数:
  1. $redis->bitcount('login:20150601');
(3)统计某几天的活跃用户(只有这几天某天有过登录就是活跃用户,所以使用OR操作即可)

如:计算7天活跃

  1. $weekStart = strtotime('-7 day');
  2. $weekEnd = time();
  3. for($i = $weekStart; $i<=$weekEnd ; $i += 86400) {
  4. if($redis->get('destKey7')) {
  5. $redis->bitop('OR', 'destKey7', 'destKey7', 'login:'.date('Ymd', $i));
  6. }
  7. else {
  8. $redis->set('destKey7', $redis->get('login:'.date('Ymd', $i)));
  9. }
  10. }
  11. $dataList[1] = $redis->bitcount('destKey7');
(4)计算连续登录的用户数

如:计算最近7天连续7天登录的用户数

  1. $weekStart = strtotime('-7 day');
  2. $weekEnd = time();
  3. for($i = $weekStart; $i<=$weekEnd ; $i += 86400) {
  4. if($redis->get('destKey7and')) {
  5. $redis->bitop('AND', 'destKey7and', 'destKey7and', 'login:'.date('Ymd', $i));
  6. }
  7. else {
  8. $redis->set('destKey7and', $redis->get('login:'.date('Ymd', $i)));
  9. }
  10. }
  11. $dataList[2] = $redis->bitcount('destKey7and');
3 完整代码:
(1)、文件结构:
  • index.php:用于展示活跃用户的

  • login.php 用于模拟登录的

  • data.php:生成用户登录记录的

界面:

登录页:输入用户的userId

活跃用户显示页:

(2)、源码详细
  • 登录页:
  1. <?php
  2. if (isset($_POST['submit'])) {
  3. $userId = (int) $_POST['userId'];
  4. $redis = new Redis();
  5. $redis->connect('127.0.0.1', 6379);
  6. $key = 'login:'.date('Ymd');
  7. if($userId){
  8. $result = $redis->setbit($key, $userId, 1);
  9. header('Location:index.php');
  10. }
  11. }
  12. ?>
  • 显示页:
  1. <?php
  2. $redis = new Redis();
  3. $redis->connect('127.0.0.1', 6379);
  4. $key = 'login:'.date('Ymd');
  5. $dataList = $details = array();
  6. //过去35天内每天的活跃用户数
  7. $startTime = strtotime('-35 day');
  8. $endTime = time();
  9. for($start = $startTime ; $start <= $endTime; $start += 86400) {
  10. $key = 'login:'.date('Ymd', $start);
  11. $details[] = array('date'=>date('Y-m-d', $start), 'num'=>$redis->bitcount($key));
  12. }
  13. //今天登录的用户数
  14. $dataList[0] = $redis->bitcount($key);
  15. //7天活跃用户数
  16. $weekStart = strtotime('-7 day');
  17. $weekEnd = time();
  18. for($i = $weekStart; $i<=$weekEnd ; $i += 86400) {
  19. if($redis->get('destKey7')) {
  20. $redis->bitop('OR', 'destKey7', 'destKey7', 'login:'.date('Ymd', $i));
  21. }
  22. else {
  23. $redis->set('destKey7', $redis->get('login:'.date('Ymd', $i)));
  24. }
  25. if($redis->get('destKey7and')) {
  26. $redis->bitop('AND', 'destKey7and', 'destKey7and', 'login:'.date('Ymd', $i));
  27. }
  28. else {
  29. $redis->set('destKey7and', $redis->get('login:'.date('Ymd', $i)));
  30. }
  31. }
  32. //14天活跃用户数
  33. $halfMonthStart = strtotime('-14 day');
  34. $halfMonthEnd = time();
  35. for($i = $halfMonthStart; $i<=$halfMonthEnd ; $i += 86400) {
  36. if($redis->get('destKey14')) {
  37. $redis->bitop('OR', 'destKey14', 'destKey14', 'login:'.date('Ymd', $i));
  38. }
  39. else {
  40. $redis->set('destKey14', $redis->get('login:'.date('Ymd', $i)));
  41. }
  42. if($redis->get('destKey14and')) {
  43. $redis->bitop('AND', 'destKey14and', 'destKey14and', 'login:'.date('Ymd', $i));
  44. }
  45. else {
  46. $redis->set('destKey14and', $redis->get('login:'.date('Ymd', $i)));
  47. }
  48. }
  49. //30天活跃用户数
  50. $monthStart = strtotime('-30 day');
  51. $monthEnd = time();
  52. for($i = $monthStart; $i<=$monthEnd ; $i += 86400) {
  53. if($redis->get('destKey30')) {
  54. $redis->bitop('OR', 'destKey30', 'destKey30', 'login:'.date('Ymd', $i));
  55. }
  56. else {
  57. $redis->set('destKey30', $redis->get('login:'.date('Ymd', $i)));
  58. }
  59. if($redis->get('destKey30and')) {
  60. $redis->bitop('AND', 'destKey30and', 'destKey30and', 'login:'.date('Ymd', $i));
  61. }
  62. else {
  63. $redis->set('destKey30and', $redis->get('login:'.date('Ymd', $i)));
  64. }
  65. }
  66. $dataList[1] = $redis->bitcount('destKey7');
  67. $dataList[2] = $redis->bitcount('destKey7and');
  68. $dataList[3] = $redis->bitcount('destKey14');
  69. $dataList[4] = $redis->bitcount('destKey14and');
  70. $dataList[5] = $redis->bitcount('destKey30');
  71. $dataList[6] = $redis->bitcount('destKey30and');
  72. ?>
  • 生成登录记录:
  1. <?php
  2. $redis = new Redis();
  3. $redis->connect('127.0.0.1', 6379);
  4. $startTime = strtotime('-35 day');
  5. $endTime = time();
  6. for($start = $startTime ; $start <= $endTime; $start += 86400) {
  7. $key = 'login:'.date('Ymd', $start);
  8. for($userId = 301; $userId < 30000; $userId++) {
  9. $value = array_rand(array(0, 1));
  10. $redis->setbit($key, $userId, $value);
  11. }
  12. }
  13. ?>