Redis 概率相关的数据类型

Redis 概率相关的数据类型

    • HyperLogLog
    • [Bloom filter 布隆过滤器](#Bloom filter 布隆过滤器)
    • [Cuckoo filter 布谷鸟过滤器](#Cuckoo filter 布谷鸟过滤器)
    • [Count-min sketch](#Count-min sketch)
    • TOPK
    • t-digest
    • 参考

HyperLogLog

估算集合中唯一值的个数,不精确的去重计数,标准误差为 0.81%,最多占用 12 KB 的空间。

原理 :基于伯努利实验,抛硬币想要连续 K 次均为反面,则需要尝试 N 次实验。N 和 K 的关系为 N = 2K。HyperLogLog 将值 hash 为数字,记录最低位连续 0 的最大数量作为 K,进而估计出唯一值的个数 N。为了避免极端值的影响,使用了 214 = 16384 个桶,然后取调和平均数。

一共 16384 个桶,每个桶用 6bit 记录最大连续数,空间为 16384 * 6 / 8 = 12288byte = 12kb。

Bloom filter 布隆过滤器

判断某个元素是否存在于某个集合中,可能会误判。如果返回不存在则值一定不存在,如果返回存在则值可能存在也可能不存在。

添加的原理:对值使用 N 个 hash 函数得到 N 个 hash 值,hash 值对 bitmap 长度取模后,将 bitmap 的 N 个位置设置为 1。

判断是否存在的原理:使用同样的方式 hash 取模后,判断 bitmap 的 N 个位置,如果有位置是 0,则肯定不存在,如果都为 1 则可能存在。

创建 Bloom filter 时可以指定误报率、预期容量、扩展因子。当容量达到上限时,会自动创建子 Bloom filter,容量 = 当前预期容量 * 扩展因子。子过滤器会增加查询时的延迟,因为若一个子过滤器返回不存在,会继续检查下一个过滤器。

Cuckoo filter 布谷鸟过滤器

与 Bloom filter 一样,用于判断某个元素是否存在于某个集合中,也可能会误判。如果返回不存在则值一定不存在,如果返回存在则值可能存在也可能不存在。

Bloom filter 不支持删除,想要删除元素必须重建。而 Cuckoo filter 支持删除。

插入原理

使用两个 hash 函数,所以每个元素有两个候选位置。不存元素 x,而是存元素的指纹。数组的一个位置中会存储多个指纹。

复制代码
f = fingerprint(x)
i1 = hash(x)
i2 = i1 xor hash(f)

fingerprint() 函数用于对元素 x 生成指纹 f。

直接对 x hash 生成数组索引 i1。

对指纹 f hash 后与 i1 异或得到数组索引 i2。由于异或的特性,也可以通过 i1 = i2 xor hash(f) 得到 i1。

这里我不明白为什么直接能得到数组索引,不是应该与数组长度取模后才能得到吗?

  1. 如果 i1、i2 两个位置都是空的,随机选择一个将 fingerprint 写入。

  2. 如果只有一个位置是空的,将 fingerprint 写入这个位置。

  3. 如果两个位置都不为空,则随机选择一个位置将其踢出,然后将 fingerprint 写入。

  4. 被踢出的元素通过 i xor hash(f) 得到另一个候选位置

    • 如果为空,则写入 fingerprint

    • 如果不为空,则将其踢出,然后将 fingerprint 写入。然后重复步骤 4 直到找到空位,或者到达最大踢出次数后报错。

判断是否存在原理

查看 i1、i2 中是否有与 f 相同的指纹,有则存在,没有则不存在。

删除原理

找到 i1、i2 中与 f 相同的指纹,删除一个。

删除之前没有添加的项目可能会破坏过滤器并导致误报。

Count-min sketch

估计集合中某个元素出现的频率。可能高估,但绝不会低估。

更新的原理 :有 w * d 的二维数组,且有 d 个 hash 函数,执行 update(element, count) 时,使用 d 个 hash 函数对 element 进行 hash 得到 d 个 hash 值,然后分别对 w 取模,得到 d 个数组下标 index,然后将 d 个数组对应 index 位置的值累加上 count。

复制代码
index_1 = hash_1(element) % w
index_2 = hash_2(element) % w
...
index_d = hash_d(element) % w

arr_1[index_1]+=count
arr_2[index_2]+=count
...
arr_d[index_d]+=count

查询频率的原理:按照上述步骤得到 d 个数组下标后,分别获取数组对应位置的值后再取最小值。

复制代码
index_1 = hash_1(element) % w
index_2 = hash_2(element) % w
...
index_d = hash_d(element) % w

res = min(arr_1[index_1], arr_2[index_2],..., arr_d[index_d])

取最小值的原因:多个不同的元素可能发生 hash 碰撞,导致该位置的计数是多个元素的总和。一个元素在 d 个数组中计数的最小值是最接近真实值的一个。

TOPK

估算数据流中出现频率最高的 K 个元素。

原理

基于 HeavyKeepers 算法,使用了最小堆和指数衰减计数策略。由一个最小堆和二维数组构成,最小堆负责实时维护 topk;二维数组负责统计元素出现的频率,与 Count-min sketch(简称 CMS) 的操作类似,区别在于 CMS 只存储了 count,而 TOPK 同时存储了元素的 fingerprint 和 count。在添加元素时 CMS 是直接在 count 上累加,而 TOPK 是有条件的。

  • 如果槽为空,则直接写入 fingerprint、count=1
  • 如果新元素的 fingerprint 与槽中的 fingerprint 相等,则 count++
  • 如果 fingerprint 不一致,说明发生了哈希冲突。根据 P = b−count (b>1, 通常取 1.08) 计算出衰减概率 P,然后生成一个 (0, 1) 的随机数 R。
    • 如果 R <= P,则槽中的 count--,如果此时 count> 0 则 fingerprint 不变,否则将 fingerprint 更新为新元素的 fingerprint,count 设为 1。
    • 如果 R > P,则槽保持不变。

衰减概率 P 是根据 count 计算的,count 越大 P 就越小。频率低的元素很快就衰减到 0,而频率高的元素则不容易衰减。

t-digest

估算数据流的百分位数,比如估算指定百分位的值,估算某个值所处的百分位,估算指定排名的值,估算某个值的排名,计算修剪平均数(去除数据两端特定比例的极端值后计算剩余数据的均值)。

参考

  1. Probabilistic | Docs
  2. cuckoo-conext2014.pdf
  3. 热点数据检测 HeavyKeeper在高并发的场景中,热点数据一直是我们需要关注的问题。如何去衡量热点数据是关键。这篇文 - 掘金
相关推荐
IT大白鼠3 天前
Redis 系列 · 第 01 篇——认知入门:Redis 是什么
redis·nosql
彧azz3 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
lbb 小魔仙3 天前
OpenClaw + cpolar 实战:远程 NAS、分享小游戏、RDP,再配置公网 AI 入口
数据库·人工智能·redis·oracle·prometheus
夕除3 天前
redis--018
redis
两锭子3 天前
Redis基础
redis
烟沙九洲3 天前
Redis 缓存穿透、缓存击穿、缓存雪崩
数据库·redis
字节探索3 天前
Redis 只会当缓存用?这 10 大实战场景,让你的系统快到飞起
redis·后端
于樱花森上飞舞3 天前
【Redis】哨兵详解
java·开发语言·数据库·redis
Rain的Java大神之路4 天前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
Wang's Blog4 天前
Java 接入Redis: 通用命令与键管理
java·服务器·redis