redis数据结构--hyperloglog

1.业务背景介绍现存实现手法以及痛点

公司广告落地页项目的 一个统计pv,uv的需求

1.公司需要统计现有各个系统的pv,uv情况,数据中台做数据分析。
2.落地页项目需要获取到落地页pv,uv,表单等数据以供投手分析广告投放效果。

第一版实现方案

1.落地页被访问,上报访问请求,后端获取到访问请求信息,通过mq上报给数据中台。
2.每晚通过xxljob请求数据中台获取指定落地页的pv,uv数据落库。
3.业务需要查询自己

业务快速增长面临的痛点

以前业务压力不太大通过定时任务请求数据中台接口,单次请求一秒内可以接受,随着业务数据增长,日均访问落地页数量2000多个,目前日均pv,uv在百万级,有时候数据中台接口不稳定会导致一次请求需要5-6秒,一次任务需要一两个小时才能跑完,这期间会造成服务器报警。基于目前的现状需要优化pv,uv的统计。且当天数据需要次日才可以统计出来(业务查询当天实时调用中台接口)。

2.业务场景调研,bitmap–hyperloglog

现存业务场景:

1.每天在投放的有2000多个落地页,需要知道每个落地页的pv,uv数据。
2.单个落地页的pv,uv峰值也在百万级别。

建模为程序问题

1.单天需要有最少2000个数据结构分别统计每个落地页的数据。
2.单个数据结构占用内存应该足够小。
3.随着业务增长,数据量线性增长可以支撑。

网上有许多解决方案,说一下最常见的两种解决方案。

bitmap

bitmap的思想很简单,就是来一个请求我们通过这个请求的一个标识(可以是ip等其他)来设置该请求的bit位。具体了解bitmap可以参考下:漫画:什么是Bitmap算法?.

查到的解决方案都是统计日活这一类相关的,下面结合我们的实际业务来分析下方案的可行性。

1.统计uv我们可以拿到用户请求的ip,由于是投放广告,用户是不需要登录的,获取不到userId相关的属性,这里定为使用用户的ip的hash值作为bit位。
2.由于生产uv峰值达到了百万级别,就按1百万来算,存储1百万个bit位需要内内存大约为
1000000 / 8 / 1024 大约为120k内存,2000个即相当于240m 一天需要240m(虽然bitmap对于数据量未真正达到这个量级的数据有优化,不会占用到这么多)。

hyperloglog

查询在redis中hyperloglog存储2的64次方的数据只需要12k。

3.优缺点和业务契合度

bitmap

优点:数据准确,误差率为hash函数取的位数的大小以及产生hash冲突的概率,误差可控。
缺点:占用内存大,且随着业务增长会对redis造成压力,且内存占用不可控,会造成一些意料之外的情况。

hyperloglog

优点:占用内存小,目前一天业务数据只需要24m内存,那么2000个落地页每天仅需要24m的内存即可统计,内存可控,可以适应业务增长带来的数据增长,2的64次足够容纳业务业务峰值
缺点:有固定0.81%的误差但是针对uv数据,这些误差是可接受的。

所以最终确定选用hyperloglog统计该次的数据。

4.hyperloglog数学原理介绍

提问环节

对于hyperloglog存储2的64次方的数据只需要12k。且误差率在0.81%之内。

不知道各位有没有什么疑问:

为什么是12k?

12k是如何存储2^64次方的数据的,且保证了误差在0.81%之内?

伯努利过程

伯努利过程就是一个抛硬币实验的过程。抛一枚正常硬币,落地可能是正面,也可能是反面,二者的概率都是 1/2 。伯努利过程就是一直抛硬币,直到落地时出现正面位置,并记录下抛掷次数k。比如说,抛一次硬币就出现正面了,此时 k 为 1; 第一次抛硬币是反面,则继续抛,直到第三次才出现正面,此时 k 为 3。

对于 n 次伯努利过程,我们会得到 n 个出现正面的投掷次数值 , 其中这里的最大值是k_max。

根据一顿数学推导,我们可以得出一个结论: 来作为n的估计值。也就是说你可以根据最大投掷次数近似的推算出进行了几次伯努利过程。
n次伯努利过程

伯努利过程转化为程序问题建模:

我们可以将每一次的用户pv请求转化为一次伯努利请求:

使用用户ip计算ip的hash值,hash其实就是一串0和1的二进制数,记录下该hash值首次出现1的下标作为k,这样就将一次pv请求转化为一次伯努利过程了。

5.redis的实现

redis中hyperloglog的实现

只介绍了hyperloglog的数学原理,但是疑问并没有解答,下面,我们就来讲解一下 HyperLogLog 是如何模拟伯努利过程,并最终统计uv数据的。

HyperLogLog 在添加元素时,会通过Hash函数,将元素转为64位比特串,例如输入5,便转为101(省略前面的0,下同)。这些比特串就类似于一次抛硬币的伯努利过程。比特串中,0 代表了抛硬币落地是反面,1 代表抛硬币落地是正面,如果一个数据最终被转化了 10010000,那么从低位往高位看,我们可以认为,这串比特串可以代表一次伯努利过程,首次出现 1 的位数为5,就是抛了5次才出现正面。
Redis 中 HyperLogLog 一共分了 2^14 个桶,也就是 16384 个桶。每个桶中是一个 6 bit 的数组,如下图所示。
redis分桶

!问题

为什么一个桶是6bit?

HyperLogLog 将上文所说的 64 位比特串的低 14 位单独拿出,它的值就对应桶的序号,然后将剩下 50 位中第一次出现 1 的位置值设置到桶中。50位中出现1的位置值最大为50,所以每个桶中的 6 位数组正好可以表示该值。

在设置前,要设置进桶的值是否大于桶中的旧值,如果大于才进行设置,否则不进行设置。示例如下图所示。
在这里插入图片描述

疑问:
这样内存是确定在12k了,那么如何保证误差呢?

LogLog中求解的办法

直接求平均数,对于一个很大的数对于结果的影响很大。

hyperloglog中求解的办法

求调和平均数和修正
在这里插入图片描述
这样即使有单个数据很大,对于结果的影响也是微乎其微的。

6.讨论适用场景和回顾

7.总结

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值