Redis 命令及数据类型 -- CF(Cuckoo Filter)
摘要
- 本文介绍 Redis 扩展模型 RedisBloom 中的 Cuckoo Filter 数据类型
- 本文基于
redis-7.4.7,springboot-3.5.8 - Redis官网:https://redis.io/
- Redis 命令文档:https://redis.io/docs/latest/commands/
- RedisBloom 的安装参见 Redis 扩展模块 -- RedisBloom 的安装方法
- 示例代码:GitHub
Cuckoo Filter(布谷鸟过滤器)
-
Cuckoo Filter 是 Bloom Filter 的改进版,支持 动态添加和删除元素,仍能提供比布隆过滤器更高的查询性能。
-
在高 QPS 查询场景下,Cuckoo Filter 通常优于 Bloom Filter。
-
优点:低误判率 + 高负载率,基于相同的集合和误报率,Cuckoo Filter通常占用空间更少。相对的,算法实现也就更复杂。
-
缺点:与Bloom Filter一样,有可能将一个不在集合中的元素错误的判断成在集合中
-
Bloom Filter 的误报率通过调整
位数组的大小和哈希函数数量来控制,而 Cuckoo Filter 的误报率受指纹大小和桶大小控制。
布谷哈希算法
-
算法使用两个不同哈希函数计算对应 key 的位置。
-
- 当两个哈希任意位置为空,则随机选择一个位置插入
-
- 当两个哈希有位置为空时,则插入到空位置
-
- 当两个哈希位置均不为空时,随机选择一个位置插入并踢出原key,原key会再次经过计算获得新的位置,转至1执行,反复直到成功或者达到最大迭代次数。
-
Cuckoo Filter 的实现原理
-
布谷过滤器 是在
布谷哈希算法的算法基础上扩充来的,但是它所提供的概念不是表,而是提供了多个数据桶(Bucket) -
每一个数据桶都是个
一维数组,每个数组的保存内容为条目(Entry),每一个条目里面可以保存一个指纹数据(指纹数据就是原始数据经过哈希计算得到的一个n位的数据标记),除了指纹数据之外,还会同时得出一个保存位置P1标记。 -
当两个数据计算得到的指纹数据相同时,就会发生冲突,冲突操作的解决思路是使用
布谷哈希算法,简单说就是新的数据会将原有数据踢出,而被踢出的数据会被重新计算得到新的指纹数据和保存位置标记。 -
布谷过滤器里面需要进行各种数据的
踢出操作,这个踢出的方式就是使用P1标志位和指纹数据进行异或计算得出来的P2标志位,按照同样的思路(前提:一直都有冲突操作)一直计算新的P2位,直到冲突解决或者达到最大迭代次数,就会失败。 -
指纹数据里面包含有
唯一性,所以可以实现数据的删除,当然,不同的数据计算是有可能得到相同指纹的,那么一旦删除数据之后,有可能造成数据的"假删除",所以布谷过滤器本身也是存在有误差的。 -
Cuckoo Filter 中的
BUSKETSIZE- BUSKETSIZE,表示每个桶(Busket)中存放的元素个数,即桶大小。
- Cuckoo Filter的数组里存的不是位,而是桶(busket),每个桶里可以存放多个数据。
- 同一个桶中存放的数据越多,空间利用率更高,相应的误判率也就越高,性能也更慢。
- Redis的CuckooFilter实现中,BUSKETSIZE应该是一个在1到255之间的整数,默认的 BUSKETSIZE 是
2。 - 桶(Busket)中并不实际保存数据本身,而是保存数据的指纹(fingerprint)。指纹越小,HASH冲突造成误判的几率就越小。这个参数的调整比较复杂,Redis的CuckooFilter中不支持调整这个参数。
CF 命令说明
-
对应Redis命令:
CF.xxx
| 命令 | 功能说明 | 是否创建 Filter | 关键参数含义 | 返回值 | 示例 | 使用要点 / 备注 |
|---|---|---|---|---|---|---|
| CF.RESERVE | 显式创建 Cuckoo Filter | 是 | capacity:容量(必填)BUCKETSIZE:每个桶里最多能放多少个 fingerprint(指纹),默认 2(大多数情况下的最优解)MAXITERATIONS:重排次数,越大成功率越高EXPANSION:扩容倍数,默认 1(不扩容) |
OK | CF.RESERVE user:cf 100000 |
✅ 生产推荐 支持删除与计数 |
| CF.ADD | 添加一个元素,不去重 | 是 (不存在则创建) | item:元素 |
OK | CF.ADD user:cf user_1 |
若满可能失败 |
| CF.ADDNX | 元素不存在时才添加,去重 | 是 (不存在则创建) | item |
1 新增0 已存在 |
CF.ADDNX user:cf user_1 |
幂等写入首选 |
| CF.INSERT | 批量插入 | 是(不存在则创建) | ITEMS:元素列表 |
OK | CF.INSERT user:cf ITEMS u1 u2 |
默认配置 |
| CF.INSERTNX | 批量插入(不存在才加) | 是 | ITEMS |
0/1 列表 |
CF.INSERTNX user:cf ITEMS u1 u2 |
幂等 + 批量 |
| CF.EXISTS | 判断单个元素是否存在 | 否 | item |
1 可能存在0 不存在 |
CF.EXISTS user:cf user_1 |
仍有误判 |
| CF.MEXISTS | 批量判断是否存在 | 否 | item... |
0/1 列表 |
CF.MEXISTS user:cf u1 u9 |
高并发推荐 |
| CF.COUNT | 返回元素出现次数 | 否 | item |
整数 | CF.COUNT user:cf user_1 |
⭐ Bloom 没有的能力 |
| CF.DEL | 删除一个元素 | 否 | item |
1 删除成功0 不存在 |
CF.DEL user:cf user_1 |
⭐ Bloom 不支持 |
| CF.INFO | 查看 Filter 元信息 | 否 | 无 | KV 列表 | CF.INFO user:cf |
运维分析 |
| CF.SCANDUMP | 分块导出 Filter | 否 | iterator |
iterator + data |
CF.SCANDUMP user:cf 0 |
迁移 / 备份 |
| CF.LOADCHUNK | 从 dump 恢复 Filter | 是 | iterator + data |
OK | CF.LOADCHUNK user:cf 1 "xxx" |
与 SCANDUMP 配合 |
Bloom vs Cuckoo
| 维度 | Bloom Filter | Cuckoo Filter |
|---|---|---|
| 查询复杂度 | O(k)(k 个哈希函数) | O(1)(2–4 次 bucket 访问) |
| 插入复杂度 | O(k) | 平均 O(1),最坏可能触发重排 |
| 删除支持 | ❌ 原生不支持 | ✅ 原生支持 |
| 误判率(False Positive) | 可配置,稳定 | 可配置,通常更低 |
| 漏判(False Negative) | ❌ 理论上不会 | ❌ 理论上不会 |
| 空间利用率 | 高(但受 k 影响) | 通常更高(特别是低误判率) |
| 扩容成本 | 高(需重建) | 中等(支持扩展策略) |
| 实现复杂度 | 低 | 较高 |
RedisBloom Cuckoo Filter 不支持设置
误判率,通常 容量 越大,误判率越低。
CF 命令示例
1 | # 创建 Cuckoo Filter |
SpringBoot 集成
-
SpringBoot 的 RedisTemplate 中没有提供对
RedisBloom的封装,需要自己封装,我这里封装了一个简易的RedisCuckooFilterTool
1 | package com.example.redisbloom; |