背景
假设有这么一个需求:需要记录、并且统计公司员工一年的签到状态。如果使用MySQL来存储,则格式大致为
字段名 | 字段类型 |
---|---|
id | Bigint |
uid | string |
date | string |
is_check | tinyint |
如果公司有10万员工,则一年的存储量为3650万条数据,显然存储占用是非常大的。而且随着表数据量变大,查询也会变慢。那有没有其他更优的存储方式呢?
是有的。Redis中的bitmap内存开销小、效率高且操作简单,很适合用于签到这类场景。下面就一起来学习下bitmap这种数据结构。
什么是bitmap
bitmap,中文名称也叫「位图」。其并不是 Redis 的基本数据类型(比如 String、List) 而是基于 String 数据类型的按位操作,高阶数据类型的一种。由于string最大支持512MB。对应到bitmap刚好为2^32位。
Bitmap 实现原理
Bitmap的基本原理就是使用bit位的0或者1 来表示元素的二义性(比如是否签到,是否有效,是否是黑名单用户)
比如对于8个bit位,我们可以通过将idx = 2 和 idx = 5 置为1来表示是否签到,也就是说:1个Byte就可以表示8个用户的签到状态。
Bitmap语法
SETBIT key offset value
命令描述
-
针对key存储的字符串值,设置或清除指定偏移量offset上的位(bit)
-
当key不存在时,会创建一个新的字符串
时间复杂度
- O(1)
example
// uid =1023在2023-10-18-01进行签到
setbit 2023:1018:01 1023 1
GETBIT key offset
命令描述
- 返回key对应的字符串中
offset
位置的位(bit)
时间复杂度
- O(N)
使用
// 得到1
getbit 2023:1018:01 1023
BITCOUNT key [start end]
命令描述
-
统计给定字符串中,比特值为
1
的数量 -
默认会统计整个字符串,同时也可以通过指定
start
和end
来限定范围 -
start
和end
也可以是负数,-1
表示最后一个字节
,-2表示倒数第二个字节。注意这里是字节
,1字节=8比特 -
如果key不存在,返回
0
-
**返回值:**bit值为1的数量
时间复杂度
- O(N)
使用
// 得到bit=1的个数
bitcount 2023:1018:01
BITPOS key bit [start [end]]
命令描述
-
返回字符串中,从左到右,第一个比特值为
bit
(0或1)的偏移量 -
默认情况下会检查整个字符串,但是也可以通过指定
start
和end
变量来指定字节
范围,与BITCOUNT中的范围描述一致 -
**返回值: **第一个比特值为指定bit(0或1)的偏移量
时间复杂度
- O(N)
Example
// 输出1023
bitpos 2023:1018:01 1
BITOP operation destkey key [key ...]
命令描述
-
对多个字符串进行位操作,并将结果保存到
destkey
中 -
operation 可以是 AND、OR、XOR 或者 NOT
-
BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN
,对多个key求逻辑与,并将结果保存到destkey
中 -
除了
NOT
操作之外,其他操作都可以接受一个或多个key
作为输入。
不同长度的字符串
当给定的参数中,字符串长度不同时,较短的那个字符串与最长字符串之间缺少的部分会被看作 0
。
空的 key
也被看作是包含 0
的字符串序列。
返回值:将各个key的bit位进行位操作后得到的结果
bitmap应用场景
用户签到
需求: 展示用户的签到状态。
签到:
-
setbit 日期 uid 1
-
setbit uid 日期 1
查询用户某日的签到状态
- getbit uid 日期 1
查询某日登陆的用户总数
- bitcount 日期
统计活跃用户(用户登陆情况)
需求:如果用户在近一周内每天都登陆,则认为是活跃用户。
登陆: setbit 日期 uid 1
查询近一周连续登陆的用户:bitop and dest 20240201 20240202 .. 20240207
dest 中值为1的offset,就是连续7天活跃用户的ID,bitcount dest即可统计近一周活跃过的用户总和。
统计用户是否在线
登陆: setbit login uid 1
下线: setbit login uid 0
查询是否在线: getbit login uid