标签:HyperLogLog max UV Redis 试验 页面 伯努利
如果要我们设计一个基于Redis
统计页面UV
的实现方案,可能的实现方案有什么? 大家可能很容易想到的一个方案就是使用Set
对象保存每一个访问页面的用户id
,因为Set
结构天然就支持去重功能,因此使用scard
取出的Set
集合大小即为页面UV
。但是,如果页面UV
非常巨大时,使用Set
结构存储就会非常浪费空间。 Redis
提供了HyperLogLog
数据结构来解决这类统计问题,既节省内存占用,又能保证统计结果在可接受的误差之内。
Redis中的HyperLogLog使用
Redis
中的HyperLogLog
提供了两个指令pfadd
和pfcount
。pfadd
用于增加计数,pfcount
用于获取计数。针对上面的统计页面UV
的场景,当用户访问页面时,就使用pfadd
指令将用户id
添加到HyperLogLog
中,最后执行pfcount
就能获取到页面UV
。
HyperLogLog算法介绍
实际上,HyperLogLog
是一种够提供不精确的去重计的算法。存在以下的特点:
- 能够使用极少的内存来统计巨量的数据,在
Redis
中实现的HyperLogLog
,只需要12K内存就能统计2^64
个数据。 - 计数存在一定的误差,误差率整体较低。标准误差为
0.81%
。 - 误差可以被设置辅助计算因子进行降低。
伯努利试验
伯努利试验是数学概率论中的一部分内容,它的典故来源于抛硬币。
硬币拥有正反两面,每次的上抛至落下,出现正反面的概率都是1/2
。第一次出现正面的概率是1/2
,连续2次出现正面的概率就是1/4
, 连续出现K
次正面的概率就是2^(-k)
。假设一直抛硬币,直到它出现正面为止,我们记录为一次完整的试验,中间可能抛了一次就出现了正面,也可能抛了4次才出现正面。无论抛了多少次,只要出现了正面,就记录为一次试验。这个试验就是伯努利试验。
如果你觉得自己学习效率低,缺乏正确的指导,可以加入资源丰富,学习氛围浓厚的技术圈一起学习交流吧!
[Java架构群]
群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的JAVA交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。
那么对于n
次的伯努利试验,假设每次伯努利试验所经历了的抛掷次数为k
。那么第一次伯努利试验,次数设为k1
,以此类推,第n
次对应的是kn
。其中,对于这n
次伯努利试验中,必然会有一个最大的抛掷次数k_max
。
最终结合极大似然估算的方法,发现在n
和k_max
中存在估算关联:n * 2^(-k_max)=1
,即n = 2^(k_max)
。
估算优化
我们只进行n
次伯努利试验称为一轮实验。当n
足够大的时候,估算的误差率会相对减少,但仍然不够小。 但是如果我们进行更多轮次实验,然后再取每轮的 k_max
的平均数。最终再估算出 n
,这样子的误差就会相对减少很多。下面是LogLog的估算公式:
上面公式的DVLL
对应的就是n
,constant
是修正因子,它的具体值是不定的,可以根据实际情况而分支设置。m
代表的是试验的轮数。头上有一横的R
就是平均数:(k_max_1 + ... + k_max_m)/m
。
这种通过增加试验轮次,再取k_max
平均数的算法优化就是LogLog的做法。而 HyperLogLog
和LogLog
的区别就是,它采用的不是平均数,而是调和平均数。调和平均数比平均数的好处就是不容易受到大的数值的影响。下面举个例子:
程序实现
通过上面的内容我们已经知道,在抛硬币的例子中,可以通过一次伯努利试验中出现的k_max
来估算n
。
将数据转换为比特串
通过hash
函数,将数据转换为比特串,例如输入5
,可以转换为为:101
。这么做的目的是和抛硬币对上,在比特串中,0
代表反面,1
代表正面。如果一个数据最终被转化了10010000
,那么从右往左看,我们可以认为,首次出现1
的时候,就是正面。基于上面的估算结论,我们可以根据存入数据中,转化后的出现了1
的最大的位置k_max
来估算存入了多少数据,即n = 2^(k_max)
。
分桶
分桶就是分多少轮。具体到程序实现上,就是将输入数据hash
转换为比特串的时候,先确定桶号,然后再将第一个出现1
的个位数值设置到该桶中。
Redis中的HyperLogLog实现
在Redis
中的HyperLogLog
中,共分为2^14
个桶,每个桶有6
个bit
,占用内存为=2^14*6/8/1024=12K
。
当我们执行命令pfadd key value
时,首先会通过hash
函数将value
转换为64
位比特串。其中,前14位用来确定分桶(刚好有2^14
个桶),后50
位用来获取第一个出现1
的位数(从右向左),因为最大为50
,因此使用6
个bit
完全可以表示。
不同的value
,会被设置到不同桶中去,如果出现了在同一个桶的,但是后面第一个出现1
的位数不同。那么比较这个桶新的值是否比原来的值大,如果大于,则替换。否则,保持不变。
最终,2^14
个桶都保存了出现1
的最大值k_max
,此时调用pfcount
时,按照前面介绍的估算方式,便可以计算出key
的设置了多少次valu
,也就是统计值,即2^14*2^(k_max平均)
。
最后
给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。
码字不易,如果觉得本篇文章对你有用的话,请给我一键三连!关注作者,后续会有更多的干货分享,请持续关注!
标签:HyperLogLog,max,UV,Redis,试验,页面,伯努利 来源: https://blog.csdn.net/weixin_45600362/article/details/117988847
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。