基本介绍
HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
基数是什么?
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
去重复统计功能的基数估计算法-就是HyperLogLog(用于统计一个集合中不重复的元素个数,就是对集合去重复后剩余元素的计算) ,需要注意存在误差(准确率来换取空间,误差仅仅只是0.81%左右)
全集i={1,2,3,4,5,6,7,8,8,9,9,5}
去掉重复的内容
基数={1,2,3,4,5,6,7,8,9}
基本命令
序号 | 命令及描述 |
1 | PFADD key element [element ...] 添加指定元素到 HyperLogLog 中。 |
2 | PFCOUNT key [key ...] 返回给定 HyperLogLog 的基数估算值。 |
3 | PFMERGE destkey sourcekey [sourcekey ...] 将多个 HyperLogLog 合并为一个 HyperLogLog |
pfadd
Pfadd 命令将所有元素参数添加到 HyperLogLog 数据结构中。
redis> PFADD mykey a b c d e f g h i j (integer) 1 redis> PFCOUNT mykey (integer) 10
返回值:整型,如果至少有个元素被添加返回 1,否则返回 0。
pfcount
Pfcount 命令返回给定 HyperLogLog 的基数估算值。
语法:PFCOUNT key [key ...]
redis> PFADD hll foo bar zap (integer) 1 redis> PFADD hll zap zap zap (integer) 0 redis> PFADD hll foo bar (integer) 0 redis> PFCOUNT hll (integer) 3 redis> PFADD some-other-hll 1 2 3 (integer) 1 redis> PFCOUNT hll some-other-hll (integer) 6
返回值:整数,返回给定 HyperLogLog 的基数值,如果多个 HyperLogLog 则返回基数估值之和。
pgmerge
Pgmerge 命令将多个 HyperLogLog 合并为一个 HyperLogLog ,合并后的 HyperLogLog 的基数估算值是通过对所有 给定 HyperLogLog 进行并集计算得出的。
redis> PFADD hll1 foo bar zap a (integer) 1 redis> PFADD hll2 a b c foo (integer) 1 redis> PFMERGE hll3 hll1 hll2 OK redis> PFCOUNT hll3 (integer) 6
返回值:返回 OK。
统计访客应用场景
什么是UV、PV、DAU、MAU
①. UV:Unique Visitor,独立访客,一般理解为客户端IP(需要去重考虑)
②. PV:Page View,页面浏览量(不用去重)
③. DAU:日活跃用户量(登录或者使用了某个产品的用户数(去重复登录的用户))
④. MAU:MonthIy Active User,月活跃用户量
场景说明
淘宝、天猫首页的UV,平均每天是1~1.5个亿左右
每天存1.5个亿的IP,访问者来了后先去查是否存在,不存在加入
一个用户一天内的多次访问只能算作一次
java代码示例
@Service @Slf4j public class HyperLogLogService { @Resource private RedisTemplate redisTemplate; /** * 模拟有用户来点击首页,每个用户就是不同的ip,不重复记录,重复不记录 */ @PostConstruct public void init() { log.info("------模拟后台有用户点击,每个用户ip不同"); //自己启动线程模拟,实际上产不是线程 new Thread(() -> { String ip = null; for (int i = 1; i <=200; i++) { Random random = new Random(); ip = random.nextInt(255)+"."+random.nextInt(255)+"."+random.nextInt(255)+"."+random.nextInt(255); Long hll = redisTemplate.opsForHyperLogLog().add("hll", ip); log.info("ip={},该ip访问过的次数={}",ip,hll); //暂停3秒钟线程 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } },"t1").start(); } }
@RestController @Slf4j public class HyperLogLogController { @Resource private RedisTemplate redisTemplate; @ApiOperation("获得ip去重复后的首页访问量,总数统计") @RequestMapping(value = "/uv",method = RequestMethod.GET) public long uv() { //pfcount return redisTemplate.opsForHyperLogLog().size("hll"); } }
通过牺牲准确率来换取空间,对于不要求绝对准确率的场景下可以使用,因为概率算法不直接存储数据本身 通过一定的概率统计方法预估基数值,同时保证误差在一定范围内,由于又不储存数据故此可以大大节约内存 HyperLogLog就是一种概率算法的实现