Bitmaps并不属于Redis中数据结构的一种,它其实是使用了字符串类型,是set、get等一系列字符串操作的一种扩展,与其不同的是,它提供的是位级别的操作,从这个角度看,我们也可以把它当成是一种位数组、位向量结构。当我们需要存取一些boolean类型的信息时,Bitmap是一个非常不错的选择,在节省内存的同时也拥有很好的存取速度(getbit/setbit操作时间复杂度为O(1))。
假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户A上线了多少天,用户B上
线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加beta测试等活动——这个模式可以使
用SETBIT和BITCOUNT来实现。
性能
以上线次数统计例子,即使运行10年,占用的空间也只是每个用户10*365比特位(bit),也即是每个
用户456字节。对于这种大小的数据来说,BITCOUNT的处理速度就像GET和INCR这种O(1)复杂度的
操作一样快。
1 常用命令
SETBIT key offset value 可用版本 >= 2.2.0
设置或者清空key的value(字符串)在offset处的bit值。当key不存在的时候,将新建字符串value。参数offset需要大于等于0,并且小于232(限制Bitmap大小为512MB)。没有setbit的位会默认设定为0
GETBIT key offset 可用版本 >= 2.2.0
返回key对应的string在offset处的bit值。当offset超出了字符串长度或key不存在时,返回0。
BITCOUNT key [start end] 可用版本 >= 2.6.0
时间复杂度: O(N)
统计字符串被设置为1的bit数。需要注意的是,这里的start和end并不是位偏移,而是以字节(8位)为单位来偏移的,比如BITCOUNT foo 0 1是统计key为foo的字符串中第一个到第二个字节中bit为1的总数。
BITPOS key bit [start] [end] 可用版本>= 2.8.7
时间复杂度: O(N),其中 N 为位图包含的二进制位数量
返回位图中第一个值为 bit 的二进制位的位置。
在默认情况下, 命令将检测整个位图, 但用户也可以通过可选的 start 参数和 end 参数指定要检测的范围。
BITOP operation destkey key [key ...]
起始版本:2.6.0 复杂度是O(N)
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
BITOP
命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数:
BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN
,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN
,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN
,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。BITOP NOT destkey srckey
,对给定 key 求逻辑非,并将结果保存到 destkey 。除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。
执行结果将始终保持到destkey里面。
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
可用版本: >=3.2.0
时间复杂度: O(1)用于指定的每个子命令
bitfield 有三个子指令,分别是 get/set/incrby,它们都可以对指定位片段进行读写,但是最多只能处理 64 个连续的位,如果超过 64 位,就得使用多个子指令,bitfield 可以一次执行多个子指令。
eg : 命令将位偏移量为100的8位有符号整数加1,并在位偏移量0处获取4位无符号整数的值:
> BITFIELD mykey INCRBY i5 100 1 GET u4 0
1) (integer) 1
2) (integer) 0
Bitmap常见的应用场景之一就是用户签到了,在这里,我们以日期作为key,以用户ID作为位偏移(也就是索引),存储用户的签到信息(1为签到,0为未签到)。不过这个方法也是有前置条件的,那就是 userid 是整数连续的,并且活跃占比较高,否则可能得不偿失。
用户签到 SETBIT 20190602 userId 1
用户是否签到 GETBIT 20190602 userId
某天用户的活跃数: BITCOUNT 20190602
用户某个时间段签到次数 通过遍历获取GETBIT 20190602 userId值
用户连续签到天数 get userId 设置过期日期为第二天
获取当天第一个签到到的用户 BITPOS 20190602 1
统计:
$redis->bitop('AND', 'threeAnd', 'login:20190311', 'login:20190312', 'login:20190313');
echo "连续三天都签到的用户数量:" . $redis->bitCount('threeAnd');
$redis->bitop('OR', 'threeOr', 'login:20190311', 'login:20190312', 'login:20190313');
echo "三天中签到用户数量(有一天签也算签了):" . $redis->bitCount('threeOr');
$redis->bitop('AND', 'monthActivities'', $redis->keys('login:201903*'));
echo "连续一个月签到用户数量:" . $redis->bitCount('monthActivities');
3使用Bitmaps 用户在线状态
考虑到每月初需要重置连续签到次数,最简单的方式是按用户每月存一条签到数据(也可以每年存一条数据)。Key的格式为u:sign:uid:yyyyMM
,Value则采用长度为4个字节(32位)的位图(最大月份只有31天)。位图的每一位代表一天的签到,1表示已签,0表示未签。
#用户2月17号签到
SETBIT u:sign:1000:201902 16 1 偏移量是从0开始,所以要把17减1
#检查2月17号是否签到
GETBIT u:sign:1000:201902 16 偏移量是从0开始,所以要把17减1
#统计2月份的累计签到次数
BITCOUNT u:sign:1000:201902
#统计2月份的连续签到次数(暂定),并设置有效期为第二天
set u:signcount:1000:201902
#获取2月份前28天的签到数据
BITFIELD u:sign:1000:201902 get u28 0
#获取2月份首次签到的日期
BITPOS u:sign:1000:201902 1 返回的首次签到的偏移量,加上1即为当月的某一天
参考: https://blog.csdn.net/CrazyLai1996/article/details/85220910
https://www.cnblogs.com/liujiduo/p/10396020.html
https://blog.csdn.net/HUXU981598436/article/details/88191152
https://learnku.com/articles/25181