Redis学习三:HyperLogLog

本文详细介绍了Redis中的HyperLogLog数据结构,包括其作为基数估算算法的原理、应用场景以及在Redis中的操作命令如PFADD、PFCOUNT和PFMERGE。HyperLogLog以极小的内存占用提供近似的基数统计,适用于对精度要求不高但需处理大量唯一元素的场景,例如统计页面UV、注册IP等。
摘要由CSDN通过智能技术生成

系列文章目录

一、HyperLogLog是什么?

HyperLogLog并不是一种新的数据结构(实际类型为字符串类 型),而是一种基数估算算法。所谓基数估算,就是估算在一批数据中,不重复元素的个数有多少。通过HyperLogLog可以利用极小的内存空间完成独立总数的统计,数据集可以是IP、Email、ID等。

二、HyperLogLog应用场景

根据HyperLogLog的特性可知,该技术可应用于一些要求统计基数且准确性可相对低的需求。由于HyperLogLog存在误差(官网给出的说法是0.81%的误差),因此当要使用HyperLogLog来做基数计算的时候,需要考虑业务需求是否能接受轻微的误差。当然这些误差也换来了内存的损耗少的好处。

因此,HyperLogLog 主要的应用场景就是进行基数统计:

  1. 统计某用户的某文章的被阅读次数,可按每天、每月、每年等自定义时间进行统计。
  2. 对于 Google 主页面而言,同一个账户可能会访问 Google 主页面多次。于是,在诸多的访问流水中,如何计算出 Google 主页面每天被多少个不同的账户访问过就是一个重要的问题。那么对于 Google 这种访问量巨大的网页而言,其实统计出有十亿 的访问量或者十亿零十万的访问量其实是没有太多的区别的,因此,在这种业务场景下,为了节省成本,其实可以只计算出一个大概的值,而没有必要计算出精准的值。
  3. 统计注册 IP 数
  4. 统计每日访问 IP 数
  5. 统计页面实时 UV 数
  6. 统计在线用户数
  7. 统计用户每天搜索不同词条的个数
  8. …更多

三、Redis中使用HyperLogLog

3.1 PFADD

PFADD
最早可用版本:2.8.9
时间复杂度:O(1)
用法:PFADD key element [element …]
将参数中的元素都加入指定的HyperLogLog数据结构中,这个命令会影响基数的计算。如果执行命令之后,基数估计改变了,就返回1;否则返回0。如果指定的key不存在,那么就创建一个空的HyperLogLog数据结构。该命令也支持不指定元素而只指定键值,如果不存在,则会创建一个新的HyperLogLog数据结构,并且返回1;否则返回0

代码示例:

redis> PFADD  databases  "Redis"  "MongoDB"  "MySQL"
(integer) 1

redis> PFCOUNT  databases
(integer) 3

redis> PFADD  databases  "Redis"    # Redis 已经存在,不必对估计数量进行更新
(integer) 0

redis> PFCOUNT  databases    # 元素估计数量没有变化
(integer) 3

redis> PFADD  databases  "PostgreSQL"    # 添加一个不存在的元素
(integer) 1

redis> PFCOUNT  databases    # 估计数量增一
4

集成Springboot使用:

	/**
     *  将参数中的元素都加入指定的HyperLogLog数据结构中,这个命令会影响基数的计算。
     *  如果执行命令之后,基数估计改变了,就返回1;否则返回0。如果指定的key不存在,
     *  那么就创建一个空的HyperLogLog数据结构。该命令也支持不指定元素而只指定键值,
     *  如果不存在,则会创建一个新的HyperLogLog数据结构,并且返回1;否则返回0
     * @param key key值
     * @param value value
     * @return 如果 HyperLogLog 的内部被修改了,那么返回 1,否则返回 0
     */
    public Long pfAdd(String key, Object... value){
        return redisTemplate.opsForHyperLogLog().add(key, value);
    }

3.2 PFCOUNT

PFCOUNT
最早可用版本:2.8.9
时间复杂度:O(1),对于多个比较大的key的时间复杂度是O(N)
用法:PFCOUNT key [key …]
对于单个key,该命令返回的是指定key的近似基数,如果变量不存在,则返回0。
对于多个key,返回的是多个HyperLogLog并集的近似基数,它是通过将多个HyperLogLog合并为一个临时的HyperLogLog,然后计算出来的。
HyperLogLog可以用很少的内存来存储集合的唯一元素。(每个HyperLogLog只有12K加上key本身的几个字节)
HyperLogLog的结果并不精准,错误率大概在0.81%。
需要注意的是:该命令会改变HyperLogLog,因此使用8个字节来存储上一次计算的基数。所以,从技术角度来讲,PFCOUNT是一个写命令
性能问题
即使理论上处理一个存储密度大的HyperLogLog需要花费较长时间,但是当指定一个key时,PFCOUNT命令仍然具有很高的性能。这是因为PFCOUNT会缓存上一次结算的基数,而多数PFADD命令不会更新寄存器。所以才可以达到每秒上百次请求的效果。

当处理多个key时,最耗时的一步是合并操作。而通过计算出来的并集的基数是不能缓存的。所以多个key的处理速度一般在毫秒级。

代码示例:

redis> PFADD  databases  "Redis"  "MongoDB"  "MySQL"
(integer) 1

redis> PFCOUNT  databases
(integer) 3

redis> PFADD  databases  "Redis"    # Redis 已经存在,不必对估计数量进行更新
(integer) 0

redis> PFCOUNT  databases    # 元素估计数量没有变化
(integer) 3

redis> PFADD  databases  "PostgreSQL"    # 添加一个不存在的元素
(integer) 1

redis> PFCOUNT  databases    # 估计数量增一
4

集成Springboot使用:

	/**
     * 对于单个key,该命令返回的是指定key的近似基数,如果变量不存在,则返回0。
     *
     * 对于多个key,返回的是多个HyperLogLog并集的近似基数,它是通过将多个HyperLogLog合并为一个临时的HyperLogLog,然后计算出来的。
     *
     * HyperLogLog可以用很少的内存来存储集合的唯一元素。(每个HyperLogLog只有12K加上key本身的几个字节)
     *
     * HyperLogLog的结果并不精准,错误率大概在0.81%。
     *
     * 需要注意的是:该命令会改变HyperLogLog,因此使用8个字节来存储上一次计算的基数。所以,从技术角度来讲,PFCOUNT是一个写命令
     * @param key key值(可多个)
     * @return 添加的唯一元素的近似数量.
     */
    public Long pfCount(String... key){
        return redisTemplate.opsForHyperLogLog().size(key);
    }

3.3 PFMERGE

最早可用版本:2.8.9
时间复杂度:O(N),N是要合并的HyperLogLog的数量
用法:PFMERGE destkey sourcekey [sourcekey …]
合并多个HyperLogLog,合并后的基数近似于合并前的基数的并集(observed Sets)。计算完之后,将结果保存到指定的key。
除了这三个命令,我们还可以像操作String类型的数据那样,对HyperLogLog数据使用SET和GET命令。

代码示例:

redis> PFADD  nosql  "Redis"  "MongoDB"  "Memcached"
(integer) 1

redis> PFADD  RDBMS  "MySQL" "MSSQL" "PostgreSQL"
(integer) 1

redis> PFMERGE  databases  nosql  RDBMS
OK

redis> PFCOUNT  databases
(integer) 6

集成Springboot使用:

	/**
     *
     * 将多个 HyperLogLog 合并(merge)为一个 HyperLogLog , 合并后的 HyperLogLog 的基数接近于所有输入 HyperLogLog 的可见集合(observed set)的并集.
     *
     * 合并得出的 HyperLogLog 会被储存在目标变量(第一个参数)里面, 如果该键并不存在, 那么命令在执行之前, 会先为该键创建一个空的.
     * @param key key值
     * @param otherKey  另外的key值(集合)多个
     * @return 返回并集个数
     */
    public Long pfMerge(String key,String... otherKey){
        return redisTemplate.opsForHyperLogLog().union(key, otherKey);
    }

3.4 测试demo

		String key1 = "key1";
        String key2 = "key2";
        String key3 = "ley3";
        Long addCount1 = redisUtils.pfAdd(key1, "uuid-1", "uuid-2", "uuid-3");
        Long addCount2 = redisUtils.pfAdd(key2, "uuid-2", "uuid-3", "uuid-4");
        System.out.println("addCount1:"+ addCount1);
        System.out.println("addCount2:"+ addCount2);

        //获取键值为key1的基数
        Long count1 = redisUtils.pfCount(key1);
        //获取键值为key2的基数
        Long count2 = redisUtils.pfCount(key2);
        //获取键值为key1、key2的基数
        Long count12 = redisUtils.pfCount(key2,key1);
        System.out.println("count1 :"+count1);
        System.out.println("count2 :"+count2);
        System.out.println("count12 :"+count12);

        //合并key1与key2的基数到key3
        Long addCount3 = redisUtils.pfMerge(key3, key1, key2);
        System.out.println("addCount3:"+addCount3);

        //获取键值为key3的基数
        Long count3 = redisUtils.pfCount(key3);
        System.out.println("count3 :"+count3);

结果为:

addCount1:1
addCount2:1
count1 :3
count2 :3
count12 :4
addCount3:4
count3 :4

总结

HyperLogLog内存占用量非常小,但是存在错误率,开发者在进行 数据结构选型时只需要确认如下两条即可:

  • 只为了计算独立总数,不需要获取单条数据。
  • 可以容忍一定误差率,毕竟HyperLogLog在内存的占用量上有很大 的优势。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值