有道无术,术尚可求,有术无道,止于术。
本系列Redis 版本 7.2.5
源码地址:https://gitee.com/pearl-organization/study-redis-demo
文章目录
- 1. 概述
- 2. 基本命令
- 2.1 SETBIT
- 2.2 GETBIT
- 2.3 BITCOUNT
- 2.4 BITPOS
- 2.5 BITFIELD
- 2.6 BITFIELD_RO
- 2.7 BITOP
- 3. 应用场景
- 3.1 用户登录状态
- 3.2 签到打卡
1. 概述
Redis Bitmap
实际不是一个独立的数据类型,而是基于 String
类型实现的。它主要用于存储二值状态(即集合元素的取值只有 0
和 1
两种)。由于每个位只能表示两种状态,在需要快速进行大量数据的排序、查找、去重等操作时具有显著优势。
Bitmap
实际上是利用 String
类型的最大容量(512
MB)存储一个连续的二进制序列。每个字节的 8
位可以分别代表 8
个独立的状态,因此可以用一个 Bitmap
来跟踪多达数百万甚至数十亿的状态。
2. 基本命令
所有命令:
命名 | 描述 |
---|---|
BITCOUNT | 统计给定范围内为1 的位的数量 |
BITFIELD | 对字符串类型的 key 进行基于位的操作 |
BITFIELD_RO | 使用 BITFIELD 命令进行只读操作 |
BITOP | 执行针对多个 Bitmap 的并集、交集、差集等位操作 |
BITPOS | 查找指定位值的第一个位置 |
GETBIT | 获取指定偏移量处的位状态 |
SETBIT | 设置指定偏移量处的位状态 |
2.1 SETBIT
SETBIT
命令用于对 key
所储存的字符串值,设置或清除指定偏移量上的位(bit
)。时间复杂度为 O(1)
,因为直接在内存中操作字符串的位表示。返回值为存储在指定偏移量处的原始位值(0
或1
)。
基本语法:
SETBIT key offset value
参数说明:
key
:操作的key
。offset
:指定偏移量,从0
开始计数。注意,偏移量必须大于或等于0
,且小于2^32
value
:设置的值,只能是0
或1
。
注意事项:
- 如果
key
不存在,会自动为其创建一个新的字符串。 - 如果设置的偏移量超过了字符串的当前长度,会扩展字符串以确保可以在指定的偏移量处设置值。扩展部分会使用
0
填充。 - 警告操作:当
key
不存在,或者是比较小的字符串时,直接设置2^32-1
位置时,会立即分配所有内存,这有可能会导致服务阻塞
示例:
# 想设置其偏移量为7的位为1
SETBIT mykey 7 1
2.2 GETBIT
GETBIT
命令是用于获取存储在 key
中的字符串值在指定偏移量上的位(bit
)值的操作。返回值是一个整数,表示指定偏移量上的位的值。返回值只可能是 0
或 1
。
基本语法:
GETBIT key offset
示例:
# 获取偏移量为7的位的值
GETBIT mykey 7
2.3 BITCOUNT
BITCOUNT
命令用于统计指定 key
所储存的字符串值中,被设置为1
的二进制位的数量。
基本语法:
BITCOUNT key [start] [end]
参数说明:
key
:要统计的key
,对应的值应该是一个字符串。start
(可选):统计二进制位的开始位置,参数类型为整数,默认从0
开始统计。end
(可选):统计二进制位的结束位置,参数类型为整数,默认统计到整个字符串的末尾。
注意事项:
- 如果指定的
key
不存在,会将其视为空字符串,因此返回值为0
。 - 时间复杂度为
O(N)
,其中N
是字符串的长度(以字节为单位)。在处理大数据量时,请注意性能问题。
无参示例:
redis> SET mykey "foobar"
OK
redis> BITCOUNT mykey
(integer) 26
带参示例:
redis> SET mykey "\xff\xf0\x00"
OK
redis> BITCOUNT mykey 0 7
(integer) 12
redis> BITCOUNT mykey 0 0 4
(integer) 4
redis> BITCOUNT mykey 1 1 4
(integer) 6
2.4 BITPOS
BITPOS
命令用于查找字符串中第一个设置为指定值(0
或1
)的 bit
位,并返回该位置。如果没有找到匹配的 bit
,则返回-1
。
基本语法:
BITPOS key bit [start] [end]
参数说明:
key
:要操作的key
,其值应为一个字符串。bit
:要查找的bit
值,只能是0
或1
。start
(可选):开始查找的起始位置,默认为0
。end
(可选):结束查找的位置,默认为 -1
,表示字符串的最后一个bit
。
注意事项:
- 如果指定的
key
不存在,会将其视为一个空字符串,并查找空字符串中的bit
。 - 如果在指定的范围内没有找到匹配的
bit
,则返回 -1
。 - 查找范围是基于
bit
的,而不是基于字节的。例如,start=0
和end=7
表示查找前8
个bit
,而不是第一个字节。 - 命令的时间复杂度为
O(N)
,其中N
是字符串的长度(以bit
为单位)。在处理大数据量时,请注意性能问题。
假设当前对应的字符串值为"\xff\x00\x00"
,这是一个二进制字符串的十六进制表示,"\xff"
代表8
个连续的1
,"\x00"
代表8
个连续的0
。
查找第一个为1
的bit
:
BITPOS mykey 1
(integer) 0
查找第一个为0
的bit
:
BITPOS mykey 0 8
(integer) -1
BITPOS mykey 0 9
(integer) 9
2.5 BITFIELD
BITFIELD
允许将 Redis
字符串视为一个位数组,并允许用户对其中的位进行操作,如获取、设置和递增等。对于每个子命令,BITFIELD
都会返回一个响应数组,其中每个数组元素都与参数列表中的相应操作相匹配。
基本语法:
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW wrap|sat|fail]
参数说明:
key
:要操作的Redis键。GET
:用于从字符串中获取指定位置的位域值。SET
:用于设置字符串中指定位置的位域值。INCRBY
:用于将字符串中指定位置的位域值进行增加。OVERFLOW
:用于指定溢出处理方式,可选值有wrap
(回绕)、sat
(饱和)和fail
(失败)。
子命令GET
参数说明:
type
:指定读取数据的类型,可以是u
(无符号整数)或i
(有符号整数)。offset
:位字段的起始偏移位置,从0
开始计数。
子命令SET
参数说明:
type
:类型标识,可以是u
(无符号整数)或i
(有符号整数)。offset
:位字段的起始偏移位置。value
:要设置的位域值。
子命令INCRBY
参数说明:
type
:指定要递增的数据类型。offset
:位字段的起始偏移位置。increment
:递增的值。
OVERFLOW
用于指定溢出处理方式,参数说明:
wrap
:使用回绕方法处理溢出,位域超过最大值后再次增加数值则回到最小值。sat
:饱和计算,超过最大值再增加则数值仍不变。fail
:超过最大值再增加,则命令直接报错,拒绝指定。
注意事项:
- 可以在同一个命令调用中使用多个子命令,并按照给定的顺序执行它们。
- 命令的时间复杂度为
O(1)
,用于指定的每个子命令。 - 使用
BITFIELD
命令时,请确保对二进制位操作有深入的理解,以避免出现意外的结果。
示例,假设有一个 key
其对应的字符串值为"abcd"
(其ASCII码值分别为97
, 98
, 99
, 100
)。获取第一个字符的 ASCII
码值(无符号8
位整数):
BITFIELD mykey GET u8 0
修改第二个字符的ASCII
码值为大写'B'
(ASCII
码值为66
):
BITFIELD mykey SET u8 8 66
将第三个字符的ASCII
码值增加1
('c'
变为'd'
):
BITFIELD mykey INCRBY u8 16 1
2.6 BITFIELD_RO
BITFIELD_RO
命令作为 BITFIELD
命令的只读变体。这个命令允许用户从二进制位图中安全地读取数据,而不需要担心在只读副本上执行写操作。
基本语法:
BITFIELD_RO key [GET encoding offset [GET encoding offset ...]]
参数说明:
key
: 要操作的二进制位图的键名。GET
: 表示读取操作。encoding
: 指定要读取的数据的编码类型(如u8
、i8
、u16
、i16
、u32
、i32
、u64
、i64
、f32
、f64
)。offset
: 指示在二进制位图中的起始位置(以位为单位)。
注意事项:
- 由于原始的
BITFIELD
命令包含SET
和INCRBY
等写操作选项,因此它在Redis
命令表中被标记为写命令。这意味着在Redis
集群的只读副本上,即使连接处于只读模式,该命令也会被重定向到主实例。 - 为了在只读副本上允许
BITFIELD
行为而不破坏命令标志的兼容性,Redis 6.2
引入了BITFIELD_RO
变体。 - 通过使用
BITFIELD_RO
,可以在只读副本上安全地执行读取操作,而无需担心数据的不一致性或其他与写操作相关的问题
示例,假设有一个名为 hello
的二进制位图,并且我们想要从第 16
位开始读取一个 8
位有符号整数(i8
):
BITFIELD_RO hello GET i8 16
2.7 BITOP
BITOP 命令用于对多个键(包含字符串值)执行位操作,并将结果存储在目标键中。它支持四种位操作:AND
(与)、OR
(或)、XOR
(异或)和 NOT
(非)。NOT
操作是特殊的,因为它只接受一个输入键,因为位反转只作为一元运算符才有意义。
基本语法:
BITOP <AND | OR | XOR | NOT> destkey key [key ...]
参数说明:
<AND | OR | XOR | NOT>
:要执行的位操作类型。destkey
:存储操作结果的目标键。key [key ...]
:要参与位操作的键列表。对于NOT
操作,只需要一个键。
示例:
redis> SET key1 "foobar"
"OK"
redis> SET key2 "abcdef"
"OK"
redis> BITOP AND dest key1 key2
(integer) 6
redis> GET dest
"`bc`ab"
3. 应用场景
Bitmap
以极小的空间存储大量数据,2^32
次方(约40
亿)数据只需要约 500MB
内存,并提供了快速的查询和统计功能。Redis Bitmap
的应用场景广泛,尤其在处理大量二进制数据或需要快速进行二值状态统计的场合下表现出色。
二值状态统计是指在集合中,元素的取值只有 0
和 1
两种状态,在实际开发中,经常会遇到签到/未签到、登录/未登录等情况。
常用场景:
- 签到打卡:每个用户每天的签到情况可以用一个
bit
位表示,签到为1
未签到为0
。 - 限制
IP
地址访问频率:每个IP
地址对应Bitmap
的一个bit
位,访问时设置该位为1
。判断某个IP
地址是否已经访问过,并据此限制其访问频率。 - 用户登录状态:可以使用
bit
位来记录用户的登录状态,1
表示已登录,0
表示未登录。
3.1 用户登录状态
例如,当天 ID
为 0-3
的用户进行了登录:
localhost:0>SETBIT login_status:20240624 0 1
"0"
localhost:0>SETBIT login_status:20240624 1 1
"0"
localhost:0>SETBIT login_status:20240624 2 1
"0"
localhost:0>SETBIT login_status:20240624 3 1
"0"
查看某个用户当天是否登录:
localhost:0>GETBIT login_status:20240624 3
"1"
localhost:0>GETBIT login_status:20240624 4
"0"
localhost:0>
查看当天登录用户总数:
localhost:0>BITCOUNT login_status:20240624
"4"
3.2 签到打卡
设置 ID
为 123
的用户在 202405
第一天和第六天进行了签到:
localhost:0>SETBIT sign:123:202405 0 1
"0"
localhost:0>SETBIT sign:123:202405 5 1
"0"
查看用户当月某日是否进行了签到:
localhost:0>GETBIT sign:123:202405 10
"0"
localhost:0>GETBIT sign:123:202405 0
"1"
统计当月的签到次数:
localhost:0>BITCOUNT sign:123:202405
"2"
查看当月第一次签到的日期:
localhost:0>BITPOS sign:123:202405 1
"0"