Redis 扩展模块 -- RedisBloom

摘要

RedisBloom 简介

  • RedisBloom 是 Redis 官方维护的一个扩展模块,隶属于 Redis Stack,专门用于提供概率型数据结构(Probabilistic Data Structures)的高性能实现。

  • 它通过牺牲一定的精确性,换取极低的内存占用和极高的吞吐能力,非常适合海量数据场景下的“存在性判断”和“近似统计”。

  • 该模块以 Redis Module 方式加载,可无缝集成到现有 Redis 实例中。

  • Redis8+,RedisBloom 已经内置在 Redis 中,不需要单独安装。

RedisBloom 提供的核心数据结构

  • 1️⃣ Bloom Filter(布隆过滤器)

  • 2️⃣ Cuckoo Filter(布谷鸟过滤器)

    • Bloom Filter 的增强版,支持 删除元素
    • 对应Redis命令: CF.xxx
  • 3️⃣ Count-Min Sketch(CMS)

    • 近似统计元素出现频率
    • 对应Redis命令: CMS.xxx
  • 4️⃣ Top-K

    • 统计访问频率最高的 K 个元素
    • 对应Redis命令: TOPK.xxx

安装 RedisBloom

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

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
mkdir -p /usr/local/soft/modules/
cd /usr/local/soft/modules
# clone 代码,这里 --recursive 是为了拉取子模块
git clone --recursive https://github.com/RedisBloom/RedisBloom.git
cd RedisBloom
# 推荐切换到稳定的release版本
git checkout v2.8.17
# 更新子模块,非必须,如果上面 clone 时没有加上 --recursive ,这个步骤就不能省略
git submodule update --init --recursive

# 检查并安装需要的依赖
./sbin/setup
## 输出
# readies version: 7fc8e62
dnf install -q -y ca-certificates
dnf install -q -y wget unzip
/usr/local/soft/modules/RedisBloom/deps/readies/bin/enable-utf8
dnf install -q -y git jq
dnf install -q -y which
/usr/local/soft/modules/RedisBloom/deps/readies/bin/getepel
/usr/local/soft/modules/RedisBloom/deps/readies/bin/getgcc --modern
dnf install -q -y valgrind
/usr/local/soft/modules/RedisBloom/sbin/get-fbinfer
dnf install -q -y lcov
/usr/bin/python3 /usr/local/soft/modules/RedisBloom/deps/readies/bin/getrmpytools --reinstall --modern
/usr/bin/python3 /usr/local/soft/modules/RedisBloom/deps/readies/bin/getcmake --usr
/usr/bin/python3 -m pip install --disable-pip-version-check --user -r tests/flow/requirements.txt
/usr/local/soft/modules/RedisBloom/deps/readies/bin/getaws
/usr/bin/python3 -m pip install --disable-pip-version-check --user pudb

# 编译
make
# 输出
Building /usr/local/soft/modules/RedisBloom/bin/linux-x64-release/t-digest-c/src/libtdigest_static.a ...

Generating /usr/local/soft/modules/RedisBloom/bin/linux-x64-release/t-digest-c/Makefile ...
-- The C compiler identification is GNU 11.5.0
-- The CXX compiler identification is GNU 11.5.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Setting build type to 'Release' as none was specified.
-- Configuring done
-- Generating done
-- Build files have been written to: /usr/local/soft/modules/RedisBloom/bin/linux-x64-release/t-digest-c

Building /usr/local/soft/modules/RedisBloom/bin/linux-x64-release/t-digest-c/libtdigest_static.a ...
[ 50%] Building C object src/CMakeFiles/tdigest_static.dir/tdigest.c.o
[100%] Linking C static library libtdigest_static.a
[100%] Built target tdigest_static
Compiling deps/bloom/bloom.c...
Compiling deps/murmur2/MurmurHash2.c...
Compiling deps/rmutil/util.c...
Compiling src/rebloom.c...
Compiling src/sb.c...
Compiling src/cf.c...
Compiling src/rm_topk.c...
Compiling src/rm_tdigest.c...
Compiling src/topk.c...
Compiling src/rm_cms.c...
Compiling src/cms.c...
Linking /usr/local/soft/modules/RedisBloom/bin/linux-x64-release/redisbloom.so...

./sbin/setup 报错

  • 本人使用的是 Amazon Linux 2023(内核 6.1),即 EL9,相当于CentOS 9,所以第一次运行会报错,大致报错信息如下:
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
./sbin/setup
## 错误信息
[FAILED] raven-release.el9.noarch.rpm: Status code: 403 for https://dyn.su/el9/base/x86_64/raven-release.el9.noarch.rpm (IP: 104.21.57.14)
Status code: 403 for https://dyn.su/el9/base/x86_64/raven-release.el9.noarch.rpm (IP: 104.21.57.14)

In /usr/local/soft/modules/RedisBloom/deps/readies/bin/getepel:
346 # xinstall --allowerasing https://dl.fedoraproject.org/pub/epel/epel-release-latest-${EPEL}.noarch.rpm
347 fi
348
349 >>> install_raven
350 install_remi
351 # install_centos_stream_repos
352

command failed: /usr/local/soft/modules/RedisBloom/deps/readies/bin/getepel

In /usr/local/soft/modules/RedisBloom/sbin/setup:
16 python3 -m pip list
17 fi
18
19 >>> $ROOT/sbin/system-setup.py
20 if [[ $VERBOSE == 1 ]]; then
21 python3 -m pip list
22 fi 编译安装时报错

## 错误分析与解决方法
问题原因:
你这个错误不是 RedisBloom 本身的编译问题,而是 依赖环境初始化(readies / getepel)阶段失败,失败点非常明确。
readies/bin/getepel 脚本在 RHEL 9 / Rocky 9 / AlmaLinux 9 系统上尝试安装 Raven Repo,但该仓库地址 https://dyn.su/el9/... 已被 403 Forbidden 拒绝,导致脚本直接失败并中断 setup。
这不是你机器的问题,而是 RedisBloom 依赖工具链对 EL9 的兼容性滞后。
RedisBloom 的 ./sbin/setup 会调用 deps/readies/bin/getepel
这个脚本用于:
安装 EPEL
安装 Raven Repo(EL9 特有)
安装 Remi Repo
但 Raven Repo 目前已不稳定 / 不再公开提供 rpm 下载,而 readies 代码仍然在强制安装。
你的系统是 EL9 系列,日志中明确:raven-release.el9.noarch.rpm
说明你使用的可能是如下系统中的一个:
RHEL 9
Rocky Linux 9
AlmaLinux 9
CentOS Stream 9

推荐解决方案:
直接修改 getepel 脚本,禁用 install_raven
vi deps/readies/bin/getepel,找到所有 install_raven,并将其注释掉即可

Redis 启用模块

  • 将生成的 redisbloom.so 拷贝到 redis 的 modules 目录下(非必须)

1
cp /usr/local/soft/modules/RedisBloom/bin/linux-x64-release/redisbloom.so /usr/local/soft/redis-7.4.7/modules/redisbloom.so
  • Redis 启用模块有三种方法

1
2
3
4
5
6
# 1.将 redisbloom.so 添加到 redis.conf 中,需要重启 redis
loadmodule /usr/local/soft/redis-7.4.7/modules/redisbloom.so
# 2.也可以通过如下方式加载模块
redis-server --loadmodule /usr/local/soft/redis-7.4.7/modules/redisbloom.so
# 3.不需要重启redis
redis-cli MODULE LOAD /usr/local/soft/redis-7.4.7/modules/redisbloom.so
加载方式 是否持久
MODULE LOAD(redis-cli) ❌ 仅当前进程
命令行 redis-server --loadmodule ❌ 仅本次启动
redis.confloadmodule 永久生效 (推荐/生产)
  • 本文采用 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
# 将 redisbloom.so 添加到 redis.conf 中,需要重启 redis
loadmodule /usr/local/soft/redis-7.4.7/modules/redisbloom.so

# 启动redis
redis-server redis.conf

# 登录测试
redis-cli --user admin --pass 123456
# 查看模块
127.0.0.1:6379> info Modules
## 输出
# Modules
module:name=bf,ver=20817,api=1,filters=0,usedby=[],using=[],options=[handle-io-errors]

127.0.0.1:6379> MODULE LIST
# 输出
1) 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)

测试BF命令

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
# 初始化一个BloomFilter,错误率0.01,元素数量1000
127.0.0.1:6379> BF.RESERVE test 0.01 1000
OK
# 查看类型
127.0.0.1:6379> type test
MBbloom--

# 添加元素
127.0.0.1:6379> BF.ADD test user1
(integer) 1
127.0.0.1:6379> BF.ADD test user2
(integer) 1
# 批量添加元素
127.0.0.1:6379> BF.MADD test user3 user4 user5
1) (integer) 1
2) (integer) 1
3) (integer) 1
# 返回Bloom过滤器的基数,即添加的元素数量(存在误差)
127.0.0.1:6379> BF.CARD test
(integer) 5
# 查询元素是否存在
127.0.0.1:6379> BF.EXISTS test user2
(integer) 1
127.0.0.1:6379> BF.EXISTS test user6
(integer) 0
# 批量查询元素是否存在
127.0.0.1:6379> BF.MEXISTS test user1 user2 user6
1) (integer) 1
2) (integer) 1
3) (integer) 0

# 获取信息
127.0.0.1:6379> BF.INFO test
1) Capacity # 初始化时的容量,超过该值后,会触发 扩容(新建一个子 Bloom Filter)
2) (integer) 1000
3) Size # 当前 Bloom Filter 实际使用的 bit array 大小(字节),由 capacity + error_rate 计算得出,值一旦创建 不会随元素减少
4) (integer) 1480
5) Number of filters # 当前 key 内部包含的 Bloom Filter 数量
6) (integer) 1
7) Number of items inserted # 已调用 BF.ADD / BF.MADD 插入的元素总数
8) (integer) 5
9) Expansion rate # Bloom Filter 扩容时,新建子过滤器的容量增长倍率
10) (integer) 2

SpringBoot 集成

  • SpringBoot 的 RedisTemplate 中没有提供对RedisBloom的封装,需要自己封装,我这里封装了一个简易的RedisBloomService

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.example.bloomfilter;

/**
* 基于Redis Bloom插件的BloomFilter实现
* https://github.com/RedisBloom/RedisBloom/releases
* <p>
* 不想安装插件也可以使用 Redission 的 BloomFilter
*/


import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.List;

@Service
public class RedisBloomService {

private final StringRedisTemplate redisTemplate;

public RedisBloomService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

/**
* 初始化 BloomFilter,不能重复执行
*
* @param filterName BloomFilter 名称
* @param errorRate 错误率,比如为0.01,即 1%
* @param capacity 容量,比如为1000
*
* errorRate 说明
* 1% error rate requires 7 hash functions and 9.585 bits per item.
* 0.1% error rate requires 10 hash functions and 14.378 bits per item.
* 0.01% error rate requires 14 hash functions and 19.170 bits per item.
*/
public void reserve(String filterName, double errorRate, long capacity) {
redisTemplate.execute((RedisCallback<String>) connection ->
connection.scriptingCommands().eval(
("return redis.call('BF.RESERVE', KEYS[1], " + errorRate + ", " + capacity + ")").getBytes(),
ReturnType.STATUS,
1,
filterName.getBytes()
)
);
}

/**
* 添加元素到 BloomFilter,BloomFilter 不存在会自动创建
*/
public boolean add(String filterName, String value) {
// 使用 RedisModule 提供的 BF.ADD 命令
return Boolean.TRUE.equals(redisTemplate.execute((RedisCallback<Boolean>) connection ->
connection.scriptingCommands().eval(
("return redis.call('BF.ADD', KEYS[1], ARGV[1])").getBytes(),
ReturnType.BOOLEAN,
1,
filterName.getBytes(),
value.getBytes()
)
));
}

/**
* 判断元素是否存在
*/
public boolean exists(String filterName, String value) {
return Boolean.TRUE.equals(redisTemplate.execute((RedisCallback<Boolean>) connection ->
connection.scriptingCommands().eval(
("return redis.call('BF.EXISTS', KEYS[1], ARGV[1])").getBytes(),
ReturnType.BOOLEAN,
1,
filterName.getBytes(),
value.getBytes()
)
));
}

/**
* 批量添加
*/
public List<Boolean> addBatch(String filterName, String... values) {
if (values == null || values.length == 0) {
return Collections.emptyList();
}

// 构建 keys + args 数组
byte[][] keysAndArgs = new byte[1 + values.length][];
keysAndArgs[0] = filterName.getBytes(); // KEYS[1]
for (int i = 0; i < values.length; i++) {
keysAndArgs[i + 1] = values[i].getBytes(); // ARGV[1..n]
}

// 调用 BF.MADD 命令
return redisTemplate.execute((RedisCallback<List<Boolean>>) connection ->
connection.scriptingCommands().eval(
("return redis.call('BF.MADD', KEYS[1], unpack(ARGV))").getBytes(),
ReturnType.MULTI,
1, // numKeys
keysAndArgs // KEYS + ARGV
)
);
}
}