1 redis bitmap 概述

1.1 redis bitmap 是什么?

Bitmap(位图, 也称为位数组或者位向量等)是一串连续的二进制数组(0和1),可通过偏移量(offset)定位元素;是一种实现对的操作的'数据结构',在数据结构加引号主要因为:

  • Bitmap 在redis中本身不是一种独立的数据结构,底层实际上是字符串,可以借助字符串进行位操作

  • Bitmap 单独提供了一套命令,所以与使用字符串的方法不太相同。可以把 Bitmaps 想象成一个以为单位的数组数组的每个单元只能存储 0 和 1,数组的下标在 Bitmap 中叫做偏移量 offset。

由于bit是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。

1.2 占用存储空间

  • Bitmap 本身不是一种数据结构,底层实际上使用字符串来存储。
  • 由于 Redis 中字符串的最大长度是 512 MB字节,所以 BitMap 的偏移量 offset 值也是有上限的,其最大值是:8 * 1024 * 1024 * 512 = 2^32。
  • 又由于 C 语言中字符串的末尾都要存储一位分隔符,所以实际上 BitMap 的偏移量 offset 值上限是:2^32-1。
  • Bitmap 实际占用存储空间取决于 BitMap 偏移量 offset 的最大值,占用字节数可以用 (max_offset / 8) + 1 公式来计算或者直接借助底层字符串函数 strlen 来计算:
127.0.0.1:6379> setbit login:20220515 0 1
(integer) 0
# (0 / 8) + 1 = 1

127.0.0.1:6379> strlen login:20220515
(integer) 1

127.0.0.1:6379> setbit login:20220515 8 1
(integer) 0
# (8 / 8) + 1 = 2

127.0.0.1:6379> strlen login:20220515
(integer) 2

需注意的是:

  • 在第一次初始化 Bitmap 时,假如偏移量 offset 非常大,由于需要分配所需要的内存,整个初始化过程执行会比较慢,可能会造成 Redis 的阻塞
  • 在 2010 款 MacBook Pro 上,设置第 2^32-1 位,由于需要分配 512MB 内存,所以大约需要 300 毫秒;
  • 设置第 2^30-1 位(128 MB)大约需要 80 毫秒;
  • 设置第 2^28 -1 位(32MB)需要约 30 毫秒;
  • 设置第 2^26 -1(8MB)需要约 8 毫秒。
  • 一旦完成第一次分配,随后对同一 key 再设置将不会产生分配开销。

2 Redis bitmap 的操作使用

2.1 SETBIT / 设置bitmap字符串指定位置的值

  • 语法格式: SETBIT {key} {offset} {value}

  • 最早可用版本: 2.2.0

  • 时间复杂度:O(1)

  • 功能描述:

  • SETBIT 用来设置 key 对应第 offset 位的值(offset 从 0 开始算),可以设置为 0 或者 1。
  • 当指定的 KEY 不存在时,会自动生成一个新的字符串值。
  • 字符串会进行扩展以确保可以将 value 保存在指定的偏移量 offset 上。
  • 当字符串值进行扩展时,空白位置用 0 来填充。
  • 需要注意的是 offset 需要大于或等于 0,小于 2 的 32 次方。
setbit singleSquare:recommend:userId:3 999 1
>> 0

2.2 BITMAP / 获取BITMAP

  • 最早可用版本:2.2.0
  • 时间复杂度:O(1)
  • 语法格式: GETBIT key offset
  • 功能描述:
  • 获取 key 对应第 offset 位的值(offset 从 0 开始算)。
  • 当 offset 超过字符串长度时,字符串假定为一个 0 位的连续空间。
  • 当指定的 key 不存在时,假定为一个空字符串,offset 肯定是超出字符串长度范围。因此,该值也被假定为 0 位的连续空间,都会返回 0。
# 获取bigmap字符串
getbit singleSquare:recommend:userId:3 998
>> 0


## 2.3 STRLEN / 查看BITMAP长度
# 查看bitmap字符串的长度 | 占用字节数 := (max_offset / 8) + 1
``` shell
strlen singleSquare:recommend:userId:3
>> 999

2.4 BITCOUNT / 获取BITMAP非空位的个数

  • 最早可用版本:2.6.0
  • 时间复杂度:O(N)
  • 语法格式: BITCOUNT key [ start end [ BYTE | BIT]]
  • 功能描述:
  • 用来计算指定 key 对应字符串中,被设置为 1 的 bit 位的数量。
  • 一般情况下,字符串中所有 bit 位都会参与计数,我们可以通过 start 或 end 参数来指定一定范围内被设置为 1 的 bit 位的数量。
  • start 和 end 参数的设置和 GETRANGE 命令类似,都可以使用负数:比如 -1 表示最后一个位,而 -2 表示倒数第二个位等
  • 从 Redis 7.0.0 开始支持 BYTE 或者 BIT 选项
setbit singleSquare:recommend:userId:3 9999 1
>> 1250

getbit singleSquare:recommend:userId:3 999
>> 1

# 计算指定 key 对应字符串中,被设置为 1 的 bit 位的数量
bitcount singleSquare:recommend:userId:3
>> 2

2.5 BITOP / BITMAP的集合操作(交/并/补/差)

  • 最早可用版本:2.6.0
  • 时间复杂度:O(N)
  • 语法格式: BITOP operation destkey key [key ...]
  • 功能描述:
  • BITOP 是一个复合操作,支持在多个 key 之间执行按位运算并将结果存储在 destkey 指定的 key 中。
  • BITOP 命令支持四种按位运算:AND(交集)、OR(并集)、XOR(异或) 和 NOT(非):
BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN
BITOP NOT destkey srckey
  • 案例

假设 20220513 登录 App 的用户id为 1、3、5、7,如下图所示:

如果想算出 20220513 和 20220514 两天都登录过的用户数量,如下图所示:

如果想算出 20220513 和 20220514 任意一天登录过 App 的用户数量:

2.6 BITPOS

  • 最早可用版本:2.8.7
  • 时间复杂度:O(N)
  • 语法格式: BITPOS key bit [ start [ end [ BYTE | BIT]]]
  • 功能描述:
  • 用来计算指定 key 对应字符串中,第一位为 1 或者 0 的 offset 位置。除此之外,BITPOS 也有两个选项 start 和 end,跟 BITCOUNT 一样。
  • BYTE、BIT 这两个选项从 7.0.0 版本开始才能使用。
127.0.0.1:6379> bitpos login:20220513 1
(integer) 1

X 参考文献