前面曾介绍过使用Redis集合构建唯一计数器,并将这个计数器用于计算网站的唯一访客IP。虽然使用Redis集合实现唯一计数器能够在功能上满足我们的要求,但是如果考虑得更长远一些,就会发现这个使用Redis集合实现的唯一计数器有一个明显的缺陷:随着被计数元素的不断增多,唯一计数器占用的内存也会越来越大;计数器越多,它们的体积越大,这一情况就会越严峻。
以计算唯一访客IP为例:
-
存储一个IPv4格式的IP地址最多需要15个字节(比如"127.234.122.101")。
-
根据网站的规模不同,每天出现的唯一IP可能会有数十万、数百万甚至数千万个。
-
为了记录网站在不同时期的访客,并进行相关的数据分析,网站可能需要持续地记录每天的唯一访客IP数量,短则几个月,长则数年。
综合以上条件,如果一个网站想要长时间记录访客的IP,就必须创建多个唯一计数器。如果网站的访客比较多,那么它创建的每个唯一计数器都将包含大量元素,并因此占用相当一部分内存。
表7-1展示了不同规模的网站在不同时间段中,存储唯一访客IP所需的最大内存。可以看到,当网站的唯一访客数量达到1000万时,网站每个月就要花费4.5GB内存去存储唯一访客的IP,对于记录唯一访客IP数量这个简单的功能来说,这样的内存开销实在让人难以接受,并且这还只是存储IPv4地址的开销,随着IPv6地址的逐渐普及,计数器将来可能需要存储IPv6地址,那时它的开销还会再翻上几倍!
表7-1 不同规模的网站在使用集合记录访客唯一IP时所需的内存数量
为了高效地解决计算唯一访客IP数量这类问题,研究人员开发了很多不同的方法,其中一个就是本章要介绍的HyperLogLog算法。
1.1 HyperLogLog简介
HyperLogLog是一个专门为了计算集合的基数而创建的概率算法,对于一个给定的集合,HyperLogLog可以计算出这个集合的近似基数:近似基数并非集合的实际基数,它可能会比实际的基数小一点或者大一点,但是估算基数和实际基数之间的误差会处于一个合理的范围之内,因此那些不需要知道实际基数或者因为条件限制而无法计算出实际基数的程序就可以把这个近似基数当作集合的基数来使用。
HyperLogLog的优点在于它计算近似基数所需的内存并不会因为集合的大小而改变,无论集合包含的元素有多少个,HyperLogLog进行计算所需的内存总是固定的,并且是非常少的。具体到实现上,Redis的每个HyperLogLog只需要使用12KB内存空间,就可以对接近:264个元素进行计数,而算法的标准误差仅为0.81%,因此它计算出的近似基数是相当可信的。
本章将对Redis中HyperLogLog的各个操作命令进行介绍,通过使用这些命令,用户可以:
-
对集合的元素进行计数。
-
获取集合当前的近似基数。
-
合并多个HyperLogLog,合并后的HyperLogLog记录了所有被计数集合的并集的近似基数。
在介绍HyperLogLog命令的同时,本章还会说明如何通过这些命令去实现一个只需要固定内存的唯一计数器,以及一个能够检测出重复信息的检查器。
1.2 PFADD:对集合元素进行计数
用户可以通过执行PFADD命令,使用HyperLogLog对给定的一个或多个集合元素进行计数:
PFADD hyperloglog element [element ...]
根据给定的元素是否已经进行过计数,PFADD命令可能返回0,也可能返回1:
-
如果给定的所有元素都已经进行过计数,那么PFADD命令将返回0,表示HyperLog-Log计算出的近似基数没有发生变化。
-
与此相反,如果给定的元素中出现了至少一个之前没有进行过计数的元素,导致HyperLogLog计算出的近似基数发生了变化,那么PFADD命令将返回1。
举个例子,通过执行以下命令,我们可以使用alphabets这个HyperLogLog对"a"、“b”、"c"这3个元素进行计数:
redis> PFADD alphabets "a" "b" "c"
(integer) 1
因为这是alphabets第一次对元素"a"、“b”、"c"进行计数,所以alphabets计算的近似基数将发生变化,并使PFADD命令返回1。
但是如果我们再次要求alphabets对元素"a"进行计数,那么这次PFADD命令将返回0,这是因为已经计数过的元素"a"并不会对alphabets计算的近似基数产生影响:
redis> PFADD alphabets "a"
(integer) 0
其他信息
复杂度:O(N),其中N为用户给定的元素数量。
版本要求:PFADD命令从Redis 2.8.9版本开始可用。
1.3 PFCOUNT:返回集合的近似基数
在使用PFADD命令对元素进行计数之后,用户可以通过执行PFCOUNT命令来获取HyperLogLog为集合计算出的近似基数:
PFCOUNT hyperloglog [hyperloglog ...]
比如,通过执行以下命令,我们可以获取到alphabets这个HyperLogLog计算出的近似基数:
redis> PFCOUNT alphabets
(integer) 3
PFCOUNT命令的返回值为3,这表示HyperLogLog算法认为alphabets目前已经计数过3个不同的元素。
另外,当用户给定的HyperLogLog不存在时,PFCOUNT命令将返回0作为结果:
redis> PFCOUNT not-exists-hyperloglog
(integer) 0
1.3.1 返回并集的近似基数
当用户向PFCOUNT传入多个HyperLogLog时,PFCOUNT命令将对所有给定的Hyper-LogLog执行并集计算,然后返回并集HyperLogLog计算出的近似基数。
比如,我们可以创建两个HyperLogLog,并分别使用这两个HyperLogLog去对两组字母进行计数:
redi