hyperloglog实战 - 你的过滤器选对了吗?

2024-03-11 08:58

本文主要是介绍hyperloglog实战 - 你的过滤器选对了吗?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、业务背景

最近接到一个统计需求,为了监控视频效果,我们每次浏览视频都要记录uv。当量小的时候,没有问题,随着用户量的的增长,占用空间大的问题开始暴露。假如有1000万用户,采用set结构记录UV,我们只存储int类型的用户id,1000万*4byte/1024kb/1024mb=38.14G,这是一个视频一天的数据,很明显存储成本太高,必须找到一种既能满足需求,又很少占用空间的方案。

二、技术选型

经过调研,找到了以下几种技术方案,下面一一分析。

1、flink的state去重

MapState 是 Flink 中 KeyedState 的状态类型,这种方式实现去重是一个精确的去重结果,将设备 ID 保存在 MapState 中。而state 直接存放在 RocksDB 中,RocksDB是类似HBase的kv数据库,本地性能好,维护较大的状态集合问题不大。

这种方案的优点是数据准确定非常高,确定是随着state增长,对checkpoint效率和成功率都有挑战,而且需要定期清理state,无发做到长时间数据去重。

2、bitmap去重

bitmap的原理是使用一个bit来存放某种状态,适用于大规模数据,但是状态又不是很多的情况。假设使用redis的bitmap,首先将用户id通过算法转换为整数,这个整数就是偏移量,每个id占用一位,1是访问过。

这个方案的优点是占用内存小,查询方便,效率高,能够精确去重,缺点是需要计算偏移量,如果偏移量过大就占用很多空间。

3、布隆过滤器

布隆过滤器是由一个很长的二进制向量和一系列随机映射函数。它适合过滤不存的元素场景,如果判断不存在则一定不存在,如果判断存在,则有一定概率不存在。计算puv时,用一个变量计数,用这个过滤器判断计数器是否增加。

这个方案的优点是空间效率高,查询时间短。缺点是有误判率,多个维度时,初始分配的空间一样,会占用较大空间。

4、hyperloglog

hyperloglog是一种算法,目的是做基数统计,可以预估一个集合中不同数据的个数,它不会保存元数据,只记录数量,支持输入非常大体积的数据量。在 redis 中也存在 hyperloglog 类型的结构,能够使用 12k 的内存,允许误差在 0.81%的情况下统计 2^64 个数据。

这个方案的有点事占用空间小,空间不会随着数量的变大而无线变大。缺点是有0.81%的标准误差率,只能用来计数,不能用来查询元素。

综合上面的方案,对于1000万用户,统计上亿视频,允许一定误差率的场景,方案 4 更加合适,内存能够控制在12k以内,查询速度也能满足需求。

三、hyperloglog 原理分析

1、原理说明

HyperLogLog 算法来源于论文 HyperLogLog the analysis of a near-optimal cardinality estimation algorithm,想要了解 HLL 的原理,先要从伯努利试验说起,伯努利实验说的是抛硬币的事。一次伯努利实验相当于抛硬币,不管抛多少次只要出现一个正面,就称为一次伯努利实验。

我们用 k 来表示每次抛硬币的次数,n 表示第几次抛的硬币,用 k_max 来表示抛硬币的最高次数,最终根据估算发现 n 和 k_max 存在的关系是 n=2^(k_max),但同时我们也发现了另一个问题当试验次数很小的时候,这种估算方法的误差会很大,例如我们进行以下 3 次实验:

  • 第 1 次试验:抛 3 次出现正面,此时 k=3,n=1;
  • 第 2 次试验:抛 2 次出现正面,此时 k=2,n=2;
  • 第 3 次试验:抛 6 次出现正面,此时 k=6,n=3。

对于这三组实验来说,k_max=6,n=3,但放入估算公式明显 3≠2^6。为了解决这个问题 HLL 引入了分桶算法和调和平均数来使这个算法更接近真实情况。

分桶算法是指把原来的数据平均分为 m 份,在每段中求平均数在乘以 m,以此来消减因偶然性带来的误差,提高预估的准确性,简单来说就是把一份数据分为多份,把一轮计算,分为多轮计算。

而调和平均数指的是使用平均数的优化算法,而非直接使用平均数。

例如小明的月工资是 1000 元,而小王的月工资是 100000 元,如果直接取平均数,那小明的平均工资就变成了 (1000+100000)/2=50500‬ 元,这显然是不准确的,而使用调和平均数算法计算的结果是 2/(1/1000+1/100000)≈1998 元,显然此算法更符合实际平均数。

2、具体的计算流程

这是hyperloglog的在线计算网站: http://content.research.neustar.biz/blog/hll.html

hy.png

  • 第一步对输入的值3,157,369进行hash获得值2,297,270,962,hash的值转二进制 11100000101001010101001011101110100111010
  • 第二步二进制数的最后6位设置为桶的index(长16,宽4的矩形),为什么是6位呢?因为2的6次方正好等于矩阵数组的个数64,从图中看出index是50,红色的方块是要放入的位置
  • 第三步获取从右向左的第一个1,如图看到是10,也就是2
  • 最后一步多个不同的值,就被分散到不同的桶中去了,且每个桶有其 k_max。然后当要统计出总共有多少的时候,就是一次估算。最终结合所有桶中的 k_max,代入估算公式,便能得出估算值。

四、hyperloglog redis实战

在 Redis 的 HyperLogLog 实现中用到的是 16384 个桶,也就是 2^14,每个桶的 maxbits 需要 6 个 bits 来存储,最大可以表示 maxbits=63,于是总共占用内存就是2^14 * 6 / 8 = 12k字节。\

HyperLogLog 提供了两个指令 pfadd 和 pfcount,根据字面意义很好理解,一个是增加计数,一个是获取计数。pfadd 用法和 set 集合的 sadd 是一样的,来一个用户 ID,就将用户 ID 塞进去就是。pfcount 和 scard 用法是一样的,直接获取计数值。

127.0.0.1:6379> PFADD runoobkey "redis"(integer) 1127.0.0.1:6379> PFADD runoobkey "mongodb"(integer) 1127.0.0.1:6379> PFADD runoobkey "mysql"(integer) 1127.0.0.1:6379> PFCOUNT runoobkey

我们将数据增加到 10w 个,看看总量差距有多大。

public class Test {public static void main(String[] args) {//连接本地的redisJedis jedis = new Jedis("localhost");System.out.println(jedis.getClient().getPort());System.out.println("连接本地的Redis服务器成功");for (int i = 0; i < 100000; i++) {jedis.pfadd("my-user-uv", "user" + i);}long total = jedis.pfcount("my-user-uv");System.out.printf("%d %d\n", 100000, total);jedis.close();}}

差了 275 个,按百分比是 0.275%,对于上面的 UV 统计需求来说,误差率也不算高。然后我们把上面的脚本再跑一边,也就相当于将数据重复加入一边,查看输出,可以发现,pfcount 的结果没有任何改变,还是 99725,说明它确实具备去重功能。

参考资料

  • 优秀的基数统计算
  • Redis HyperLoglog、Bloom Filter 、Bitmap

(转自:https://juejin.cn/post/6992890603503632415)

这篇关于hyperloglog实战 - 你的过滤器选对了吗?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/797334

相关文章

Python与DeepSeek的深度融合实战

《Python与DeepSeek的深度融合实战》Python作为最受欢迎的编程语言之一,以其简洁易读的语法、丰富的库和广泛的应用场景,成为了无数开发者的首选,而DeepSeek,作为人工智能领域的新星... 目录一、python与DeepSeek的结合优势二、模型训练1. 数据准备2. 模型架构与参数设置3

Java实战之利用POI生成Excel图表

《Java实战之利用POI生成Excel图表》ApachePOI是Java生态中处理Office文档的核心工具,这篇文章主要为大家详细介绍了如何在Excel中创建折线图,柱状图,饼图等常见图表,需要的... 目录一、环境配置与依赖管理二、数据源准备与工作表构建三、图表生成核心步骤1. 折线图(Line Ch

Java 8 Stream filter流式过滤器详解

《Java8Streamfilter流式过滤器详解》本文介绍了Java8的StreamAPI中的filter方法,展示了如何使用lambda表达式根据条件过滤流式数据,通过实际代码示例,展示了f... 目录引言 一.Java 8 Stream 的过滤器(filter)二.Java 8 的 filter、fi

Java使用Tesseract-OCR实战教程

《Java使用Tesseract-OCR实战教程》本文介绍了如何在Java中使用Tesseract-OCR进行文本提取,包括Tesseract-OCR的安装、中文训练库的配置、依赖库的引入以及具体的代... 目录Java使用Tesseract-OCRTesseract-OCR安装配置中文训练库引入依赖代码实

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程

《在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程》本文介绍了在Java中使用ModelMapper库简化Shapefile属性转JavaBean的过程,对比... 目录前言一、原始的处理办法1、使用Set方法来转换2、使用构造方法转换二、基于ModelMapper

Java实战之自助进行多张图片合成拼接

《Java实战之自助进行多张图片合成拼接》在当今数字化时代,图像处理技术在各个领域都发挥着至关重要的作用,本文为大家详细介绍了如何使用Java实现多张图片合成拼接,需要的可以了解下... 目录前言一、图片合成需求描述二、图片合成设计与实现1、编程语言2、基础数据准备3、图片合成流程4、图片合成实现三、总结前

nginx-rtmp-module构建流媒体直播服务器实战指南

《nginx-rtmp-module构建流媒体直播服务器实战指南》本文主要介绍了nginx-rtmp-module构建流媒体直播服务器实战指南,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有... 目录1. RTMP协议介绍与应用RTMP协议的原理RTMP协议的应用RTMP与现代流媒体技术的关系2

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck