RedisBloom插件:让你的Redis支持布隆和布谷鸟!缓存穿透-URL映射(不可删)/黑名单管理(可增删)


一、Bloom Filter 的原理

布隆过滤器(Bloom Filter)是一种高效的概率型数据结构,1970 年由 Burton Howard Bloom 提出,用于快速判断一个元素是否可能存在于一个集合中。它的核心优势在于空间效率高和查询速度快,但存在一定的误判率,且不支持删除操作。

1. 数据结构

Bloom Filter 由以下两部分组成:

  • 一个很长的二进制向量(bit array):初始时所有位都为 0。
  • 一系列随机映射函数(hash functions):通常是 k 个独立的哈希函数,用于将元素映射到二进制向量的多个位置。

2. 工作原理

  • 添加元素
    1. 对输入元素使用 k 个哈希函数,生成 k 个哈希值。
    2. 将这些哈希值对 bit array 的长度取模,得到 k 个索引位置。
    3. 将 bit array 中对应的 k 个位置置为 1。
  • 查询元素
    1. 对查询元素使用相同的 k 个哈希函数,生成 k 个哈希值。
    2. 检查 bit array 中对应的 k 个位置是否都为 1。
      • 如果有一个位置为 0,则该元素一定不在集合中
      • 如果所有位置都为 1,则该元素可能在集合中(存在误判)。
  • 误判率(False Positive Rate): 误判率与 bit array 的大小(m)、哈希函数数量(k)和集合元素数量(n)密切相关。公式为: [ P = (1 - e^{-kn/m})^k ] 通过调整 m 和 k,可以在空间效率和误判率之间取得平衡。

3. 优点

  • 空间效率高:相比存储完整数据集,占用内存极小。
  • 查询速度快:只需计算哈希函数并检查 bit array,时间复杂度为 O(k)。

4. 不足之处

  • 误判率:可能将不在集合中的元素误判为存在(False Positive),但不会漏判(False Negative)。
  • 不支持删除:由于多个元素可能共享相同的 bit 位,删除一个元素可能影响其他元素。
  • 无法精确计数:只能判断是否存在,无法统计元素出现次数。

二、Bloom Filter 在 Redis 中的集成

Redis 原生并不直接支持 Bloom Filter,但通过插件(如 RedisBloom)可以实现。RedisBloom 是一个独立的模块,通常需要手动编译和加载到 Redis 中。

1. 安装 RedisBloom

以下是安装 RedisBloom 的步骤(以 Linux 系统为例):

bash 复制代码
# 下载 RedisBloom 源码
git clone https://github.com/RedisBloom/RedisBloom.git
cd RedisBloom
# 编译
make
# 将编译好的模块加载到 Redis
redis-server --loadmodule /path/to/redisbloom.so

2. RedisBloom 提供的命令

RedisBloom 提供了以下常用命令:

  • BF.ADD key item:将单个元素添加到布隆过滤器。
  • BF.MADD key item1 item2 ...:将多个元素添加到布隆过滤器。
  • BF.EXISTS key item:检查元素是否存在。
  • BF.MEXISTS key item1 item2 ...:检查多个元素是否存在。
  • BF.RESERVE key error_rate capacity:创建一个指定误判率和容量的布隆过滤器。

3. 代码流程(以 Java 和 Jedis 为例)

以下是一个使用 Jedis 操作 Redis Bloom Filter 的示例:

java 复制代码
import redis.clients.jedis.Jedis;

public class BloomFilterExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        // 创建一个布隆过滤器,误判率为 0.01,预期容量为 10000
        jedis.sendCommand(() -> "BF.RESERVE".getBytes(), "mybloom", "0.01", "10000");

        // 添加元素
        jedis.sendCommand(() -> "BF.ADD".getBytes(), "mybloom", "apple");
        jedis.sendCommand(() -> "BF.ADD".getBytes(), "mybloom", "banana");

        // 检查元素是否存在
        Long existsApple = (Long) jedis.sendCommand(() -> "BF.EXISTS".getBytes(), "mybloom", "apple");
        Long existsOrange = (Long) jedis.sendCommand(() -> "BF.EXISTS".getBytes(), "mybloom", "orange");

        System.out.println("apple exists: " + (existsApple == 1));  // true
        System.out.println("orange exists: " + (existsOrange == 1)); // false

        jedis.close();
    }
}

4. RedisBloom 源码分析

RedisBloom 的核心实现位于 bloom.c 文件中,以下是部分关键逻辑的简化说明:

  • 初始化布隆过滤器

    c 复制代码
    BloomFilter *bf = createBloomFilter(error_rate, capacity);
    bf->bits = calloc(size, sizeof(uint8_t));  // 分配 bit array
    bf->hashes = k;  // 设置哈希函数数量
  • 添加元素

    c 复制代码
    void bloomAdd(BloomFilter *bf, const char *item) {
        for (int i = 0; i < bf->hashes; i++) {
            uint64_t hash = murmurhash(item, i);  // 使用 MurmurHash 计算哈希值
            uint64_t idx = hash % bf->size;
            setBit(bf->bits, idx);  // 设置对应位置为 1
        }
    }
  • 查询元素

    c 复制代码
    int bloomCheck(BloomFilter *bf, const char *item) {
        for (int i = 0; i < bf->hashes; i++) {
            uint64_t hash = murmurhash(item, i);
            uint64_t idx = hash % bf->size;
            if (!getBit(bf->bits, idx)) return 0;  // 有一个位为 0,则不在集合中
        }
        return 1;  // 所有位均为 1,可能存在
    }

完整源码可在 RedisBloom GitHub 查看。


三、为什么使用布谷鸟过滤器?

Bloom Filter 的不足(尤其是无法删除元素)限制了其在某些场景下的应用。这时,布谷鸟过滤器(Cuckoo Filter)成为一个更好的选择。

1. 布谷鸟过滤器的优势

  • 支持删除:通过存储指纹(fingerprint)而非简单 bit,可以安全删除元素。
  • 更高的空间效率:在低误判率下比 Bloom Filter 更节省空间。
  • 依然保持高效查询:查询复杂度为 O(1)。

2. 布谷鸟过滤器原理

  • 数据结构
    • 一个由多个桶(bucket)组成的哈希表,每个桶可以存储多个指纹。
    • 每个元素通过两个哈希函数映射到两个候选桶。
  • 添加元素
    1. 计算两个哈希值,确定两个候选桶。
    2. 如果任一桶有空位,则将元素的指纹存入。
    3. 如果两个桶都满,则随机踢出一个已有元素("布谷鸟"策略),重新插入被踢出的元素。
  • 删除元素
    1. 在两个候选桶中查找指纹。
    2. 如果找到匹配的指纹,直接删除。
  • 查询元素
    1. 检查两个候选桶中是否存在匹配的指纹。

3. Redis 中的布谷鸟过滤器实现

RedisBloom 同样支持布谷鸟过滤器(Cuckoo Filter),命令包括:

  • CF.ADD key item:添加元素。
  • CF.DEL key item:删除元素。
  • CF.EXISTS key item:检查元素是否存在。
  • CF.RESERVE key capacity:创建指定容量的布谷鸟过滤器。

4. 示例代码(Jedis)

java 复制代码
import redis.clients.jedis.Jedis;

public class CuckooFilterExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost", 6379);

        // 创建一个容量为 10000 的布谷鸟过滤器
        jedis.sendCommand(() -> "CF.RESERVE".getBytes(), "mycuckoo", "10000");

        // 添加元素
        jedis.sendCommand(() -> "CF.ADD".getBytes(), "mycuckoo", "apple");

        // 检查元素
        Long existsApple = (Long) jedis.sendCommand(() -> "CF.EXISTS".getBytes(), "mycuckoo", "apple");
        System.out.println("apple exists: " + (existsApple == 1));  // true

        // 删除元素
        jedis.sendCommand(() -> "CF.DEL".getBytes(), "mycuckoo", "apple");

        // 再次检查
        existsApple = (Long) jedis.sendCommand(() -> "CF.EXISTS".getBytes(), "mycuckoo", "apple");
        System.out.println("apple exists after delete: " + (existsApple == 1));  // false

        jedis.close();
    }
}

5. RedisBloom 中布谷鸟过滤器源码

源码位于 cuckoo.c 文件中,核心逻辑如下:

  • 插入元素

    c 复制代码
    int cuckooInsert(CuckooFilter *cf, const char *item) {
        uint64_t h1 = hash1(item);
        uint64_t h2 = hash2(item);
        uint8_t fingerprint = getFingerprint(item);
    
        if (insertToBucket(cf, h1, fingerprint) || insertToBucket(cf, h2, fingerprint)) {
            return 1;  // 插入成功
        }
        // 触发"布谷鸟"策略,递归插入
        return relocate(cf, h1, h2, fingerprint);
    }
  • 删除元素

    c 复制代码
    int cuckooDelete(CuckooFilter *cf, const char *item) {
        uint64_t h1 = hash1(item);
        uint64_t h2 = hash2(item);
        uint8_t fingerprint = getFingerprint(item);
    
        if (removeFromBucket(cf, h1, fingerprint) || removeFromBucket(cf, h2, fingerprint)) {
            return 1;  // 删除成功
        }
        return 0;
    }

四、总结与对比

  • Bloom Filter
    • 适合不需要删除的场景(如缓存穿透、URL 去重)。
    • 空间效率高,但误判率不可避免,且不支持删除。
  • Cuckoo Filter
    • 适合需要动态添加和删除的场景(如黑名单管理)。
    • 支持删除,误判率更低,但在实现上稍复杂。

在 Redis 中,二者都通过 RedisBloom 模块实现,源码开放且易于扩展。选择哪种过滤器取决于具体业务需求:若删除不是必需,Bloom Filter 更简单高效;若需要动态调整集合,Cuckoo Filter 是更好的选择。

相关推荐
bobz9657 分钟前
strongswan ipsec 支持多个 子网 cidr
后端
不修×蝙蝠8 分钟前
SpringBoot 第二课(Ⅰ) 整合springmvc(详解)
java·spring boot·后端·spring·整合springmvc
uhakadotcom8 分钟前
Prompt Flow 入门:简化 AI 应用开发流程
后端·面试·github
uhakadotcom14 分钟前
ONNX Runtime入门:高效深度学习推理框架
后端·面试·github
uhakadotcom30 分钟前
PyTorch FSDP:大规模深度学习模型的数据并行策略
后端·面试·github
程序员阿明43 分钟前
spring boot maven一栏引入本地包
spring boot·后端·maven
uhakadotcom1 小时前
Foreign Function Interface (FFI)入门:跨语言调用技术
后端·面试·github
uhakadotcom1 小时前
AI助力数据可视化:Data Formulator工具解析
后端·面试·github
co松柏1 小时前
到底 MCP 有什么魅力?10分钟让 AI 直接操作数据库!
人工智能·后端·程序员
Victor3561 小时前
Dubbo(7)Dubbo与Spring Cloud的区别是什么?
后端