redis HyperLogLog原理

2023-11-19

假设现在有一个这样的需求,我们想要实时统计有多少用户访问我们的网站。一个简单的解决方案是用一个set集合来存储用户ID,然后计算任意时刻集合中不同ID的个数即为网站实时访问量。这是一种简单可行的做法,但是假如这个网页访问量很大加上随着时间推移存储集合数据所需要的内存空间越来越大,所需要的统计成本也越来越高。举个例子,假如用一个int类型来保存用户id,当有一千万用户的时候,总共需要38M内存。为了省内存我们还有另一种方案,位图法,我们用bitmap来保存数据,用户访问的时候我们就把对应的bit置1,最后统计这个bitmap总共有几个bit为1来统计访问量。这样,一千万用户就需要一千万个bit,约为1.2M大小。假如我要统计网站每天的用户访问量,那就意味着需要很多个bitmap,每个1.2M还是太大了。那有没有其它更省内存的解决方案呢?这时候我们需要另外一种算法来解决这个问题--hyperloglog算法。redis 中的hyperloglog,不管你的访问量有多大,几千万还是几个亿,最多只需要约12k内存!

首先介绍一下概率论中伯努利试验:伯努利试验(Bernoulli experiment)是指在同样的条件下重复地、相互独立地进行的一种随机试验,其特点是该随机试验只有两种可能结果:发生或者不发生。比如抛硬币,只有正面反面两种结果。
在介绍 HyperLogLog 之前,我们可以考虑这样一个游戏:不断抛硬币,直到第一次出现正面朝上,记录总共抛了多少次。

这个游戏中,假如在某次场景下,前3次都是反面,第4次正面,问这个概率是多少?这个很简单(\frac{1}{2})^{4},那么相当于平均需要玩 2^{4}=16次这个游戏才会出现一次这种场景。反过来说,如果某次游戏要抛到第4次才第一次出现正面朝上,那么可以推出这个游戏已经玩了16回。这个就是hyperloglog的基本原理。

然后我们用“1”来表示正面朝上,“0”表示反面朝上。用“0”,“1”组成的序列来表示某次游戏的结果,比如上面这个例子要抛4次的序列是:0 0 0 1。如果要抛5次就是:0 0 0 0 1,以此类推。

简单来看,其实 HyperLogLog 的基数统计就使用了这样的思想,通过二进制中 ‘1’出现的第一个位置来估算整体的数量。

举个例子,假设上面那个游戏一共玩了N次,其中最多的一次抛了6次硬币才结束,那么它的结果序列是:0 0 0 0 0 1。我们可以推算N=2^{6},即总共玩了64次这个游戏。

这种方法根据某次的结果来估算游戏进行的次数毫无疑问误差会比较大。我们可以对每次游戏的结果求平均值提高估算的精度。

假如依次进行时五次游戏,结果分别是:

  1. "0 0 1",n1=3;
  2. "0 1',n2=2;
  3. "1",n3=1;
  4. "0 0 0 0 1",n4=5;
  5. "0 1",n5=2.

 如果直接按n最大的一次计算的话,2^{5},32次,误差很大。

如果我们按五次游戏的平均值来算,\frac{3+2+1+5+2}{5}2^{3}=8,精度已经提高了很多。

我们还可以进一步提高精度,上面的均值用的是算术平均,现在我们改用调和平均来算一下.调和平均计算公式如下:

\frac{5}{\frac{1}{3}+\frac{1}{2}+1+\frac{1}{5}+\frac{1}{2}}2^{2}=4,精度又近了一步,由于算术平均值容易受极值影响,在一些极端场景下误差会比较大。再举一个例子,两次游戏:

  1. “0 0 0 0 0 0 0 0 0 0 0 0 0 0 1”,n1=15;
  2. "1",n2=1;

 如果拿算术平均算的话是2^{8}=256,误差非常大;如果是拿调和平均算,\frac{2}{1+\frac{1}{15}}=22^{2}=4,误差就小了很多。

 

关于调和平均和算术平均,再举一个例子:两个人,一个月薪1千,一个月薪十万,那么两人的算术平均工资是50500,调和平均工资是2000,可以明显看出调和平均对消除极值的影响效果更好,所以redis中hyperloglog的实现也是用的调和平均。

我们来进一步优化我们的方案,假设进行了n次游戏,然后把n次游戏分成若干各组,然后根据每个组中的最大抛硬币次数来估算n的值。

比如,一共进行16次游戏,分成2个组,然后估算游戏次数:

第一组8次分别是:“1”, “0 1”,“01” ,“0 01”,"1","0 0 1","0 1","1"那么最大抛币次数是3;

第二组分别是“01”,“0 0 0 01”,“1”, “0 1”,“0 01” ,“0 1”,“0” “01”,最大抛币次数是5.

然后对3和5计算平均值是4,估算结果是2^{4}=16。当然,当数据量比较少的时候误差可能会比较大。根据伯努利大数定律,辛钦大数定律,切比雪夫大数定律,样本越多均值误差就越小。在redis的实现中,是总共分为16384个组。下面开始看一下redis中是如何实现的。

redis实现

redis中是通过pfadd 命令将所有元素参数添加到 HyperLogLog 数据结构中。假设用户ID为123456789的用户访问了www.HyperLogLog.com这个网站.我们可以用一下命令来进行统计

pfadd   www.HyperLogLog.com 123456789

然后redis开始对这个用户进行一次伯努利试验,具体做法通过一个hash函数来对123456789生成一个64bit的值,这里先假设是8888886666666660000,那么他的二进制表示是

0111101101011011101010110101001111110111010111110101000010100000

 

redis的hyperloglog数据结构中共有16384个桶来保存每次伯努利试验的结果。16384=2^{14},因此需要14个bit来保存 。对于每次hash生成的64位的整形数,取高12位来作为hash桶索引,剩下的50个bit用来作为伯努利试验结果。

上面这个例子中,高12位是0111 1011 0101 10,十进制是7894。低50位的伯努利试验结果中第一次出现1是在第6位,那么它的意思是说这次试验是属于7894组,那么我们就要把这次试验结果6保存到哈希表第7894个桶中。当要把试验结果保存的时候,我们要先看一下这次试验结果6是不是比桶中保存的结果大,只有大于才保存,因为我是用各个分组中最大的结果来进行求均值。对于每次试验,结果最大就是50,2^{6}=64,就是用只需要用6个bit就可以保存这个试验结果。所以redis是用一个长度为16384*6个bit的数组来保存所有的试验结果,大小约为12k字节。

考虑这样一种场景,如果访问量只有几百几千,其实是不需要那么大空间来因为,因为这种情况下16384个桶会有很多桶是空的。比如有一千个连续的桶是空的,那么原本需要6000个bit去表示,占750字节。在redis里边其实只需两个字节就能保存这个信息。

ZERO

一个字节大小,高两位固定为00,剩下六位可以表示64个连续为0的空桶,

XZERO 两个字节大小,高两位固定为01,剩下的14位可以表示16384个连续的空桶
VAL 一个字节,高一位固定为1,中间五位表示值大小,剩下两位表示连续几个相同值的桶

 根据上表,如果要表示连续1000个空桶,可以用XZERO类型表示:01 000011 1110 1000,也就是0x01 00 03e8这个数。

以上只是hyperloglog的大致流程,redis在实际计算的时候是会加上一些修正的。

这个网站可以看hyperloglog的演示动画,可以加深理解:hyperloglog

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

redis HyperLogLog原理 的相关文章

  • 是否可以使用带有 FUSE 文件系统的 Linux VFS 缓存?

    默认情况下 Linux VFS 缓存似乎不适用于 FUSE 文件系统 例如 read 调用似乎被系统地转发到 FUSE 文件系统 我在 FUSE 特定的远程文件系统上工作 我需要一个非常积极的缓存 我需要实现自己的页面缓存吗 或者是否可以为
  • 使用 sidekiq 处理两个独立的 Redis 实例?

    下午好 我有两个独立但相关的应用程序 他们都应该有自己的后台队列 阅读 单独的 Sidekiq 和 Redis 进程 然而 我希望偶尔能够将工作推给app2的队列来自app1 从简单的队列 推送的角度来看 如果app1没有现有的 Sidek
  • Docker&Celery - 错误:Pidfile (celerybeat.pid) 已存在

    应用程序包括 姜戈 雷迪斯 芹菜 码头工人 Postgres 在将项目合并到 docker 之前 一切都运行顺利且正常 但是一旦将其移入容器 就开始出现问题 起初它开始得很好 但过了一会儿我确实收到了以下错误 celery beat 1 E
  • socket.io redis 和内存泄漏

    我的socket io版本是 电子邮件受保护 cdn cgi l email protection and 电子邮件受保护 cdn cgi l email protection 我在 Windows 上 在某些地方 我看到问题已得到解决 我
  • 保护节点 Redis

    我正在尝试保护 Node Redis IPC 服务器以使用私钥 公钥 我已经关注了本教程 http bencane com 2014 02 18 sending redis traffic through an ssl tunnel wit
  • Redis键空间事件不触发

    我有两个 Redis 客户端 在一个文件中我有一个简单的脚本设置并删除了 Redis 键 var redis require redis var client redis createClient 6379 127 0 0 1 client
  • 如何在节点redis客户端上设置读取超时?

    在 github 上我没有看到读取超时的选项 https github com NodeRedis node redis https github com NodeRedis node redis There s connect timeo
  • 如何设置 Celery 以通过 ssl 与 Azure Redis 实例对话

    使用 的伟大答案 如何在microsoft azure上的django项目中配置celery redis https stackoverflow com questions 39616701 how to configure celery
  • WSL Redis 遇到系统尚未使用 systemd 作为 init 系统(PID 1)启动。无法操作[已关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在尝试遵循本文中讨论的 Redis 安装过程article https www digitalocean com community
  • 库存管理系统的 SQL 与 NoSQL

    我正在开发一个基于 JAVA 的网络应用程序 主要目的是拥有在多个称为渠道的网站上销售的产品的库存 我们将担任所有这些渠道的管理者 我们需要的是 用于管理每个渠道的库存更新的队列 库存表 其中包含每个通道上分配的正确快照 将会话 ID 和其
  • Docker-compose Predis 不通过 PHP 连接

    我正在尝试使用 docker compose 将 PHP 与 redis 连接 docker compose yml version 2 services redis image redis 3 2 2 php image company
  • redis-cli 重定向到 127.0.0.1

    我在PC1上启动Redis集群 然后在PC2上连接它 当需要重定向到另一个集群节点时 它会显示Redirected to slot 7785 located at 127 0 0 1 但应该显示Redirected to slot 7785
  • Redis Docker compose无法处理RDB格式版本10

    我无法在 docker compose 文件中启动 redis 容器 我知道docker compose文件没问题 因为我的同事可以成功启动项目 我读到有一个删除 dump rdb 文件的解决方案 但我找不到它 我使用Windows机器 任
  • Lua中按字符分割字符串

    我有像这样的字符串 ABC DEF 我需要将它们分开 字符并将两个部分分别分配给一个变量 在 Ruby 中 我会这样做 a b ABC DEF split 显然Lua没有这么简单的方法 经过一番挖掘后 我找不到一种简短的方法来实现我所追求的
  • Java 将字节转换为二进制安全字符串

    我有一些以字节为单位的数据 我想将它们放入Redis中 但是Redis只接受二进制安全字符串 而我的数据有一些二进制非安全字节 那么如何将这些字节转换为二进制安全字符串以便将它们保存到 Redis 中呢 Base64 对我有用 但它使数据更
  • Redis、会话过期和反向查找

    我目前正在构建一个网络应用程序 并想使用 Redis 来存储会话 登录时 会话会使用相应的用户 ID 插入到 Redis 中 并且过期时间设置为 15 分钟 我现在想实现会话的反向查找 获取具有特定用户 ID 的会话 这里的问题是 由于我无
  • 有没有办法让特定的key在集群模式下定位到特定的redis实例上?

    我想让我的多锁位于不同的redis实例上 我发现redission可以指定一个实例来执行命令 但是如果该命令与key相关 则指定的实例会将命令传输到另一个实例 你能给我一些建议吗 你可以 但这并不是微不足道的 首先 Redis 在键中使用大
  • Redis是如何实现高吞吐量和高性能的?

    我知道这是一个非常普遍的问题 但是 我想了解允许 Redis 或 MemCached Cassandra 等缓存 以惊人的性能极限工作的主要架构决策是什么 如何维持连接 连接是 TCP 还是 HTTP 我知道它完全是用C写的 内存是如何管理
  • 如何使redis中的“HSET”子键“过期”?

    我需要使 Redis 哈希中所有超过 1 个月的密钥过期 这不可能 https github com antirez redis issues 167 issuecomment 2559040 为了保持 Redis 简单 https git
  • StackExchange.Redis Get 函数抛出 TimeoutException

    我在用着StackExchange Redis与 C 和StackExchangeRedisCacheClient Get函数抛出以下异常 myCacheClient Database StringGet txtKey Text myCac

随机推荐

  • 区块链数字签名、验签,以及椭圆曲线算法JS库—elliptic的使用

    目录 一 简介 二 椭圆曲线密码elliptic 1 安装elliptic和js sha3 2 Keccak256 3 签名过程 一 简介 数字签名是一种将类似现实世界中物理签名 盖章
  • 更改element button 按钮颜色

    在全局的index scss里面改 显示时按钮样式 el button inblack 需要更改的按钮类型 background 060606 important border color 060606 important color ff
  • AppDomain一——基本原理

    一 问题的提出 技术一定是为了解决某个应用场景的问题而产生的 很多时候 我们都想使用 开发 USB式 热插拔 的应用 例如 开发一个WinForm应用 并且这个WinForm应用能允许开发人员定制扩展插件 我们不能关闭程序 在把新的dll替
  • vue项目部署方式:tomcat部署和nginx部署

    LINUX 发布 VUE项目 关于vue部署 1 nginx转发 一般项目前后端分离得话 都会用nginx作为反向代理转发的 因为项目要兼容ie9 使用axios发ajax请求的时候 不能通过CORS解决跨域的问题 所以尝试部署nginx作
  • 阿里云CDN架构接入WAF应用防火墙案例实践

    文章目录 1 网站架构变化 2 配置WAF应用防火墙 2 1 配置网站接入WAF防火墙 2 2 WAF防火墙生成CNAME地址 2 3 配置WAF防火墙HTTPS证书 2 4 WAF防火墙开启HTTP回源SLB 3 配置CDN加速器回源WA
  • 高德地图之地理编码

    首先申明是地理编码呢 地理编码 又称为地址匹配 是从已知的地址描述到对应的经纬度坐标的转换过程 该功能适用于根据用户输入的地址确认用户具体位置的场景 常用于配送人员根据用户输入的具体地址找地点 既地理编码 地址转坐标 下面一步步来看怎么实现
  • 面试题——Java中的锁

    文章目录 谈谈你对线程安全的理解 1 synchronized 关键字是怎么用的 1 1 构造方法可以使用 synchronized 关键字修饰么 1 2 使用 String 作为锁对象 会有什么问题 1 3 synchronized 的底
  • 单元测试到底是什么?应该怎么做?

    一 什么是单元测试 单元测试 unit testing 是指对软件中的最小可测试单元进行检查和验证 至于 单元 的大小或范围 并没有一个明确的标准 单元 可以是一个函数 方法 类 功能模块或者子系统 单元测试通常和白盒测试联系到一起 如果单
  • 微信小程序open-data组件功能调整

    这里我开源了一个微信小程序的案例 https gitee com xiaoshixiaoran wechat applet 相关后台接口我会有空用SSM重写一遍再挂上去 由于微信小程序官网在2021 12 27号发布了组件功能调整 原来的获
  • 1-100之间的所有能被3整除的数字的和,偶数和奇数的和 ,平均值

    1 求 1 100 之间的所有平均值 需要一个 sum 和的变量 还需要一个平均值average变量 var sum 0 var average 0 for var i 1 i lt 100 i sum sum i average sum
  • 配置SourceTree

    一 从官网下载安装包 二 添加账户 选择这一个 否则看不到private仓库 用户名是自己github的用户名 密码需要在github生成 在这个位置点击 配置权限后就成功了 然后输入密码就行
  • HarmonyOS-开发避坑指南——源码下载和编译,企业级项目实战讲解

    安装文件系统打包工具 运行 mkfs vfat 如果未找到该命令 需要安装 运行 mcopy 如果未找到该命令 需要安装 sudo apt get install dosfstools mtools 官方文档说明的两个文件系统打包工具sud
  • windows终端的bash配置

    个人记录 现在json文件中加入 guid 00000000 0000 0000 ba54 000000000002 closeOnExit true commandline PROGRAMFILES git usr bin bash ex
  • 牛客网左神算法中级班学习笔记(第三章)

    本文是牛客网左神算法中级班学习笔记 分析 宏观考虑 搞两个点A B 起始都在左上角 B往右走 走到最右边就往下走 A往下走 走到最下边就往右走 A B每次一起走一步 打印A B两点连线即可 用一个Boolean控制下 交替打印顺序 publ
  • java简易聊天程序

    目录 项目结构 TCP 窗体组成 server client properties 项目结构 TCP 窗体组成 server package cn itcast chat import javax swing import java awt
  • ChatGPTBox 沉浸式的感受ChatGPT带来的快感

    ChatGPT基础功能 1 自然流畅的对话 ChatGPT通过对海量对话数据的学习 具有自然流畅的对话能力 能够与用户进行逼真的自然语言交互 2 能够理解语境 ChatGPT能够理解语境 不仅能根据上下文生成回答 还能识别当前对话的主题 更
  • LabVIEW 读写和缩放音频文件

    LabVIEW 提供了多种方式来读取和写入 WAV 格式的音频文件 完成本模块后 您将能够使用位于 Programming Graphics Sound Sound Files 中的 Simple Read 和 Simple Write 用
  • 感性是什么意思

    感性是什么意思 2005 09 25 15 55 xinghuali 分类 恋爱 有人说自己很感性 不知到底是什么意思 人在这方面分两种 一种是理性 一种就是感性 理性是很理智的那种 就是做事都依据道理 不会冲动 而感性的就是凭着感觉来的那
  • 如何让学习变得有效率

    最近一直在反思这样一个问题 为什么我的学习如此的没有效率 来提高班近三年的时间 我几乎都在全日制学习中度过 可是我的速度并不快 原因在哪 在这里学习 米老师一遍遍强调 如何学习 如何打包 全局观才是我们在这里真正应该学的 可这些在我这些年的
  • redis HyperLogLog原理

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