《玩转Redis》系列文章主要讲述Redis的基础及中高级应用。本文是《玩转Redis》系列第【10】篇,最新系列文章请前往公众号“zxiaofan”查看,或百度搜索“玩转Redis zxiaofan”即可。
本文关键字:玩转Redis、HyperLogLog原理、基数缓存、密集存储结构和稀疏存储结构;
大纲
- 伯努利试验
- HyperLogLog结构
- HyperLogLog对象头
- pfcount及基数缓存
- pfadd底层逻辑
- 密集存储结构和稀疏存储结构
- HyperLogLog引发的思考
名词解释:
1、基数:集合中不重复元素的个数;
2、HLL:HyperLogLog 的简写;
概要
上文《玩转Redis-HyperLogLog统计微博日活月活》介绍了牛逼哄哄的HyperLogLog,传入元素数量或体积非常大时,HLL所需空间固定且很小。12kb内存可计算接近 2^64 个不同元素的基数。如此厉害,怎能不继续深入探索呢?
PS:看完这篇文章,你会发现HyperLogLog能统计的基数值实际并不是 2^64 。
1. 伯努利试验
介绍HyperLogLog底层原理前,我们先了解下伯努利试验(援引百度百科)。
伯努利试验(Bernoulli experiment)是在同样的条件下重复地、相互独立地进行的一种随机试验。
其特点是该随机试验只有两种可能结果:发生或者不发生。我们假设该项试验独立重复地进行了n次,那么就称这一系列重复独立的随机试验为n重伯努利试验,或称为伯努利概型。
以抛硬币为例,每次抛硬币出现正面的概率都是50%。假设一直抛硬币,直到出现正面,则一个伯努利试验结束;
假设第1次伯努利试验抛硬币的次数为K1,第n次伯努利试验抛硬币的次数为Kn。我们记录这n次伯努利试验的最大抛硬币次数为K_max;
结合极大似然估算法,我们能得到估算关系 n = 2^(K_max)。当然,用这种方式计算的结果会有较大的偏差,HyperLogLog内部运用了 分桶平均、调和平均、偏差修正 等一系列数学优化方案,涉及较复杂的数学算法,此处暂不深入研究。
下图的计算公式引自网络:
2. HyperLogLog结构
HyperLogLog总体分为2大部分:对象头、寄存器(桶)。
HYLL | E | N/U | Cardin. |
---|---|---|---|
4 字节 | 1 字节 | 3 字节 | 8 字节 |
HLL源码中结构体定义如下:
struct hllhdr {
char magic[4]; /* "HYLL",对应源码注释中的HYLL*/
uint8_t encoding; /* HLL_DENSE or HLL_SPARSE.稀疏/密集存储结构标记,对应源码注释中的E */
uint8_t notused[3]; /* 保留3字节备用,目前未使用,值为0,对应源码注释中的N/U */
uint8_t card[8]; /* Cached cardinality(基数缓存), little endian. 对应源码注释中的Cardin,cardinality<基数> */
uint8_t registers[]; /* Data bytes. */
};
HyperLogLog底层结构有 dense(密集存储结构) 和 sparse(稀疏存储结构) 2种,无论哪种存储结构,都有一个 16 byte 的对象头(header);
3. HyperLogLog对象头(HLL header)
3.1. magic魔术字符串
从定义看,magic占用 4 byte ,存储的是“HYLL”标记,那么它究竟有什么用呢?使用过HyperLogLog的同学肯定对下面这个异常不陌生&#