Redis-HyperLogLog

基于HyperLogLog算法,使用极小的空间完成巨量运算

Redis 中HyperLogLog 基本使用

常用命令

  1. PFADD key element [element …]: 将任意数量的元素添加到指定的 HyperLogLog 里面。
  2. PFCOUNT key [key …]: 计算hyperloglog的独立总数
  3. prmerge destkey sourcekey [sourcekey…]: 合并多个hyperloglog

python 操作Redis HyperLogLog

from MyRedis.RedisTool import RedisToolclass RedisHLL:def __init__(self):self._conn = RedisTool.redis_connection("127.0.0.1", 8100, "redis")def hll_test(self):self._conn.pfadd('test', "junebao", "python", "redis", "hyperloglog", "java")count = self._conn.pfcount("test")print(count)if __name__ == '__main__':RedisHLL().hll_test()  # 5

HyperLogLog 算法原理

特点:

  • 能使用极少的内存来统计巨量的数据,在Redis中的HyperLogLog只需要12k内存就能统计 2642^{64}264
  • 计数存在一定的误差,但误差率整体较低,标准误差为 0.81%
  • 可以设置辅助计算因子减小误差

LogLog 简介

HyperLogLog 其实是 LogLog 算法的改进版,Loglog源于著名的伯努利实验

这个实验是这样的:随机抛一枚硬币,那么正面朝上和反面朝上的概率都应该是 50% ,那么如果一直重复抛硬币,直到出现正面朝上,就记作1次伯努利实验。

对于单个一次伯努利实验,抛硬币的次数是不确定的,有可能第一次就正面朝上,那这1次就被记为1次伯努利实验,也有可能抛了10次才出现正面朝上,那这10次才会被记作1次伯努利实验。

假设做了n次伯努利实验,第一次实验抛了 k1k_1k1​ 次硬币, 第二次抛了 k2k_2k2​ 次硬币,那么第 n 次实验就抛了 knk_nkn​ 次硬币。在 [k1−kn][k_1 -k_n][k1​−kn​] 之间,就必然存在一个最大值 kmaxk_{max}kmax​ , kmaxk_{max}kmax​的意义就是在这一组伯努利实验中,出现正面朝上需要的最多的抛掷次数。结合极大似然估计方法得到伯努利实验的次数 nnn 和这个最大值 kmaxk_{max}kmax​ 存在关系: n=2kmaxn = 2^{k_{max}}n=2kmax​

例如:实验0和1表示硬币的正反,一轮做五次实验,某轮伯努利实验的结果为

# 第一次
001
# 第二次
01
# 第三次
1
# 第四次
0001
# 第五次
001

那么这一轮伯努利实验的 kmax=4k_{max}=4kmax​=4 ,按照上面的公式应该得到 5=245=2^45=24,这个误差显然太过巨大,我们可以增加某一轮实验的次数,用python模拟一下

import randomclass BernoulliExp:def __init__(self, freq: int):self.freq = freqself.option = [0, 1]def run(self):k_max = 0for i in range(self.freq):num = 0while True:num += 1result = random.choice(self.option)if result == 1:break# print(f"第{i}次伯努利实验,抛了{num}次硬币")if num > k_max:k_max = numreturn k_maxif __name__ == '__main__':be = BernoulliExp(5000)k_max = be.run()print(f"k_max={k_max}")

通过测试,当每一轮进行5000次伯努利实验时,进行五轮,kmaxk_{max}kmax​分别为 12, 12, 14, 11, 15,误差仍旧很大,所以我们可以进行多轮伯努利实验,求kmaxk_{max}kmax​的平均值,用python模拟一下

import randomclass BernoulliExp:def __init__(self, freq: int, rounds: int, num: int):"""Args:freq: int,每轮进行多少次实验rounds: k_max 对多少轮实验求平均num: 进行多少次这样的实验(求误差)"""self.freq = freqself.option = [0, 1]self.rounds = roundsself.number_of_trials = numdef _run_one_round(self):k_max = 0for i in range(self.freq):num = 0while True:num += 1result = random.choice(self.option)if result == 1:break# print(f"第{i}次伯努利实验,抛了{num}次硬币")if num > k_max:k_max = numreturn k_maxdef get_k_max(self):sum_k_max = 0for i in range(self.rounds):sum_k_max += self._run_one_round()return sum_k_max / self.roundsdef deviation(self):dev = 0for i in range(self.number_of_trials):k_max = self.get_k_max()print(f"第{i}次:k_max = {k_max}")dev += (2 ** k_max) - self.freqreturn dev/self.number_of_trialsif __name__ == '__main__':be = BernoulliExp(6, 16384, 5)dev = be.deviation()print(f"误差:{dev}")
第0次:k_max = 4.03546142578125
第1次:k_max = 4.034423828125
第2次:k_max = 4.05010986328125
第3次:k_max = 4.02423095703125
第4次:k_max = 4.045654296875
误差:10.427087015403654

这时误差依旧非常大,但我们发现 kmaxk_{max}kmax​却浮动在4.038上下,这就说明nnn和 kmaxk_{max}kmax​ 之间的关系确实存在,但公式前面还应该有一个常数项,原公式应该是 n=α⋅2kmaxn = \alpha · 2^{k_{max}}n=α⋅2kmax​

通过简单计算,把 α\alphaα设为 0.36520.36520.3652:

第0次:k_max = 4.055908203125
第1次:k_max = 4.0262451171875
第2次:k_max = 4.03045654296875
第3次:k_max = 4.04534912109375
第4次:k_max = 4.048095703125
误差:0.01269833279264585

这里0.3652是用n=6n=6n=6计算出来的,但当n取其他值时,这个因子也能基本将相对误差控制在0.1以内。

上面的公式,便是LogLog的估算公式

DVLL=constant∗m∗2R‾DV_{LL} = constant * m * 2 ^ {\overline{R}}DVLL​=constant∗m∗2R

其中 DVLLDV_{LL}DVLL​就是n,constant就是调和因子, m是实验轮数,R‾\overline{R}R 是 kmaxk_{max}kmax​的平均值。


而 HyperLogLog和LogLog的区别就是使用调和平均数计算kmaxk_{max}kmax​,这样如果计算的数值相差较大,调和平均数可以较好的反应平均水平,调和平均数的计算方式为:

Hn=n∑i=1n1xiH_n = \frac{n}{\sum_{i=1} ^ n \frac{1}{x_i}}Hn​=∑i=1n​xi​1​n​

所以 HyperLogLog 的公式就可以写为

DVHLL=const∗m∗m∑j=1m12RjDV_{HLL} = const * m * \frac{m}{\sum_{j=1} ^ m \frac{1}{2^{R_j}}}DVHLL​=const∗m∗∑j=1m​2Rj​1​m​

在Redis中的实现方法

如果我们我们可以通过kmaxk_{max}kmax​来估计nnn,那同样的,对于一个比特串,我们就可以按照这个原理估算出里面1的个数,例如在

统计一个页面每日的点击量(同一用户不重复计算)

要实现这个功能,最简单的办法就是维持一个set,每当有用新户访问页面,就把ID加入集合(重复访问的用户也不会重复加),点击量就是集合的长度,但这样做最大的问题就是会浪费很多空间,如果一个用户ID占8字节,加入有一千万用户,那就得消耗几十G的空间,但Redis只用了12k就完成了相同的功能。

首先,他把自己的12k划分为 16834 个 6bit 大小的 “桶”,这样每个桶所能表示的最大数字为 1111(2)=63{1111}_{(2)} = 631111(2)​=63, 在存入时,把用户ID作为Value传入,这个value会被转换为一个64bit的比特串,前14位用来选择这个比特串从右往左看,第一次出现1的下标要储存的桶号。

例如一个value经过Hash转换后的比特串为

[0000 0000 0000 1100 01]01 0010 1010 1011 0110 1010 0111 0101 0110 1110 0110 0100

这个比特串前14位是 110001(2){110001}_{(2)}110001(2)​,转换成10进制也就是49,而它从右往左看,第3位是1,所以3会被放到49桶中(首先要看49桶中原来的值是不是小于3,如果比3小,就用3替换原来的,否则不变,【因为桶中存的是kmaxk_{max}kmax​】), kmaxk_{max}kmax​在这里最大也只能是64,用6bit肯定够用。

这样不管有多少用户访问网站,存储的只有这12k的数据,访问量越多,kmaxk_{max}kmax​ 越大,然后根据HyperLogLog公式,就可以较精确的估计出访问量。(一个桶可以看作一轮伯努利实验)

修正因子

constant 并不是一个固定的值,他会根据实际情况而被分支设置,如: P=log⁡2mP = \log_2 mP=log2​m

m 是分桶数

switch (p) {case 4:constant = 0.673 * m * m;case 5:constant = 0.697 * m * m;case 6:constant = 0.709 * m * m;default:constant = (0.7213 / (1 + 1.079 / m)) * m * m;
}

参考

https://www.cnblogs.com/linguanh/p/10460421.html#commentform

https://chenxiao.blog.csdn.net/article/details/104195908

HyperLogLog原理与在Redis中的使用相关推荐

  1. HyperLogLog 算法原理及其在 Redis 中的实现

    一.问题引入 大家在项目上可能会遇到过下面这些相同或者类似的需求: 统计一个APP的日活量(DAU)和月活量(MAU).日活(月活)是指在一个统计日(统计月)之内,登录或者使用产品的不同用户数量,它是 ...

  2. 【承】Redis 原理篇——关于 Redis 中的事务

    前言 关于 Redis 的"起承转合",我前面已经用五个篇章的长度作了一个 Redis 基础篇--"起"篇的详细阐述,相信大家无论之前有没有接触过 Redis, ...

  3. HyperLogLog 算法的原理讲解以及 Redis 是如何应用它的

    作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:http://www.cnblogs.com/linguan ...

  4. Redis中布隆过滤器的使用及原理

    <玩转Redis>系列文章主要讲述Redis的基础及中高级应用.本文是<玩转Redis>系列第[11]篇,最新系列文章请前往公众号"zxiaofan"查看, ...

  5. redis HyperLogLog原理

    假设现在有一个这样的需求,我们想要实时统计有多少用户访问我们的网站.一个简单的解决方案是用一个set集合来存储用户ID,然后计算任意时刻集合中不同ID的个数即为网站实时访问量.这是一种简单可行的做法, ...

  6. redis中HLL的使用hyperloglog

    redis中HLL的使用 这里给出官方文档(中文翻译版)连接,里面关于时间复杂度.返回值.命令方式.使用案例等等都有详细说明 本文对每个命令都简介总结并个人案例展示 pfadd 添加 影响基数估值则返 ...

  7. Redis中的Bitmaps、HyperLogLog、Geospatial

    目录 Bitmaps 如何选择集合类型 判断用户登录状态 小结 HyperLogLog 什么是基数 应用场景 命令 Geospatial 如何实现定位 Geohash技术 geohash的计算 geo ...

  8. Redis 中 bitmap 的原理和使用

    原理 先声明一下:Redis 有5种数据类型,而 BitMap 在 Redis 中并不是一个新的数据类型,其底层是 Redis 实现. 通常情况下,我们在 redis 中存储一个字符串,如:" ...

  9. Redis中的zset 存储结构(实现)原理

    同时满足以下条件时使用ziplist 编码: 元素数量小于128 个 所有member 的长度都小于64 字节 在ziplist 的内部,按照score 排序递增来存储.插入的时候要移动之后的数据. ...

最新文章

  1. 相参、相参积累和相参雷达
  2. 安卓模拟器_exagear模拟器安卓模拟器
  3. P4254-[JSOI2008]Blue Mary开公司【李超树】
  4. 信息学奥赛C++语言:换钱
  5. stm32l4 外部中断按键会卡死_stm32f103c8怎么实现外部中断按键点灯,按一下就亮,再按一下就灭,求大神帮忙...
  6. 华为抓取错误日志在哪里_分析Spider抓取情况和SEO优化
  7. 令牌环网Token Ring协议
  8. 全行业产业链图示(摘自企查查)
  9. 保温杯市场前景分析及行业研究报告
  10. 【Unity项目优化宝典】Unity3D手游开发客户端开发经验总结
  11. 这是最好的企业管理手册
  12. jzoj 4246【五校联考6day2】san
  13. 双十一自动领喵币工具
  14. 汇编语言笔记-keil5软件仿真及调试
  15. 第二章第十六题(几何:六边形面积)(Geometry: area of a hexagon)
  16. daimayuan每日一题#810 最短路计数
  17. 新冠全球确诊超2亿!德尔塔后,新「毒王」拉姆达已蔓延32国
  18. VRay Next for SketchUp 室外建筑日景表现教程
  19. 进程间各种通信方式的C++实现
  20. 无人机从零到一(组装、校准到起飞)

热门文章

  1. 与python相关计算机基础知识
  2. 六.dbms_session(提供了使用PL/SQL实现ALTER SESSION命令)
  3. JDBC数据对象存储
  4. MySQL 调优基础(三) Linux文件系统
  5. Android4.2.2的Stagefright维护编解码器的数据流
  6. .Net 中的反射(查看基本类型信息) - Part.2
  7. 西宁a货翡翠,孝感a货翡翠
  8. 【云周刊】第205期:阿里云重磅开源实时计算平台Blink,挑战计算领域的“珠峰”...
  9. C#设计模式(19)——状态者模式(State Pattern)
  10. [Oracle]快速构造大量数据的方法