一、应用场景

需要统计网页每天的用户访问量的数据,同一个用户一天之内的多次访问请求只能计数一次。

二、实现方案

方案一:使用set数据类型

最简单想到的实现就是:为每一个页面一个独立的 set 集合来存储所有当天访问过此页面的用户 ID。当一个请求过来时,我们使用 sadd 将用户 ID 塞进去就可以了。通过 scard 可以取出这个集合的大小,这个数字就是这个页面的用户访问量数据。

redis 访问量高 redis记录访问量_redis 访问量高


优点:简单,容易实现,查询也是非常方便,数据准确性非常高。

缺点:占用内存过大,随着key的增多,性能也会下降。

方案二:使用bitmap数据类型

我们知道,对于一个32位的int,如果我们只用来记录id,那么只能够记录一个用户,但如果我们转成2进制,每位用来表示一个用户,那么我们就能够一口气表示32个用户,空间节省了32倍!对于有大量数据的场景,如果我们使用bitset,那么,可以节省非常多的内存。对于没有登陆的用户,我们也可以使用哈希算法,把对应的用户标识哈希成一个数字id。bitset非常的节省内存,假设有1亿个用户,也只需要100000000/8/1024/1024约等于12兆内存。

Redis已经为我们提供了SETBIT的方法,使用起来非常的方便,我们可以看看下面的例子,我们在item页面可以不停地使用SETBIT命令,设置用户已经访问了该页面,也可以使用GETBIT的方法查询某个用户是否访问。最后我们通过BITCOUNT可以统计该网页每天的访问数量。

redis 访问量高 redis记录访问量_数据_02


优点:占用内存更小,查询方便,可以指定查询某个用户,数据可能略有瑕疵,对于非登陆的用户,可能不同的key映射到同一个id,否则需要维护一个非登陆用户的映射,有额外的开销。

缺点:如果用户非常的稀疏,那么占用的内存可能比方法一更大。

方案三:使用HyperLogLog数据类型

HyperLogLog 提供不精确的去重计数方案,标准误差大概在 0.81%,这样的精确度已经可以满足上面的用户访问量的统计需求了。

redis 访问量高 redis记录访问量_java_03


优点:占用内存极小,对于一个key,只需要12kb。对于拼多多这种超多用户的特别适用。

缺点:查询指定用户的时候,可能会出错,毕竟存的不是具体的数据。总数也存在一定的误差。

三、HyperLogLog实现原理

对于HyperLogLog一个Key只需要12kb,是不是非常神奇,我们来看看它的原理。

基数
基数就是指一个集合中不同值的数目,比如[a,b,c,d]的基数就是4,[a,b,c,d,a]的基数还是4,因为a重复了一个,不算。基数也可以称之为Distinct Value,简称DV。下文中可能有时候称呼为基数,有时候称之为DV,但都是同一个意思。HyperLogLog算法就是用来计算基数的。

HyperLogLog算法来源于论文《HyperLogLog the analysis of a near-optimal cardinality estimation algorithm》(http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf),可以使用固定大小的字节计算任意大小的DV。

参考文献

  1. 探索HyperLogLog算法(含Java实现)