我们一直都知道,redis几大常用数据结构,字符串、散列、列表、集合、有序集合。其实后来Redis做了很多补充,其中之一就是HyperLogLog,另外的还有GEO(地理位置),是3.2版本加的。
这里我们就来简单介绍下HyperLogLog结构。
先说用处:这个结构可以非常省内存的去统计各种计数,比如注册ip数、每日访问IP数、页面实时UV(PV肯定字符串就搞定了)、在线用户数等。
这里看到所有的用处都是xxx数,所以这个数据结构的特点就是,可以比较准确的估算出你要统计的数量,但是却无法知道统计的详细内容。比如统计每日访问IP数,可以获取当时访问过的IP总数量,但是没法知道这些IP都是什么。
有得必有失,当然你要统计上面提到的那些内容,可以用集合来处理,这样可以知道数量,也能获得所有的详细列表。但是一个大型的网站,每天IP比如有100万个呢,我们粗算一个IP消耗15字节,那么100万个IP就是15M,如果1千万,就是150M。
再来看看我们的HyperLogLog,在Redis中每个键占用的内容都是12K,理论存储近似接近2^64个值,不管存储的内容是什么。12K,知道这个数据结构的作用了吧。这也是为什么他不能知道里面的详细内容了。这是一个基于基数估算的算法,只能比较准确的估算出基数,可以使用少量固定的内存去存储并识别集合中的唯一元素。而且这个估算的基数并不一定准确,是一个带有 0.81% 标准错误(standard error)的近似值。
HyperLogLog结构,在范围允许的情况下无论多少值,都只会占用12K内存。
这样比如我们把每日IP记录下来,假设每天有一亿个IP访问,如果使用集合的话,一天的内存使用就是1.5G,假设我们存储一个月的记录,就需要45G容量。但是使用HyperLogLog的话,一天12K,一个月360K。如果我们不需要知道IP具体信息的话,完全可以把这些记录留在内存一年、或者不删都行。如果需要,我们也会把所有的IP访问记录通过其他途径存储起来。把每天的信息存储起来,我们可以计算每月IP总数(MERGE),一年的IP总数等(去重)。
下面介绍一下HyperLogLog的命令,其实他和集合的命令比较像,只是命令少,不能获取列表而已。另外这个数据结构需要2.8.9及以上的版本才能使用哦~
在执行这个命令之后,HyperLogLog内部的结构会被更新,并有所反馈,如果执行完之后HyperLogLog内部的基数估算发生了变化,那么就会返回1,否则(认为已经存在)就返回0。
这个命令还有一个比较神器的就是可以只有键,没有值,这样的意思就是只是创建空的键,不放值。
如果这个键存在,不做任何事情,返回0;不存在的话就创建,并返回1。
这个命令的时间复杂度为O(1),所以就放心用吧~
命令例子:
redis> PFADD ip:20160929 "1.1.1.1" "2.2.2.2" "3.3.3.3" (integer) 1 redis> PFADD ip:20160929 "2.2.2.2" "4.4.4.4" "5.5.5.5" # 存在就只加新的 (integer) 1 redis> PFCOUNT ip:20160929 # 元素估计数量没有变化 (integer) 5 redis> PFADD ip:20160929 "2.2.2.2" # 存在就不会增加 (integer) 0
其实我们发现在少的时候还是挺准的,哈哈。
其实在上面的学习中我们已经用过这个了,这里再来介绍下。
当命令作用于单个键的时候,返回这个键的基数估算值。如果键不存在,则返回0。
当作用于多个键的时候,返回这些键的并集估算值。类似于把这些键都合并了之后,在调用这个命令输出。
这个命令在作用于单个值的时候,时间复杂度为O(1),并且具有非常低的平均常数时间;在作用于N个值的时候,时间复杂度为O(N),这个命令的常数复杂度会比较低些。
命令例子:
redis> PFADD ip:20160929 "1.1.1.1" "2.2.2.2" "3.3.3.3" (integer) 1 redis> PFCOUNT ip:20160929 (integer) 3 redis> PFADD ip:20160928 "1.1.1.1" "4.4.4.4" "5.5.5.5" (integer) 1 redis> PFCOUNT ip:20160928 ip:20160929 (integer) 5
合并(merge)多个HyperLogLog为一个HyperLogLog。其实这个也很好理解,而合并后的估算基数也近似于所有HyperLogLog估算基数的并集。
这个命令的第一个参数为目标键,剩下的参数为要合并的HyperLogLog。命令执行时,如果目标键不存在,则创建后再执行合并。
这个命令的时间复杂度为O(N),其中N为要合并的HyperLogLog的个数。不过这个命令的常数时间复杂度比较高。
命令例子:
redis> PFADD ip:20160929 "1.1.1.1" "2.2.2.2" "3.3.3.3" (integer) 1 redis> PFADD ip:20160928 "1.1.1.1" "4.4.4.4" "5.5.5.5" (integer) 1 redis> PFMERGE ip:201609 ip:20160928 ip:20160929 OK redis> PFCOUNT ip:201609 (integer) 5