Redis 中 HyperLogLog 讲解

是什么

作用

  • 估计集合的基数(去重元素个数)
    • [a,b,c,d] 的基数是 4
    • [a,b,c,d,a] 的基数还是 4

原理

  • 概率论的伯努利实验 + 修正
    • 让我们玩一个游戏

      • 你来掷硬币,我来猜你掷了多少回合
      • 每回合规定,直到掷出反面结束,否则一直掷
        • 比如你可以一直掷正面,不结束该回合
        • 也可能第一次就是反面,回合结束
      • 我需要的唯一信息
        • 掷硬币次数最多的回合,掷了多少次硬币
    • 答案很简单

      • 若你最多掷了n次,则你掷了 (1 << n)个回合

        • 可参考论文数学证明

        • 基于正反1/2的假设

          • 掷1次概率 1/2
          • 掷2次概率 1/2 * 1/2
          • 掷3次概率 1/2 * 1/2 * 1/2
          • ......
          • 掷n次概率 1/ (1 << n)

          因为我们可以猜测你大概玩了(1 << n)个回合

    • 这个游戏大致描述了算法的思想

      具体的原理详见论文

LogLog算法原型

问题

获取一个任意元素集合的基数

前提

  • 用64位哈希值作为元素的唯一指代,哈希值中的01出现的概率各为1/2
  • 哈希值的二进制01串,表示硬币的正面反面,我们从头开始数,直到1结束,找到000...1的长度
    • 00001011010... 长度为5
    • 10101010000... 长度为1
    • ......
  • 为了准确性,我们分布式的玩掷硬币游戏,元素被哈希到多个阵营,分别玩掷硬币游戏
  • 统计各个阵营基数的和,作为总体的基数

解决步骤

  • 64位哈希值分为,14位找哈希桶,50位找000...1的长度

    • 14位找哈希桶,共有 (1 << 14) 个哈希桶

    • 50位找000...1的长度,最长是50,我们把各个桶000...1的长度的最大值存下来

    • (1 << 6) - 1 = 63 > 50,所以每个桶最少用6位即可存下来最长长度

    • (1 << 14) * 6 / 8 / 1024 = 12K,共占用12K内存,分布如下

        第0组     第1组                         .... 第16383组
      [000 000] [000 000] [000 000] [000 000] .... [000 000]
      
    • 比如

      • 处理 01001001010001 00100010010001111...
        • 前14位 01001001010001 大小为 4689,因此在第4689组
        • 后50位一开始000...1的长度为3,后面的即可忽略不计,因此第4689组值为000011 = 3
      • 处理 01001001010001 00001001010011101...
        • 前14位 01001001010001 大小为 4689,因此也在第4689组
        • 后50位一开始000...1的长度为5,比3大,因此第4689组值更改为000101 = 5
      • 处理 01001001010001 10001001010011101...
        • 前14位 01001001010001 大小为 4689,因此还在第4689组
        • 后50位一开始000...1的长度为1,比5小,不处理
  • 将所有的元素的哈希值按照如上处理

  • 假设每个哈希桶存的值为R,那么该桶的基数大致为 (1 << R)

  • 所有桶的基数和为 ∑(1 << R)

  • 为减少误差,若桶数为m,实际使用 m * (1 << R0) ,其中R0为R的平均值

  • 为进一步减少误差,常添加一个常数修正,故最终表达式为:

HyperLogLog优化

调和平均数

  • 原因

    算数平均数,易受到极端值的影响

  • 调整后

数据量小时

此时由概率论方法统计误差较大,修正为

// m 为桶数
// V 为结果为0的桶的个数
if (DV < (5 / 2) * m && V != 0) {
  DV = m * log(m/V)
} 

常数的选择

若m为桶数,定义p

Constant 为

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;
}

......

posted @ 2021-06-09 22:06  Jamgun  阅读(130)  评论(0编辑  收藏  举报