基本介绍
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 数据结构中。
代码语言:javascript复制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 的基数估算值。
代码语言:javascript复制语法: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 进行并集计算得出的。
代码语言:javascript复制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代码示例
代码语言:javascript复制@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();
}
}
代码语言:javascript复制@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就是一种概率算法的实现