redis中的HyperLogLog

249 阅读4分钟

本文将对redis中新出现的HyperLogLog这种数据结构进行介绍,主要介绍其出现场景,相关命令使用及数学原理。

简介

首先假设我们遇到了一个业务问题:
假设产品经理让你设计一个模块,来统计PV(Page View页面的访问量),那么你会怎么做? 我想很多人对于PV(Page View页面的访问量)的统计会很快的想到使用Redis的incr、incrby指令,给每个网页配置一个独立Redis计数器就可以了,把这个技术区的key后缀加上当它的日期,这样一个请求过来,就可以通过执行incr、incrby指令统计所有PV。

此时当你完成这个需求后,产品经理又让你设计一个模块,统计UV(Unique Visitor,独立访客),那么你又会怎么做呢? UV与PV不一样,UV需要根据用户ID去重,如果用户没有ID我们可能需要考虑使用用户访问的IP或者其他前端穿过了的唯一标志来区分,此时你可能会想到使用如下的方案来统计UV。

  • 存储在MySQL数据库表中,使用distinct count计算不重复的个数

  • 使用Redis的set、hash、bitmaps等数据结构来存储,比如使用set,我们可以使用用户ID,通过sadd加入set集合即可 但是上面的两张方案都存在两个比较大的问题:

  • 随着数据量的增加,存储数据的空间占用越来越大,对于非常大的页面的UV统计,基本不合实际

  • 统计的性能比较慢,虽然可以通过异步方式统计,但是性能并不理想 因此针对UV的统计,我们将会考虑使用Redis的新数据类型HyperLogLog. HyperLogLog是用来做基数统计的算法,它提供不精确的去重计数方案(这个不精确并不是非常不精确),标准误差是0.81%,对于UV这种统计来说这样的误差范围是被允许的。

  • HyperLogLog的优点在于,输入元素的数量或者体积非常大时,基数计算的存储空间是固定的。在Redis中,每个HyperLogLog键只需要花费12KB内存,就可以计算接近2^64个不同的基数。

  • 但是:HyperLogLog只能统计基数的大小(也就是数据集的大小,集合的个数),他不能存储元素的本身,不能向set集合那样存储元素本身,也就是说无法返回元素。

HyperLogLog指令都是pf(PF)开头,这是因为HyperLogLog的发明人是Philippe Flajolet,pf是他的名字的首字母缩写。

命令

1、用pfadd添加键值对   通过pfadd命令,能把键值对添加到HyperLogLog对象中,添加后即可进行基数统计。格式如下:

pfadd key element [element ...]

用HyperLogLog命令可以在一个键上同时添加多个值。

image.png

key为Mary的基数值为2

2、用pfcount统计基数值 用pfcount可以查看一个或多个键的基数,格式如下:

pfcount key [key ...]

如果对应的key不存在,则返回0.

image.png

3、用pfmerge进行合并操作   通过pfmerge命令,能把多个HyperLogLog合并成一个,格式如下:

pfmerge destkey sourcekey [sourcekey ...]

1 其中,sourcekey是待合并的对象,可以是一个或多个;destkey是合并后HyperLogLog的键,如果合并前destkey不存在,则会新建一个。

image.png

4、统计网站访问总人数 利用HyperLogLog统计访问人数的方法。

在实际项目里,可能访问列表会很长,用HyperLogLog统计的性能比较快,另外还能用较小的存储空间代价来完成通国际访问总人数的工作。

image.png

时间复杂度:
O(1)

原理

Linear Counting是Hyperloglog的基础,本文将首先对其进行介绍。

Linear Counting

Linear Counting是采用概率的方式进行基数估计的最简单的方法。下面通过一个实例描述Linear Counting的计算过程:

  • 数据哈希:假设原始数据的基数为n,使用一组哈希空间为m的哈希函数H,将原始数据转换为满足均匀分组的一组哈希数组。
  • 分桶数据统计:构建一个长度为m的bitmap,其中每一个bit对应哈希空间的一个值。生成哈希数组的值如果存在,则把相应的bit设置为1。当所有值设置完成后,统计bitmap中为0的bit数为u。

image.png
可以通过下述的公式计算基数估计的结果:

image.png

注意这里的log指的是自然对数。

公式的推导过程有兴趣的可以参考这篇文章。其中最重要的是要清楚,在经过n次数据的哈希后,bitmap中的某个bit值为0,是一个伯努利事件。记住这一点再理解公式推导就容易多了。

详细公式可见: www.yuque.com/abser/about…

参考文献: blog.csdn.net/qq_41125219… www.yuque.com/abser/about… www.jianshu.com/p/41256ac5b… blog.codinglabs.org/articles/al…