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在高并发的场景中,热点数据一直是我们需要关注的问题。如何去衡量热点数据是关键。这篇文 - 掘金
相关推荐
Y3815326626 小时前
SERP API + Redis 缓存层:4 种方案对比与选型
数据库·redis·缓存
海兰8 小时前
【高速缓存】RedisVL 高级查询(全文搜索、混合搜索和 多向量搜索)
数据库·人工智能·redis·缓存
记录者t10 小时前
redis常见问题:大key
数据库·redis
青石路10 小时前
如果写Redis序列化与读Redis序列化不一致,你觉得会发生什么
java·redis
難釋懷11 小时前
Nginx代理https请求
redis·nginx·https
流星白龙20 小时前
【Redis】2.Redis重大版本
数据库·redis·junit
流星白龙20 小时前
【Redis】7.Hash表
数据库·redis·哈希算法
流星白龙21 小时前
【Redis】4.基本全局命令
数据库·redis·缓存