为什么要使用HyperLogLog? 在我们实际开发的过程中,可能会遇到这样一个问题,当我们需要统计一个大型网站的独立访问次数时,该用什么的类型来统计? 如果我们使用Redis中的集合来统计,当它每天有数千万级别的访问时,将会是一个巨大的问题。因为这些访问量不能被清空,我们运营人员可能会随时查看这些信息,那么随着时间的推移,这些统计数据所占用的空间会越来越大,逐渐超出我们能承载最大空间。 例如,我们用IP来作为独立访问的判断依据,那么我们就要把每个独立IP进行存储,以IP4来计算,IP4最多需要15个字节来存储信息,例如:110。110。110。110。当有一千万个独立IP时,所占用的空间就是15bit10000000约定于143MB,但这只是一个页面的统计信息,假如我们有1万个这样的页面,那我们就需要1T以上的空间来存储这些数据,而且随着IP6的普及,这个存储数字会越来越大,那我们就不能用集合的方式来存储了,这个时候我们需要开发新的数据类型HyperLogLog来做这件事了。HyperLogLog介绍 HyperLogLog(下文简称为HLL)是Redis2。8。9版本添加的数据结构,它用于高性能的基数(去重)统计功能,它的缺点就是存在极低的误差率。 HLL具有以下几个特点:能够使用极少的内存来统计巨量的数据,它只需要12K空间就能统计264的数据;统计存在一定的误差,误差率整体较低,标准误差为0。81;误差可以被设置辅助计算因子进行降低。基础使用 HLL的命令只有3个,但都非常的实用,下面分别来看。添加元素127。0。0。1:6379pfaddkeyredis(integer)1127。0。0。1:6379pfaddkeyjavasql(integer)1 相关语法:pfaddkeyelement〔element。。。〕 此命令支持添加一个或多个元素至HLL结构中。统计不重复的元素127。0。0。1:6379pfaddkeyredis(integer)1127。0。0。1:6379pfaddkeysql(integer)1127。0。0。1:6379pfaddkeyredis(integer)0127。0。0。1:6379pfcountkey(integer)2 从pfcount的结果可以看出,在HLL结构中键值为key的元素,有2个不重复的值:redis和sql,可以看出结果还是挺准的。 相关语法:pfcountkey〔key。。。〕 此命令支持统计一个或多个HLL结构。合并一个或多个HLL至新结构 新增k和k2合并至新结构k3中,代码如下:127。0。0。1:6379pfaddkjavasql(integer)1127。0。0。1:6379pfaddk2redissql(integer)1127。0。0。1:6379pfmergek3kk2OK127。0。0。1:6379pfcountk3(integer)3 相关语法:pfmergedestkeysourcekey〔sourcekey。。。〕 pfmerge使用场景 当我们需要合并两个或多个同类页面的访问数据时,我们可以使用pfmerge来操作。代码实战 接下来我们使用Java代码来实现HLL的三个基础功能,代码如下:importredis。clients。jedis。Jedis;publicclassHyperLogLogExample{publicstaticvoidmain(String〔〕args){JedisjedisnewJedis(127。0。0。1,6379);添加元素jedis。pfadd(k,redis,sql);jedis。pfadd(k,redis);统计元素longcountjedis。pfcount(k);打印统计元素System。out。println(k:count);合并HLLjedis。pfmerge(k2,k);打印新HLLSystem。out。println(k2:jedis。pfcount(k2));}} 以上代码执行结果如下:k:2k2:2HLL算法原理 HyperLogLog算法来源于论文HyperLogLogtheanalysisofanearoptimalcardinalityestimationalgorithm,想要了解HLL的原理,先要从伯努利试验说起,伯努利实验说的是抛硬币的事。一次伯努利实验相当于抛硬币,不管抛多少次只要出现一个正面,就称为一次伯努利实验。 我们用k来表示每次抛硬币的次数,n表示第几次抛的硬币,用kmax来表示抛硬币的最高次数,最终根据估算发现n和kmax存在的关系是n2(kmax),但同时我们也发现了另一个问题当试验次数很小的时候,这种估算方法的误差会很大,例如我们进行以下3次实验:第1次试验:抛3次出现正面,此时k3,n1;第2次试验:抛2次出现正面,此时k2,n2;第3次试验:抛6次出现正面,此时k6,n3。 对于这三组实验来说,kmax6,n3,但放入估算公式明显326。为了解决这个问题HLL引入了分桶算法和调和平均数来使这个算法更接近真实情况。 分桶算法是指把原来的数据平均分为m份,在每段中求平均数在乘以m,以此来消减因偶然性带来的误差,提高预估的准确性,简单来说就是把一份数据分为多份,把一轮计算,分为多轮计算。 而调和平均数指的是使用平均数的优化算法,而非直接使用平均数。 例如小明的月工资是1000元,而小王的月工资是100000元,如果直接取平均数,那小明的平均工资就变成了(1000100000)250500元,这显然是不准确的,而使用调和平均数算法计算的结果是2(110001100000)1998元,显然此算法更符合实际平均数。 所以综合以上情况,在Redis中使用HLL插入数据,相当于把存储的值经过hash之后,再将hash值转换为二进制,存入到不同的桶中,这样就可以用很小的空间存储很多的数据,统计时再去相应的位置进行对比很快就能得出结论,这就是HLL算法的基本原理,想要更深入的了解算法及其推理过程,可以看去原版的论文,链接地址在文末。小结 当需要做大量数据统计时,普通的集合类型已经不能满足我们的需求了,这个时候我们可以借助Redis2。8。9中提供的HyperLogLog来统计,它的优点是只需要使用12k的空间就能统计264的数据,但它的缺点是存在0。81的误差,HyperLogLog提供了三个操作方法pfadd添加元素、pfcount统计元素和pfmerge合并元素。参考文献论文HyperLogLog:theanalysisofanearoptimalcardinalityestimationalgorithm