Redis BitMap 总结

BitMap是什么

Bitmap(即Bitset)
    Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),bitmap就是通过最小的单位bit来进行0或者1的设置,表示某个元素对应的值或者状态。

Redis中的BitMap

Redis从2.2.0版本开始新增了setbit,getbit,bitcount等几个bitmap相关命令。虽然是新命令,但是并没有新增新的数据类型,因为setbit等命令只不过是在set上的扩展。在bitmap上可执行AND,OR,XOR以及其它位操作。


相关命令

setBit

说明:给一个指定key的值得第offset位 赋值为value。

参数:[key ,offset ,value]: bool or int (1 or 0)

返回值:LONG: 0 or 1

getBit

说明:返回一个指定key的二进制信息

参数:[key, offset]

返回值:LONG

bitCount

说明:返回一个指定key中位的值为1的个数

参数:[key, start ,offset] (是以byte为单位不是bit)

返回值:LONG

bitOp

说明:对不同的二进制存储数据进行位运算(AND、OR、NOT、XOR)

参数:operation destkey key [key …]

返回值:LONG

 

bitmap的优势、限制

优势

1.基于最小的单位bit进行存储,所以非常省空间。 
2.设置时候时间复杂度O(1)、读取时候时间复杂度O(n),操作是非常快的。 
3.二进制数据的存储,进行相关计算的时候非常快。 
4.方便扩容

限制

redis中bit映射被限制在512MB之内,所以最大是2^32位。建议每个key的位数都控制下,因为读取时候时间复杂度O(n),越大的串读的时间花销越多。

bitmap空间、时间粗略计算方式

在一台2010MacBook Pro上,offset为2^32-1(分配512MB)需要~300ms,offset为2^30-1(分配128MB)需要~80ms,offset为2^28-1(分配32MB)需要~30ms,offset为2^26-1(分配8MB)需要8ms。<来自官方文档>

大概的空间占用计算公式是:($offset/8/1024/1024)MB

 

使用场景

一个简单的例子:日活跃用户

    为了统计今日登录的用户数,我们建立了一个bitmap,每一位标识一个用户ID。当某个用户访问我们的网页或执行了某个操作,就在bitmap中把标识此用户的位置为1。在Redis中获取此bitmap的key值是通过用户执行操作的类型和时间戳获得的。 

1011 1101

0010 0101

 

这个简单的例子中,每次用户登录时会执行一次redis.setbit(daily_active_users, user_id, 1)。将bitmap中对应位置的位置为1,时间复杂度是O(1)。统计bitmap结果显示有今天有9个用户登录。Bitmap的key是daily_active_users,它的值是1011110100100101。

 我以前一直以为是从后往前数15-8 | 7-0,跟字节一样,后来研究发现是 实际上顺序从前往后排 0 -7| 8 - 15 

127.0.0.1:6379> set daily_active_users "\x00\x00"
OK

127.0.0.1:6379> get daily_active_users
"\x00\x00"

127.0.0.1:6379> setbit daily_active_users 0 1
(integer) 0

127.0.0.1:6379> get daily_active_users
"\x80\x00"

127.0.0.1:6379> setbit daily_active_users 14  1
(integer) 0
127.0.0.1:6379> get daily_active_users
"\x80\x02"

用户签到

 

很多网站都提供了签到功能(这里不考虑数据落地事宜),并且需要展示最近一个月的签到情况,如果使用bitmap我们怎么做?一言不合亮代码! 

connect('127.0.0.1');


//用户uid
$uid = 1;

//记录有uid的key
$cacheKey = sprintf("sign_%d", $uid);

//开始有签到功能的日期
$startDate = '2017-01-01';

//今天的日期
$todayDate = '2017-01-21';

//计算offset
$startTime = strtotime($startDate);
$todayTime = strtotime($todayDate);
$offset = floor(($todayTime - $startTime) / 86400);

echo "今天是第{$offset}天" . PHP_EOL;

//签到
//一年一个用户会占用多少空间呢?大约365/8=45.625个字节,好小,有木有被惊呆?
$redis->setBit($cacheKey, $offset, 1);

//查询签到情况
$bitStatus = $redis->getBit($cacheKey, $offset);
echo 1 == $bitStatus ? '今天已经签到啦' : '还没有签到呢';
echo PHP_EOL;

//计算总签到次数
echo $redis->bitCount($cacheKey) . PHP_EOL;

/**
* 计算某段时间内的签到次数
* 很不幸啊,bitCount虽然提供了start和end参数,但是这个说的是字符串的位置,而不是对应"位"的位置
* 幸运的是我们可以通过get命令将value取出来,自己解析。并且这个value不会太大,上面计算过一年一个用户只需要45个字节
* 给我们的网站定一个小目标,运行30年,那么一共需要1.31KB(就问你屌不屌?)
*/
//这是个错误的计算方式
echo $redis->bitCount($cacheKey, 0, 20) . PHP_EOL;

活跃用户统计

$key1 = 'Userlogin2017-08-01';
$key2 = 'Userlogin2017-08-02';
$key3 = 'Userlogin2017-08-03';

##分别记录下 8月1号  和 8月2号 的活跃用户

$redis->setBit($key1, $uid, 1);
$redis->setBit($key2, $uid, 1);

##进行bitmap 计算统计1号2号都活跃的用户

$redis->bitOp('AND','8182',$key1,$key2);
$both_active = $redis->bitCount('8182');

##进行bitmap 计算统计1号 或 2号 或 3号 活跃的用户

$redis->bitOp('OR','818283',$key1,$key2,$key3);
$other_active = $redis->bitCount('818283');

假设当前站点有5000W用户,那么一天的数据大约为50000000/8/1024/1024=6MB

何时使用:

  如果活跃用户在百万级别,使用Redis BitMap很划算。

  如果活跃用户很少,而用户id都是10位以上的int。那就很浪费内存了,还不如使用set集合,然后求交集就可以了。

参考:

https://segmentfault.com/a/1190000008188655

https://blog.csdn.net/u011957758/article/details/74783347

https://blog.csdn.net/qq_28018283/article/details/76572342

 

 

你可能感兴趣的:(redis)