什么是位图
位图(Bitmap)是通过一个 bit
来表示某个元素对应的值或者状态。它并不是什么新的数据结构。它的内容其实就是普通的字符串。我们可以通过 get/set
获取位图的内容,也可以使用 getbit/setbit
操作 bit
值(0 或者 1)。
Bit
即比特,是目前计算机中数据最小的单位。8个Bit一个Byte(字节)。Bit的值,要么为 0 ,要么为 1。由于Bit是计算机中最小的单位,使用它进行储存将非常节省空间。特别适合一些数据量大的场景。例如,统计每日活跃用户、统计每月打卡数等统计场景。
常用命令介绍
1)SETBIT
作用:对于某个KEY的某位设值
用法:SETBIT key offset value
返回值: 原来储存的位
redis> SETBIT bit 10086 1
(integer) 0
2)GETBIT
作用:获取某KEY某位的值
用法:GETBIT key offset
返回值:0 或 1。 当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。
redis> SETBIT bit 10086 1
(integer) 0
redis> GETBIT bit 10086
(integer) 1
3)BITOP
作用:对多个键进行位操作。 OP
是 operation
的简写。
用法:BITOP operation destkey key1 key2 [key ...]
参数说明:
operation 表示位运算符。一共有四种操作,见下表。
destkey 表示运算结果保存的值
key1、key2、key3 表示进行运算的key
operation | 描述 |
---|---|
AND | 逻辑并 |
OR | 逻辑或 |
NOT | 逻辑非 |
XOR | 逻辑异或 |
返回值:保存到 destkey 的字符串的长度,和输入 key 中最长的字符串长度相等。
redis> SETBIT bits-1 0 1 # bits-1 = 1001
(integer) 0
redis> SETBIT bits-1 3 1
(integer) 0
redis> SETBIT bits-2 0 1 # bits-2 = 1011
(integer) 0
redis> SETBIT bits-2 1 1
(integer) 0
redis> SETBIT bits-2 3 1
(integer) 0
redis> BITOP AND and-result bits-1 bits-2
(integer) 1
redis> GETBIT and-result 0 # and-result = 1001
(integer) 1
redis> GETBIT and-result 1
(integer) 0
redis> GETBIT and-result 2
(integer) 0
redis> GETBIT and-result 3
(integer) 1
4)BITCOUNT
作用:计算给定字符串上,位为1的个数
用法:BITCOUNT key [start] [end] 注意:此处的[start] [end] 为 字节开始和结束的位置,非偏移量的位置
返回值:被设置为 1 的位的数量。不存在的key,或空字符串,值为0
redis> SETBIT tian 0 1
(integer) 0
redis> BITCOUNT tian
(integer) 1
redis> SETBIT tian 2 1
(integer) 0
redis> BITCOUNT tian
(integer) 2
5)BITPOS
用法:获取某个键第一位被设置为 0 或 1 位的位置
作用:BITPOS key bit [start] [end]
返回值:返回第一个被设为 0 或 1 的位置
redis> SET test_str 'youthcity'
OK
# 查看值为 1 的最开始的位数
redis> BITPOS test_str 1
(integer) 1
# 查看值为 0 的最开始位数
redis> BITPOS test_str 0
(integer) 0
redis> BITPOS test_1 1 # 若没有找到指定位,则返回 -1
(integer) -1
6)魔术指令 BITFIELD
作用:一次对多个位范围进行操作。bitfield 有三个子指令,分别是 get/set/incrby。每个指令都可以对指定片段做操作。
用法:BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
返回值:返回一个数组作为回复, 数组中的每个元素就是对应操作的执行结果。
# 从第1位开始取4位,设值为5(有符号数)
redis> BITFIELD key SET i4 0 5
1) (integer) 0
# 从第1位开始取4位,结果为有符号数
redis> BITFIELD key GET i4 0
1) (integer) 5
# 从第1位取4位,结果为有符号数
# 从第5位取4位,设值为6,结果为无符号数
# 从第5位去4位,值增加1,结果为无符号数
redis> BITFIELD key GET i4 0 SET u4 4 6 INCRBY u4 4 1
1) (integer) 5
2) (integer) 0
3) (integer) 7
BITFIELD还提供了三种溢出策略:
-
WRAP
(wrap around,回绕)。一个i8的整数,值为127,递增1会导致值变为-128; -
SAT
(saturation arithmetic,饱和计算)。一个i8的整数,值为120,递增10结果变为127(i8 类型所能储存的最大整数值); -
FAIL
。 发生溢出时,操作失败。并返回空值表示计算未被执行。
redis> BITFIELD tian_key SET i8 0 127 OVERFLOW WRAP INCRBY i8 0 1
1) (integer) 0
2) (integer) -128
redis> BITFIELD tian_key_2 SET i8 0 120 OVERFLOW SAT INCRBY i8 0 10
1) (integer) 0
2) (integer) 127
redis> BITFIELD tian_key_3 SET i8 0 127 OVERFLOW FAIL INCRBY i8 0 1
1) (integer) 0
2) (nil)
应用场景
1) 统计用户上线次数
实现原理:
每当用户在某一天上线的时候,我们就使用 SETBIT
,以用户名作为 key
,将那天所代表的网站的上线日作为 offset
参数,并将这个 offset
上的位设置为 1
。
例如:
- 某应用上线第100天,若用户A在该天上线一次。
SETBIT A 100 1
- 某应用上线第101天,用户A上线。
SETBIT A 101 1
- 统计用户 A 总共上线次数。
BITCOUNT A
2)用户签到
与统计用户上线次数原理类似。
原理:以用户ID为KEY,以当前时间距离开始时间的时间差为偏移量,若用户签到一次,则将位置为 1
。最后 bitcount
KEY,获取用户一共签到的次数。
const start_date = '20180801';
const end_date = '20180830';
const offset = moment(start_date).unix() - moment(end_date).unix();
redis.setBit('user_id_2018', offset, 1);
// 统计活跃天数
redis.bitCount('user_id_2018');
3)统计活跃用户
需求:统计某天或连续几天,活跃用户数
方案:若某用户上线,则以日期为KEY,以用户user_id为偏移量(若ID不为整数,则将ID hash化为唯一ID),设置位为 1
redis.setBit('')
const status = 1;
const user_id = 100;
redis.setBit('active_20180820', user_id, status);
redis.setBit('active_20180821', user_id, status);
// 将20180820号与20180821日进行和运算,得出两天都上线的结果。并存入KEY—— dest_201808_20_21
redis.bitOp('AND', 'dest_201808_20_21', 'active_20180820', 'active_20180821');
redis.bitCount('dest_201808_20_21');
4)用户在线状态
需求:提供接口检查用户是否在线。
方案:使用bitmap
存储用户在线状态。使用一个KEY,若用户在线,则以用户ID位偏移量,将位设为 1
;若不在线,则设置为 0
。