Redis高级数据结构HyperLogLog

news2024/11/25 4:21:11

HyperLogLog(Hyper[ˈhaɪpə(r)])并不是一种新的数据结构(实际类型为字符串类型),而是一种基数算法,通过HyperLogLog可以利用极小的内存空间完成独立总数的统计,数据集可以是IP、Email、ID等。

如果你负责开发维护一个大型的网站,有一天产品经理要网站每个网页每天的 UV 数据,然后让你来开发这个统计模块,你会如何实现?

如果统计 PV 那非常好办,给每个网页一个独立的 Redis 计数器就可以了,这个计数器的 key 后缀加上当天的日期。这样来一个请求,incrby 一次,最终就可以统计出所有的 PV 数据。

但是 UV 不一样,它要去重,同一个用户一天之内的多次访问请求只能计数一次。这就要求每一个网页请求都需要带上用户的 ID,无论是登陆用户还是未登陆用户都需要一个唯一 ID 来标识。

一个简单的方案,那就是为每一个页面一个独立的 set 集合来存储所有当天访问过此页面的用户 ID。当一个请求过来时,我们使用 sadd 将用户 ID 塞进去就可以了。通过 scard 可以取出这个集合的大小,这个数字就是这个页面的 UV 数据。

但是,如果你的页面访问量非常大,比如一个爆款页面几千万的 UV,你需要一个很大的 set 集合来统计,这就非常浪费空间。如果这样的页面很多,那所需要的存储空间是惊人的。为这样一个去重功能就耗费这样多的存储空间,值得么?其实需要的数据又不需要太精确,1050w 和 1060w 这两个数字对于老板们来说并没有多大区别,So,有没有更好的解决方案呢?

这就是HyperLogLog 的用武之地,Redis 提供了 HyperLogLog 数据结构就是用来解决这种统计问题的。HyperLogLog 提供不精确的去重计数方案,虽然不精确但是也不是非常不精确,Redis官方给出标准误差是 0.81%,这样的精确度已经可以满足上面的 UV 统计需求了。

操作命令

HyperLogLog提供了3个命令: pfadd、pfcount、pfmerge。

例如08-15的访问用户是u1、u2、u3、u4,08-16的访问用户是u-4、u-5、u-6、u-7

pfadd

pfadd key element [element …]

pfadd用于向HyperLogLog 添加元素,如果添加成功返回1:

pfadd 08-15:u:id "u1" "u2" "u3" "u4"

pfcount

pfcount key [key …]

pfcount用于计算一个或多个HyperLogLog的独立总数,例如08-15:u:id的独立总数为4:

pfcount 08-15:u:id

如果此时向插入u1、u2、u3、u90,结果是5:

pfadd 08-15:u:id "u1" "u2" "u3" "u90"

pfcount 08-15:u:id

如果我们继续往里面插入数据,比如插入100万条用户记录。内存增加非常少,但是pfcount 的统计结果会出现误差。

以使用集合类型和 HperLogLog统计百万级用户访问次数的占用空间对比:

数据类型 1天 1个月 1年

集合类型 80M 2.4G 28G

HyperLogLog 15k 450k 5M

可以看到,HyperLogLog内存占用量小得惊人,但是用如此小空间来估算如此巨大的数据,必然不是100%的正确,其中一定存在误差率。前面说过,Redis官方给出的数字是0.81%的失误率。

pfmerge

pfmerge destkey sourcekey [sourcekey ... ]

pfmerge可以求出多个HyperLogLog的并集并赋值给destkey,请自行测试。

原理概述

基本原理

HyperLogLog基于概率论中伯努利试验并结合了极大似然估算方法,并做了分桶优化。

实际上目前还没有发现更好的在大数据场景中准确计算基数的高效算法,因此在不追求绝对准确的情况下,使用概率算法算是一个不错的解决方案。概率算法不直接存储数据集合本身,通过一定的概率统计方法预估值,这种方法可以大大节省内存,同时保证误差控制在一定范围内。目前用于基数计数的概率算法包括:

Linear Counting(LC):早期的基数估计算法,LC在空间复杂度方面并不算优秀;

LogLog Counting(LLC):LogLog Counting相比于LC更加节省内存,空间复杂度更低;

HyperLogLog Counting(HLL):HyperLogLog Counting是基于LLC的优化和改进,在同样空间复杂度情况下,能够比LLC的基数估计误差更小。

举个例子来理解HyperLogLog 算法,有一天Fox老师和Mark老师玩抛硬币的游戏,规则是Mark老师负责抛硬币,每次抛的硬币可能正面,可能反面,每当抛到正面为一回合,Mark老师可以自己决定进行几个回合。最后需要告诉Fox老师最长的那个回合抛了多少次以后出现了正面,再由Fox老师来猜Mark老师一共进行了几个回合。

 

进行了n次,比如上图:

第一次: 抛了3次才出现正面,此时 k=3,n=1

第二次试验: 抛了2次才出现正面,此时 k=2,n=2

第三次试验: 抛了4次才出现正面,此时 k=4,n=3

…………

第n 次试验:抛了7次才出现正面,此时我们估算,k=7,n=n

k是每回合抛到1(硬币的正面)所用的次数,我们已知的是最大的k值,也就是Mark老师告诉Fox老师的数,可以用k_max表示。由于每次抛硬币的结果只有0和1两种情况,因此,能够推测出k_max在任意回合出现的概率 ,并由kmax结合极大似然估算的方法推测出n的次数n = 2^(k_max) 。概率学把这种问题叫做伯努利实验。

现在Mark老师已经完成了n个回合,并且告诉Fox老师最长的一次抛了4次,Fox老师此时也胸有成竹,马上说出他的答案16,最后的结果是:Mark老师只抛了3回合,

这三个回合中k_max=4,放到公式中,Fox老师算出n=2^4,于是推测Mark老师抛了16个回合,但是Fox老师输了,要负责买奶茶一个星期。

所以这种预估方法存在较大误差,为了改善误差情况,HLL中引入分桶平均的概念。

同样举抛硬币的例子,如果只有一组抛硬币实验,显然根据公式推导得到的实验次数的估计误差较大;如果100个组同时进行抛硬币实验,样本数变大,受运气影响的概率就很低了,每组分别进行多次抛硬币实验,并上报各自实验过程中抛到正面的抛掷次数的最大值,就能根据100组的平均值预估整体的实验次数了。

分桶平均的基本原理是将统计数据划分为m个桶,每个桶分别统计各自的​k_max,​ 并能得到各自的基数预估值,最终对这些基数预估值求平均得到整体的基数估计值。LLC中使用几何平均数预估整体的基数值,但是当统计数据量较小时误差较大;HLL在LLC基础上做了改进,采用调和平均数过滤掉不健康的统计值。

什么叫调和平均数呢?举个例子

求平均工资:A的是1000/月,B的30000/月。采用平均数的方式就是: (1000 + 30000) / 2 = 15500

采用调和平均数的方式就是: 2/(1/1000 + 1/30000) ≈ 1935.484

可见调和平均数比平均数的好处就是不容易受到大的数值的影响,比平均数的效果是要更好的。

结合实例理解原理

现在我们和前面的业务场景进行挂钩:统计网页每天的 UV 数据。

1.转为比特串

通过hash函数,将数据转为比特串,例如输入5,便转为:101,字符串也是一样。为什么要这样转化呢?

是因为要和抛硬币对应上,比特串中,0 代表了反面,1 代表了正面,如果一个数据最终被转化了 10010000,那么从右往左,从低位往高位看,我们可以认为,首次出现 1 的时候,就是正面。

那么基于上面的估算结论,我们可以通过多次抛硬币实验的最大抛到正面的次数来预估总共进行了多少次实验,同样也就可以根据存入数据中,转化后的出现了 1 的最大的位置 k_max 来估算存入了多少数据。

2.分桶

分桶就是分多少轮。抽象到计算机存储中去,存储的是一个长度为 L 的位(bit)大数组 S ,将 S 平均分为 m 组,这个 m 组,就是对应多少轮,然后每组所占有的比特个数是平均的,设为 P。容易得出下面的关系:

L = S.length

L = m * p

以 K 为单位,S 占用的内存 = L / 8 / 1024

3、对应

假设访问用户 id 为:idn , n->0,1,2,3....

在这个统计问题中,不同的用户 id 标识了一个用户,那么我们可以把用户的 id 作为被hash的输入。即:

hash(id) = 比特串

不同的用户 id,拥有不同的比特串。每一个比特串,也必然会至少出现一次 1 的位置。我们类比每一个比特串为一次伯努利试验。

现在要分轮,也就是分桶。所以我们可以设定,每个比特串的前多少位转为10进制后,其值就对应于所在桶的标号。假设比特串的低两位用来计算桶下标志,总共有4个桶,此时有一个用户的id的比特串是:1001011000011。它的所在桶下标为:1*2^1 + 1*2^0 = 3,处于第3个桶,即第3轮中。

上面例子中,计算出桶号后,剩下的比特串是:10010110000,从低位到高位看,第一次出现 1 的位置是 5 。也就是说,此时第3个桶中,k_max = 5。5 对应的二进制是:101,将 101 存入第3个桶。

模仿上面的流程,多个不同的用户 id,就被分散到不同的桶中去了,且每个桶有其 k_max。然后当要统计出某个页面有多少用户点击量的时候,就是一次估算。最终结合所有桶中的 k_max,代入估算公式,便能得出估算值。

Redis 中的 HyperLogLog 实现

Redis的实现中,HyperLogLog 占据12KB(占用内存为=16834 * 6 / 8 / 1024 = 12K)的大小,共设有 16384 个桶,即:2^14 = 16384,每个桶有 6 位,每个桶可以表达的最大数字是:25+24+...+1 = 63 ,二进制为: 111 111 。

对于命令:pfadd key value

在存入时,value 会被 hash 成 64 位,即 64 bit 的比特字符串,前 14 位用来分桶,剩下50位用来记录第一个1出现的位置。

之所以选 14位 来表达桶编号是因为分了 16384 个桶,而 2^14 = 16384,刚好地,最大的时候可以把桶利用完,不造成浪费。假设一个字符串的前 14 位是:00 0000 0000 0010 (从右往左看) ,其十进制值为 2。那么 value 对应转化后的值放到编号为 2 的桶。

index 的转化规则:

首先因为完整的 value 比特字符串是 64 位形式,减去 14 后,剩下 50 位,假设极端情况,出现 1 的位置,是在第 50 位,即位置是 50。此时 index = 50。此时先将 index 转为 2 进制,它是:110010 。

因为16384 个桶中,每个桶是 6 bit 组成的。于是 110010 就被设置到了第 2 号桶中去了。请注意,50 已经是最坏的情况,且它都被容纳进去了。那么其他的不用想也肯定能被容纳进去。

因为 fpadd 的 key 可以设置多个 value。例如下面的例子:

pfadd lgh golang

pfadd lgh python

pfadd lgh java

根据上面的做法,不同的 value,会被设置到不同桶中去,如果出现了在同一个桶的,即前 14 位值是一样的,但是后面出现 1 的位置不一样。那么比较原来的 index 是否比新 index 大。是,则替换。否,则不变。

最终地,一个 key 所对应的 16384 个桶都设置了很多的 value 了,每个桶有一个k_max。此时调用 pfcount 时,按照调和平均数进行估算,同时加以偏差修正,便可以计算出 key 的设置了多少次 value,也就是统计值,具体的估算公式如下:

 

value 被转为 64 位的比特串,最终被按照上面的做法记录到每个桶中去。64 位转为十进制就是:2^64,HyperLogLog 仅用了:16384 * 6 /8 / 1024 =12K 存储空间就能统计多达 2^64 个数。

同时,在具体的算法实现上,HLL还有一个分阶段偏差修正算法。我们就不做更深入的了解了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/518532.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pytorch移植华为mindspore记录

因为某个需求,需要把原来pytorch的神经网络移植到华为的mindspore上 这边记录下遇到的坑 附上mindspore的官方教程: https://mindspore.cn/tutorials/zh-CN/r2.0/advanced/compute_graph.html 这边附上需要移植的网络,以tensorflow和pytorch…

LeetCode 链表OJ分享

目录 删除排序链表中的重复元素回文链表剑指Offer 06.从尾到头打印链表复制带随机指针的链表 删除排序链表中的重复元素 链接: link 题目描述: 题目思路: 本题思路使用双指针,以示例二为例如下图: 如果head->val等于next-&…

ihateniggers:针对Python开发者的Windows远控木马分析

背景 墨菲安全实验室在持续监测开源软件仓库中的投毒行为,5 月 9 日起发现 4 个包含 “ihateniggers” 远程控制木马的 Python 包被 nagogygmail.com 邮箱关联的账号发布到 PyPI 仓库,试图针对Windows系统下 Python 开发者进行攻击。木马利用了discord、…

各种顺序表和链表的实现代码

目录 一、什么是线性表 二、顺序表 2.1什么是顺序表 2.2静态顺序表的代码实现 2.3动态顺序表的代码实现 三、链表 3.1什么是链表 3.2不带头单向不循环链表的代码实现 3.3带头双向循环链表的代码实现 四、顺序表和链表的区别 一、什么是线性表 线性表是n个具有相同特性…

(十五)数据编辑——图形编辑①

数据编辑——图形编辑① 数据编辑包括几何数据和属性数据的编辑。几何数据的编辑主要是针对图形的操作,即图形编辑,包括平行线复制、缓冲区生成、镜面反射、图层合并、结点操作、拓扑编辑等。属性编辑主要包括图层要素属性的添加、删除、修改、复制、粘…

谷歌落子,我们对中国大模型的期待应该是什么?

对中国大模型厂商而言,市场期待的,也恰是这些真正可落地的应用和实践。这些实践可以在社交,在电商,在低代码,在供应链,也更可以在一个个中国产业数字化转型的新洼地。 作者|思杭 皮爷 出品|产业家 在微软G…

裸辞5个月,面试了37家公司,终于.....

上半年裁员,下半年裸辞,有不少人高呼裸辞后躺平真的好快乐!但也有很多人,裸辞后的生活五味杂陈。 面试37次终于找到心仪工作 因为工作压力大、领导PUA等各种原因,今年2月下旬我从一家互联网小厂裸辞,没想…

执行增删改查时的结果处理

查询最终走到PreparedStatementHandler类的query方法,执行查询后调用DefaultResultSetHandler类的handleResultSets方法 1.处理返回的普通实体类 DefaultResultSetHandler类的handleResultSets方法 继续本类的handleResultSet方法 通过 handleRowValues 方法来…

设计一个可靠的自动化测试框架需要考虑哪些问题呢?

随着软件开发的日益普及,自动化测试框架逐渐成为了保障软件质量的必备工具。然而,如何设计一个可靠的自动化测试框架并不是一件简单的事情,需要考虑多方面的问题。本文将从需求分析、架构设计、测试用例编写等多个角度,介绍设计一…

【SpringMVC】| SpringMVC拦截器

目录 一:SpringMVC拦截器 1. 拦截器介绍 2. HandlerInterceptor接口分析 3. 自定义拦截器实现权限验证 一:SpringMVC拦截器 SpringMVC 中的 Interceptor 拦截器,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理…

C++面向对象(黑马程序员)

内存分区模型 #include<iostream> using namespace std;//栈区数据注意事项&#xff1a;不要返回局部变量的地址 //栈区的数据由编译器管理开辟和释放int* func(int b) //形参数据也会放在栈区 {b 100;int a 10; //局部变量存放在栈区&#xff0c;栈区的数据在函数执…

Qt创建项目 No suitable kits found.

最近因为要写.net6项目&#xff0c;而更新了Visual Studio&#xff0c;后来再使用Qt的时候发现不能编译了。 调整后&#xff0c;发现新建项目显示“No suitable kits found”错误。 网上也看了很多的解决方案&#xff0c;但都不得要领&#xff0c;也都没有解决。 ----------…

Java-Redis持久化之AOF操作

Java-Redis持久化之AOF操作 1.AOF(append only file)2.请你用自己的话说一说 AOF的过程?3.配置如何开启AOF操作4.AOF的优点和缺点: 1.AOF(append only file) 将我们所有的命令都记录下来保存到文件&#xff0c;恢复的时候把这个文件都执行一遍。 2.请你用自己的话说一说 AOF…

【Vue】学习笔记-全局事件总线

全局事件总线(GlobalEventBus) 一种可以在任意组件通信的方式&#xff0c;本质上就是一个对象&#xff0c;它必须满足以下条件 所有的组件对象都必须能看见他这个对象必须能够使用$ on $ emit $ off方法取绑定、触发和解绑事件 使用步骤 定义全局事件总线 //创建VUE new V…

WPF中的用户控件和自定义控件详解

总目录 文章目录 总目录一、什么是用户控件和自定义控件1 用户控件和自定义控件2 相关知识点2 用户控件和自定义控件的适用场景 二、用户控件的使用1.通过依赖属性和路由事件封装用户控件的案例2.通过依赖属性命令依赖属性封装用户控件的案例3.只对外提供属性不对外提供操作的用…

USBCAN卡和台达AS228T通信

AS228R 如图 A.把CAN卡固定上 B.接一个120欧的终端电阻 C.把来可的USBCAN卡的CANH和CANL分别接入AS228R的CANH和CANL. CAN 接口及网络拓扑 10.2.4.1 CAN 网络实体信号的定义及数据格式 CAN 信号为差分信号&#xff0c;信号电压为 CAN和 CAN-之间的电压差&#xff0c;CAN 和…

怎样删除hao123(浏览器首页被篡改了)

有时候我们打开浏览器发现首页被hao123 ,或者2345 这些浏览器给篡改了 或者打开的时候直接打开2个.这个时候想要删除它们,其他它们本身就是网页的,没有应用 在卸载的地方就不用了,它们就嵌套你的浏览器里面,打开的时候启动了他们, 下面说下方法 1 查看浏览器在什么方法下载…

第3天学习Docker-Docker部署常见应用(MySQL、Tomcat、Nginx、Redis、Centos)

前提须知&#xff1a; &#xff08;1&#xff09;搜索镜像命令 格式&#xff1a;docker search 镜像名 &#xff08;2&#xff09;设置Docker镜像加速器 详见文章&#xff1a;Docker设置ustc的镜像源&#xff08;镜像加速器&#xff09; 1、部署MySQL 拉取镜像&#xff08;这…

Radius:针对Rollup生态的Trustless Shared Sequencing层

1. 引言 前序博客&#xff1a; Rollup去中心化Rollup DecentralizationAztec 征集 Rollup Sequencer去中心化提案 Rollup中单一中心化sequencer的问题在于&#xff1a; 审查MEV extraction MEV extraction的问题不在于中心化本身&#xff0c;而在于sequencer具有操纵交易顺…

CN学术期刊《西部素质教育》简介及投稿邮箱

《西部素质教育》&#xff08;半月刊&#xff09;创刊于2015年&#xff0c;是由青海人民出版社有限责任公司主管/主办的教育类学术期刊&#xff0c;本刊恪守“追踪教育研究前沿&#xff0c;关注教育实践热点&#xff0c;探索创新教育理念&#xff0c;传播教育教学信息&#xff…