Hyperloglog
一、类型概述
HyperLogLog 是一种用于基数统计的算法。所谓基数,就是一个集合中不重复元素的个数。可以理解为一个极简内存的Set。
它的特点是:
- 极其节省内存:无论你统计的数据集有多大,一个 HyperLogLog 结构只需要占用最多 12 KB 的内存。
- 非精确计数:它是一个概率算法,给出的计数结果是一个近似值,标准误差约为 0.81% 。这意味着对于百万级别的计数,结果可能只相差几千。
- 只能计数,不能取出元素,也无法删除元素:你无法像 Set 那样获取里面存储了哪些具体的元素,只能获取基数的估算值。
核心思想:HyperLogLog 通过一个哈希函数将输入元素映射成一个位串,通过统计位串中前导零(或后导零)的最大数目 k,来估算基数的值,估算公式大致为 2^k。为了降低方差,它使用了“分桶平均”的思想,将数据分成多个桶,最终合并所有桶的估算值。
二、基本方法
PF 是 HyperLogLog 算法的发明者 Philippe Flajolet 的名字缩写,以示纪念。
1.添加元素 PFADD key element [element ...]
127.0.0.1:6379> pfadd num 1 2 3 4 5 6
(integer) 1
127.0.0.1:6379> pfadd num 3 #如果添加重复元素,会返回0
(integer) 0
127.0.0.1:6379> pfadd num 1 7 #如果有一个不是重复元素,也会返回1
(integer) 1
2.获取基数PFCOUNT key [key ...]
- 当传入一个 key 时,返回该 HyperLogLog 的基数。
- 当传入多个 key 时,会将它们合并(Union) 后,再计算合并后的总基数。
127.0.0.1:6379> pfcount num
(integer) 7
127.0.0.1:6379> pfadd count 4 5 6 7 8 9 10
(integer) 1
127.0.0.1:6379> pfcount num count
(integer) 10
3.把多个key合并起来添加到另一个key中PFMERGE destkey sourcekey [sourcekey ...]
127.0.0.1:6379> pfmerge nc num count
OK
127.0.0.1:6379> pfcount nc
(integer) 10
三、内部实现
Redis 的 HyperLogLog 实现基于论文的优化,其核心结构如下:
-
哈希函数:使用 64 位的哈希函数。
-
分桶(Register) :
- 将 64 位的哈希值分成两部分。
- 前
p位(在 Redis 中p=14)用作桶的索引。这意味着有2^14 = 16384个桶。 - 后
64 - p = 50位用于计算该桶中前导零的个数。
-
存储:
- 每个桶只需要存储一个数字(前导零的数量 + 1),这个数字最大不会超过 50。
- 在 Redis 实现中,每个桶用 6 个 bit 来存储就足够了(因为
2^6 = 64 > 50)。 - 因此,总的内存占用是:
16384 buckets * 6 bits per bucket / 8 bits per byte = 12288 bytes = 12 KB。
四、应用场景
HyperLogLog 的“牺牲精度换取空间”的特性,使其在以下场景中极具价值:
- 大规模网站的独立访客(UV)统计
- 这是最经典的场景。统计每天、每周或每月的独立 IP 或用户 ID 访问量。使用 Set 会消耗巨大内存,而 HyperLogLog 只需要 12 KB,并且误差在可接受范围内。
- 统计搜索关键词的不同个数
- 搜索引擎或大型网站可以用它来估算一天内有多少个不同的搜索查询。
- 统计在线游戏/App 中的不同用户/事件
- 例如,统计某个活动期间参与的不同玩家数量,或者某个新功能被多少不同用户使用过。
- 数据库查询优化
- 在执行某些复杂查询前,先用 HyperLogLog 估算一下不同值的数量(例如
COUNT(DISTINCT ...)),如果数量巨大,可以考虑是否优化查询策略。
- 在执行某些复杂查询前,先用 HyperLogLog 估算一下不同值的数量(例如