在开发中,经常遇到要统计某个页面的访问用户数UV。我们很容易想到使用Redis Set来统计,当用户数不多的时候确实可以,本人开发的项目中要统计一个月面一个月用户的访问数量,月活跃用户数量在千万级别以上,如果用Set即使使用Hash打散存储key也需要700-800MB的redis内存,这明显很浪费.这种场景中使用HyperLogLog进行去重统计是非常合适的.
HyperLogLog算法是一种非常巧妙的近似统计海量去重元素数量的算法。它内部维护了 16384 个桶(bucket)来记录各自桶的元素数量。当一个元素到来时,它会散列到其中一个桶,以一定的概率影响这个桶的计数值。因为是概率算法,所以单个桶的计数值并不准确,但是将所有的桶计数值进行调合均值累加起来,结果就会非常接近真实的计数值。
HyperLogLog只有三种种命令格式
pfadd 添加指定元素到 HyperLogLog 中。
pfcount 返回给定 HyperLogLog 的基数估算值。
pfmerge 将多个 HyperLogLog 合并为一个 HyperLogLog
注意并不能判断元素是否存在。
pfcount的是基于概率统计论的,有一定的误差,在0.81%左右。但是在UV统计中已经足够了。
下面往HyperLogLog 增加数据,增加数据,统计到数据不一致时停止
import redis
HOST = '127.0.0.1'
PORT = '6379'
PASSWORD = ''
pool = redis.ConnectionPool(host=HOST, port=PORT, password=PASSWORD, max_connections=1024)
conn = redis.Redis(connection_pool=pool)
for i in range(1000):
conn.pfadd("codeHole","1000%d" %i)
total = conn.pfcount("codeHole")
if total != i+1:
print("total:%d real:%d" % (total,i+1))
break
conn.delete("codeHole")
conn.close()
输出: total:104 real:105
将测试数据增加到10000看看统计结果
import redis
HOST = '127.0.0.1'
PORT = '6379'
PASSWORD = ''
pool = redis.ConnectionPool(host=HOST, port=PORT, password=PASSWORD, max_connections=1024)
conn = redis.Redis(connection_pool=pool)
for i in range(10000):
conn.pfadd("codeHole","1000%d" %i)
total = conn.pfcount("codeHole")
print(total)
conn.delete("codeHole")
conn.close()
输出9921
参考资料:《Redis深度历险:核心原理与应用实践》
Redis HyperLogLog 内部数据结构分析