Redis 扩展模块 -- RedisRoaring 的安装方法

摘要

RedisRoaring 简介

  • RedisRoaring是一个开源的 Redis 模块(Redis Module),由 Antonio Guilherme Ferreira Viggiano 维护,目的是将 Roaring Bitmap 数据结构原生集成到 Redis 中作为新的数据类型与命令集。

  • 它基于 CRoaring 库实现压缩位图(Roaring Bitmap)功能,这是一种高效、压缩的位图数据结构,适合处理大规模整数集合。

  • 兼容 Redis 6.0 及以上版本,在不同版本中对命令元数据、ACL 分类等功能有自动适配支持。

原生的redis bitmap有哪些问题?

  • 结论先行:Redis 原生 Bitmap 的核心问题是「空间不可控 + 大规模位运算性能随最大偏移线性增长」,而 redis-roaring 通过 Roaring Bitmap 解决了稀疏数据内存浪费和大范围扫描效率问题。

  • Bitmap 是连续数组,不支持稀疏压缩, 其必须 从 0 到最大 bit offset 全部连续分配内存。你只要设置了 bit=1 的最大下标是 N,那么 Redis 至少要分配 N/8 字节内存。

  • 假设你要存储用户ID集合,但是存储的ID范围较大,比如 1-2000000000,那么就会导致Bitmap的bit offset非常大,导致Bitmap的存储空间非常大。

  • 另外如果数据比较稀疏,极端情况下只存储了2个ID,ID1:1,ID2:2000000000,那么Bitmap就要按照最大的 offset进行分配空间,导致Bitmap的存储空间非常大,但是中间的空间又被浪费了。

1
2
3
4
5
6
SETBIT user:bitmap 1 1
SETBIT user:bitmap 2000000000 1
# 2_000_000_000 bits ≈ 250 MB

# Redis 内部必须扫描:0 ... max_offset,即使只有 2 个 bit 为 1,也必须扫描 250MB 内存。
BITCOUNT user:bitmap

Roaring Bitmap实现思路

  • 将32位的int类型数据进行高16位低16位划分,高16位就被划分成2^16=65536个大桶(容器),低16位最多被划分成2^16=65536个小桶(元素)

  • 把要统计的数字拆分位高16位和低16位,高16位用作容器的索引、用于定位数字在哪个容器;低16位用作容器内元素的索引、用作定位数字在容器内的位置。

  • 高16位和低16位的计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
公式:x = high * 2^16 + low
对一个整数 x:
高 16 位(High) = x / 2^16(整数除法)
低 16 位(Low) = x % 2^16

高 16 位用于定位“容器(container)”,低 16 位用于定位容器内部的位置。

假设一个 32 位整数:x = 305419896 = 0x12345678 (十六进制)
高 16 位(High) = x / 2^16 = 305419896 / 65536 = 4660
低 16 位(Low) = x % 2^16 = 305419896 % 65536 = 22136
属于:container 4660, offset 22136

转为二进制(32 位)进行验证:
0001 0010 0011 0100 | 0101 0110 0111 1000
↑ 高 16 位 | ↑ 低 16 位

| 部分 | 二进制 | 十六进制 | 十进制 |
| ---- | ------------------- | ------ | ----- |
| 高16位 | 0001 0010 0011 0100 | 0x1234 | 4660 |
| 低16位 | 0101 0110 0111 1000 | 0x5678 | 22136 |

边界例子(容器切换点)
x = 65535
high = 65535 / 65536 = 0
low = 65535 % 65536 = 65535
属于:container 0, offset 65535 (容器0的最后一个位置)

x = 65536
high = 65536 / 65536 = 1
low = 65536 % 65536 = 0
属于:container 1, offset 0 (容器1的第一个位置)
  • 容器有三种不同类型: ArrayContainerBitmapContainerRunContainer,根据要统计的数字的数量和数字的连续性自动选择合适的容器。

类型 说明
ArrayContainer 稀疏(少量整数),元素为short类型的有序数组
最多存储 65536 个 short 元素,占用 128 KB 内存
BitmapContainer 密集 ,使用bitmap存储数据,固定占用 8kB 内存
RunContainer 连续区间,使用Run-Length Encoding方式压缩存储的元素,对连续数据的压缩效果特别好。
它的原理是,对于连续出现的数字,只记录初始数字和后续数量。即:
对于数据集:5,会压缩为5, 0
对于数据集:5, 6, 7, 8, 9, 10,会压缩为5, 5
对于数据集:5, 7, 9, 11, 12,会压缩为5, 0, 7, 0, 9, 0, 11, 1
最好情况:全部是连续数字,只会存储 2 个 short ,占用 4 bytes
最坏情况:0~65535的范围内填充所有的奇数位或偶数位,需要 65536 个short,占用 128 KB

容器的使用及容器之间的转换

  • Roaring 的目标是:在保证操作性能的前提下,使内存占用最小。

  • 因此:插入、删除、合并、运算后,会评估当前 container 的“最优类型”,必要时自动转换

  • ✅ Array → Bitmap,触发条件:容器中元素数量 > 4096

1
2
3
4
5
6
原因:
Array 每个元素占 2 bytes
4096 × 2 = 8192 bytes
Bitmap 固定 8192 bytes

超过这个点,Bitmap 更省内存 + 查询更快。
  • ✅ Bitmap → Array,触发条件:容器中元素数量 <= 4096,一般在大量删除或 AND 运算后触发。

  • ✅ RunContainer ↔ Array / Bitmap,Run 没有固定阈值,而是 动态评估内存模型,基于如下三个指标:

    • run_count : 当前 RunContainer 中有多少个连续区间
    1
    2
    3
    例如集合:{1,2,3,4,5,  10,11,12,  100}
    连续区间:[1..5], [10..12], [100..100]
    因此:run_count = 3
run_count 数据形态 适合 Run 吗
很小(1~几十) 大段连续 极其适合
中等(几百) 稍碎 可能适合
很大(上千) 高度碎片化 不适合
  • run_total_coverage : run 覆盖的总元素数量(本质就是元素的数量)

1
2
3
4
5
6
7
8
[1..5]  → 5 个
[10..12] → 3 个
[100..100] → 1 个

run_total_coverage = 9

实际上更重要的指标是 平均 run 长度
平均 run 长度 = run_total_coverage / run_count = 9 / 3 = 3
run_count coverage 平均长度 形态 适合 Run 吗
1 60,000 60,000 超长连续 极适合
10 50,000 5,000 大段连续 适合
5,000 50,000 10 碎片连续 非常不适合
  • estimated_memory : 估算内存占用,这是核心决策指标

1
2
3
4
5
6
7
8
9
10
11
memory_run: 如果用 RunContainer,需要多少内存, ≈ run_count × RUN_ENTRY_SIZE(4 bytes)
memory_array: 如果用 ArrayContainer,需要多少内存, ≈ 元素数量 × 2 bytes
memory_bitmap: 如果用 BitmapContainer,需要多少内存,固定内存 = 8192 bytes

▶ 选择逻辑(简化)
if memory_run < min(memory_array, memory_bitmap):
use RunContainer
else if memory_array < memory_bitmap:
use ArrayContainer
else:
use BitmapContainer
转换方向 触发条件
Array → Bitmap 元素数量 > 4096
Bitmap → Array 元素数量 ≤ 4096
任意 → Run 连续区间压缩明显
Run → Array / Bitmap run 碎片化或内存劣化
批量运算 根据结果自动选择
optimize 强制重新评估
  • ✅ 在 Roaring 的自动选择机制下,正常情况下单个 container 的内存占用会被控制在 ≈ 8KB 左右或更小,在“理论极限完全填满65535个 container”情况下,大约占 520 MB 内存(除了数据本身还有一些元数据)

项目 估算
Bitmap 数据 ~512 MB
Container 元数据 ~4 MB
索引结构 ~1 MB
Redis Key 自身 ~0.1 MB
合计 约 517 MB

  • 直观对比表

维度 Redis Bitmap redis-roaring
存储结构 连续 bit array 压缩 bitmap(分块)
稀疏数据内存 极差 极优
最大 offset 越大内存越爆 几乎无影响
BITCOUNT / BITOP 扫描整个 bitmap 只扫描有效 container
扩容成本 可能大规模 realloc 局部 container 扩展
单 bit 读写 极快 略慢(结构层)
模块依赖 原生 Redis Module
运维复杂度 最低 稍高

RedisRoaring核心价值与优势

    1. 内存效率更高
    • Roaring Bitmap 会根据数据稀疏/密集自动选择最优表示方式(数组、位图或运行长度编码),显著减少内存占用,相比传统 Redis Bitmap 在稀疏数据上能节省大量空间。
    1. 高性能位操作
    • 对单个位的操作保持 O(1) 性能,与 Redis 原生位图操作一致。
    • 对于大规模操作(例如 BITOP AND/OR/XOR)性能显著优于原生位图,某些操作可达到 8 倍性能提升。
    1. 结构丰富的命令集
    • 除了传统位图命令之外,redis-roaring 提供更丰富的数据操作命令,支持批量和分析型用例。

安装 RedisRoaring

安装时需要科学上网,主要是安装依赖时需要从海外网下载,如果要部署在国内服务器,可能会连接失败。
可以在海外的相同配置的服务器上进行编译,之后将编译好的redistimeseries.so上传到国内服务器即可。

1
2
3
4
5
6
7
8
9
10
11
mkdir -p /usr/local/soft/modules/
cd /usr/local/soft/modules
# clone 代码
git clone https://github.com/aviggiano/redis-roaring.git
cd redis-roaring/
# 推荐切换到稳定的release版本
git checkout v1.7.1

# 编译构建,作者将下载子模块、编译安装等命令都封装到一个脚本中,这里直接执行脚本即可
./configure.sh
# 如果没有错误,则说明编译成功,输出: dist/libredis-roaring.so
  • 实际上redis-roaring在构建后会在dist目录下生成好 Redis 相关命令和配置文件,如果没有安装Redis则可以直接使用。v1.7.1版本提供的Redis的版本为8.2.1,但此时是不带 Stack 相关模块的。

Redis 启用模块

  • 将生成的 libredis-roaring.so 拷贝到 redis 的 modules 目录下(非必须),目录不存在则创建

1
2
# 注意 .so 文件需要包含可执行权限
cp dist/libredis-roaring.so /usr/local/soft/redis-7.4.7/modules/libredis-roaring.so
  • 本文采用 loadmodule 加载模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 将 libredis-roaring.so 添加到 redis.conf 中,需要重启 redis
loadmodule /usr/local/soft/redis-7.4.7/modules/libredis-roaring.so

# 启动redis
redis-server redis.conf

# 登录测试
redis-cli --user admin --pass 123456
# 查看模块
127.0.0.1:6379> MODULE LIST
# 输出
1) 1) "name"
2) "ReJSON"
3) "ver"
4) (integer) 20816
5) "path"
6) "/usr/local/soft/redis-7.4.7/modules/rejson.so"
7) "args"
8) (empty array)
2) 1) "name"
2) "timeseries"
3) "ver"
4) (integer) 11209
5) "path"
6) "/usr/local/soft/redis-7.4.7/modules/redistimeseries.so"
7) "args"
8) (empty array)
3) 1) "name"
2) "search"
3) "ver"
4) (integer) 21025
5) "path"
6) "/usr/local/soft/redis-7.4.7/modules/redisearch.so"
7) "args"
8) (empty array)
4) 1) "name"
2) "bf"
3) "ver"
4) (integer) 20817
5) "path"
6) "/usr/local/soft/redis-7.4.7/modules/redisbloom.so"
7) "args"
8) (empty array)
5) 1) "name"
2) "REDIS-ROARING"
3) "ver"
4) (integer) 10700
5) "path"
6) "/usr/local/soft/redis-7.4.7/modules/libredis-roaring.so"
7) "args"
8) (empty array)