【Redis】16. HyperLogLog — 亿级别的基数统计

场景

老板让你统计一下网站的PV(浏览量,用户访问一次自增一次)和UV((独立访客,每个用户每天只记录一次)。

PV 可以很好解决,我们可以使用redis 计数器,当天日期为key,每收到一个请求,INCRBY 就自增加一,最后结果就是浏览量了。

UV,一个用户每天访问多次,但只能记录一次,是不是可以用缓存的 SET 集合,SET 可以去重,但是问题来了,随着访问量的增加,如果用 SET 集合,这是相当浪费内存的,不仅如此,如果对set集合聚合统计,实现也很复杂。

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时, 计算基数所需的空间总是固定的、并且是很小的。

使用位图,位图是以bit 为存储单位,是int类型的1/32,存储一亿级别的数据, 大约需要的内存:100_000_000/ 8/ 1024/ 1024 ≈ 12 M ,然而统计一个对象的基数值需要12M,如果统计10000个对象,就需要将近120G,同样不能广泛用于大数据场景。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计 算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那 样,返回输入的各个元素。

小知识: 什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

应用场景

基数不大,数据量不大就用不上,会有点大材小用浪费空间 有局限性,就是只能统计基数数量,而没办法去知道具 体的内容是什么。

统计注册 IP 数

统计每日访问 IP 数

统计页面实时 UV 数

统计在线用户数

统计用户每天搜索不同词条的个数

统计真实文章阅读数

HyperLogLog 的使用

PFADD key element [element ...]:添加指定元素到 HyperLogLog 中

PFCOUNT key [key ...] :返回给定 HyperLogLog 的基数估算值

PFMERGE destkey sourcekey [sourcekey ...]:将多个 HyperLogLog 合并为一个 HyperLogLog

127.0.0.1:6379> PFADD users 1
(integer) 1
127.0.0.1:6379> PFADD users 2
(integer) 1
127.0.0.1:6379> PFADD users 3
(integer) 1
127.0.0.1:6379> PFADD users 4 5
(integer) 1
127.0.0.1:6379> PFADD sql "mysql" "orcal"
(integer) 1
127.0.0.1:6379> PFMERGE all users sql
OK
127.0.0.1:6379> PFCOUNT all
(integer) 7

java 代码

public class JedisTest {
  public static void main(String[] args) {
    for (int i = 0; i < 100000; i++) {
      jedis.pfadd("books", "book"+i);
    }
    long total = jedis.pfcount("books");
    System.out.printf("%d %d\n", 100000, total);
    jedis.close();
  }
}

发现 10 万条数据只差了 277,按照百分比误差率是 0.277%,误差率还是很低的。
在这里插入图片描述

总结

HyperLogLog是一种算法,并非redis独有, 目的是做基数统计,故不是集合,不会保存元数据,只记录数量而不是数值。 耗空间极小,支持输入非常体积的数据量。

核心是基数估算算法,主要表现为计算时内存的使用和数据合并的处理。最终数值存在一定误差 redis中每个hyperloglog key占用了12K的内存用于标记基数(官方文档) pfadd命令并不会一次性分配12k内存,而是随着基数的增加而逐渐增加内存分配;而pfmerge操作则会将sourcekey合并 后存储在12k大小的key中,这由hyperloglog合并操作的原理(两个hyperloglog合并时需要单独比较每个桶的值)可以 很容易理解。

误差说明:基数估计的结果是一个带有 0.81% 标准错误(standard error)的近似值。是可接受的范围 Redis 对 HyperLogLog 的存储进行了优化,在计数比较小时,它的存储空间采用稀疏矩阵存储,空间占用很小,仅仅在 计数慢慢变大,稀疏矩阵占用空间渐渐超过了阈值时才会一次性转变成稠密矩阵,才会占用 12k 的空间。

具体原理看了很多遍,还是不明白,可参考微信文章:神奇的HyperLoglog解决统计问题

你可能感兴趣的:(Redis)