Redis 命令及数据类型 -- Bitfield

摘要

Bitfield 核心详解

• BITFIELD 是 Redis 用于把一个 字符串值视为一个由二进制“位数组”组成的存储区,并对其中任意指定位置的整数域进行读取、写入、自增等操作的命令。
• 这些整数域可以是任意位宽(例如 1 位、4 位、8 位、31 位、63 位等),可指定为有符号(signed)或无符号(unsigned)。

Bitfield 命令

1. BITFIELD 批量操作

• BITFIELD 命令支持在一次调用中执行多个操作,并将结果按操作顺序返回。

1
2
3
4
5
BITFIELD key [GET encoding offset | [OVERFLOW <WRAP | SAT | FAIL>]
<SET encoding offset value | INCRBY encoding offset increment>
[GET encoding offset | [OVERFLOW <WRAP | SAT | FAIL>]
<SET encoding offset value | INCRBY encoding offset increment>
...]]

• 参数说明(核心部分)

参数 含义
key 操作的 Redis 字符串键
GET encoding offset 从指定位偏移量读取一个整数
SET encoding offset value 在指定位置写入整数
INCRBY encoding offset increment 在指定位置对整数做增量操作
OVERFLOW WRAP/SAT/FAIL 配置随后的算数操作溢出行为

• 数据类型(encoding):用于指定整数的位宽和符号类型

前缀 含义 示例
u<number> 无符号整数(unsigned),占位 bits = number u5 — 5 位无符号整数
i<number> 有符号整数(signed),占位 bits = number i10 — 10 位有符号整数

• 溢出(OVERFLOW):默认算数操作中可能发生溢出,OVERFLOW 允许你控制处理策略。注意,这部分必须在后续的 SET/INCRBY 操作之前指定。

策略 行为
WRAP 环绕(默认)溢出按环形计数处理
SAT 饱和,在边界值保持最大/最小
FAIL 溢出时操作失败并返回错误
1
2
# 4 位无符号整数加 1 时,如果超过 15,则保持 15(饱和)。
BITFIELD key OVERFLOW SAT INCRBY u4 0 1

• 返回值: BITFIELD 会为每个子命令返回一个整数数组,数组各元素按操作顺序对应执行结果

1
2
3
4
BITFIELD mykey INCRBY i5 100 1 GET u4 0
# 输出
1) (integer) 1
2) (integer) 0

2. BITFIELD_RO 批量只读

  • Redis 6.0 新增的只读版本,用于批量只读

1
BITFIELD_RO key [GET encoding offset [GET encoding offset ...]]

综合示例

示例 1. 设置并读取简单整数

1
2
3
4
5
6
7
8
9
10
11
> SET mykey "" # 此时mykey 的值是空字符串,长度为 0
# SET u4 0 7:在 key mykey 偏移 0 位置设置 4 位无符号整数值为 7,即前4位变成 0111,因为至少8bit,所以实际值是 01110000
# GET u4 0:读取 key mykey 偏移 0 位的 4 位无符号整数,此时 mykey 虽然是 01110000,但这里指定只读前4位,即 0111,所以转换为二进制就是 7
> BITFIELD mykey SET u4 0 7 GET u4 0
# 输出
1) (integer) 0 # set命令的返回值,这里返回set前的值
2) (integer) 7 # get命令的返回值
# 设置有符号整数
> BITFIELD mykey SET i4 0 -2 GET i4 0
1) (integer) 7 # 返回set前的值,之前是7,即 0111,set后变为 -2,即 1110
2) (integer) -2 # get命令的返回值

负数的二进制表示

  • 以 -2 为例,先写出 +2 的二进制,以8位为例,就是 00000010,4位就是 0010
  • 按位取反(得到反码),例如 00000010,按位取反就是 11111101
  • 加 1(得到补码),11111101 + 1,得到 11111110,这就是 -2 的二进制表示
  • 如果是4位,则 -2 就是 1110

示例 2. 自增计数器

1
2
3
4
5
6
7
# 如果 counter 之前为空,则视为 0
# INCRBY u8 8 5:在 key counter 的第 8 位(下一个字节开头处)上按 8 位 unsigned 类型自增 5
# GET u8 8:读取 key counter 的第 8 位(下一个字节开头处)的 8 位无符号整数,返回这个整数的新值
BITFIELD counter INCRBY u8 8 5 GET u8 8
# 输出
1) (integer) 5 # INCRBY 的返回值,这里返回的是增加的值,而不是计算后的值
2) (integer) 5

示例 3. 带溢出控制的操作

1
2
3
4
5
6
7
# OVERFLOW SAT: 设置溢出策略为 SAT(饱和)
# INCRBY u4 0 20: 对 4 位无符号整数(最大 15)加 20,此时肯定会溢出,由于过 15 上限,结果返回 15(饱和)
# GET u4 0:读取 4 位无符号整数,即15
BITFIELD limits OVERFLOW SAT INCRBY u4 0 20 GET u4 0
# 输出
1) (integer) 15
2) (integer) 15

示例 4. 批量多个操作

1
2
3
4
5
6
7
8
9
# GET u4 0 GET u4 4: 依次读取多个不同 bit 偏移量上的小整数
# SET u4 8 3 INCRBY i5 16 1: 写入、增量操作可以混合处理
# 返回结果数组对应每个子命令顺序返回结果
BITFIELD events GET u4 0 GET u4 4 SET u4 8 3 INCRBY i5 16 1
# 输出
1) (integer) 0 # events原先为空,所以这里返回0
2) (integer) 0 # events原先为空,所以这里返回0
3) (integer) 0 # events原先为空,这里返回原先的值,所以还是0,但此时实际的值是 0000000000110000
4) (integer) 1 # 返回增加的值,即 1,但此时实际的值是 000000000011000000001000,即占用了3个字节

SpringBoot 操作 BitField

  • SpringBoot 的 StringRedisTemplate.opsForValue() 中 BitField 数据类型 的操作方法与 Redis 原生命令的对应关系如下:

注意这里一定要用 StringRedisTemplate 来操作 BitField

  • 位字段读/写/自增

方法功能 方法 Redis 原始命令 备注
位字段操作(读/写/自增) bitField(K key, BitFieldSubCommands subCommands) BITFIELD key ... 原子执行多个子命令
  • 示例

1
2
3
4
5
BITFIELD limits
OVERFLOW SAT
SET u4 0 3
INCRBY u4 0 20
GET u4 0
1
2
3
4
5
6
7
8
9
// SET u4 0 3
BitFieldSubCommands.BitFieldSet bitFieldSet = BitFieldSubCommands.BitFieldSet.create(BitFieldSubCommands.BitFieldType.unsigned(4), BitFieldSubCommands.Offset.offset(0), 3);
// OVERFLOW SAT INCRBY u4 0 20
BitFieldSubCommands.BitFieldIncrBy bitFieldIncrBy = BitFieldSubCommands.BitFieldIncrBy.create(BitFieldSubCommands.BitFieldType.unsigned(4), BitFieldSubCommands.Offset.offset(0), 20, BitFieldSubCommands.BitFieldIncrBy.Overflow.SAT);
// GET u4 0
BitFieldSubCommands.BitFieldGet bitFieldGet = BitFieldSubCommands.BitFieldGet.create(BitFieldSubCommands.BitFieldType.unsigned(4), BitFieldSubCommands.Offset.offset(0));

// 获取结果,每个子操作返回一个结果
List<Long> limits = redisTemplate.opsForValue().bitField("limits", BitFieldSubCommands.create(bitFieldSet, bitFieldIncrBy, bitFieldGet));