当前位置: 首页 > 后端技术 > PHP

如何使用redis统计海量UV?

时间:2023-03-30 02:48:03 PHP

前言我们先来思考一个常见的业务问题:如果你负责开发和维护一个大型网站,某天老板向产品经理要网站每个页面每天的UV数据,然后让你做开发这个统计模块,你会做什么?如何实现?uv统计的常用方法和优缺点其实简单统计pv更简单,直接用redis的incr就可以了,但是uv需要重复,一天内同一个用户的多次访问请求只能算一次。这就要求每一个网页请求都需要携带用户的ID,无论是登录用户还是非登录用户,都需要一个唯一的ID来标识。把set看作是每个页面的一个独立集合集合,用来存储当天访问过这个页面的所有用户ID。当一个请求来的时候,我们用sadd把用户ID塞进去。这个collection的size可以通过scard取出来,这个数就是这个页面的UV数据。是的,这是一个非常简单的解决方案。但是,如果你的页面访问量非常大,比如某个热门页面有几千万个UV,那么你需要一个很大的setcollection来统计,很浪费空间。如果有很多这样的页面,所需的存储空间是惊人的。Hashhash和set在处理uv问题上其实很相似。使用userid作为hash的key确实可以去重,但是如果访问量大的话会消耗很多内存空间bitmapbitmap也是可以统计基数的方法。可以理解为用一个位数组来存储元素,比如01101001,表示[1,2,4,8],位图中1的个数为基数。位图也可以很方便的合并多个集合,只需要对多个数组进行异或运算即可。与Set相比,Hash也大大节省了内存。我们粗略的算一下,算一亿条数据的基数。所需内存为:100000000/8/1024/1024≈12M。虽然位图在节省空间方面有不错的表现,但是如果我们需要统计1000个对象,我们需要12G左右的内存。显然,这个结果还是不能让我们满意。在这种情况下,HyperLogLog就会来拯救我们。HyperLogLog是本节要介绍的解决方案。Redis提供了HyperLogLog数据结构来解决这个统计问题。HyperLogLog提供了一种不精确的重复数据删除计数方案。虽然不精确,但也不是很不精确,标准误差为0.81%。这个精度已经可以满足上面的UV统计要求。HyperLogLog数据结构是Redis的一种高级数据结构,非常好用,但令人惊讶的是很少有人使用它。如何使用Redis的位数组自动扩展。如果设置了某个偏移量超出现有内容范围,则位数组将自动进行零扩展。命令HyperLogLog提供了两个命令pfadd和pfcount,按照字面意思很好理解,一个是增加计数,一个是获取计数。pfadd的具体实现和set集合的sadd是一样的。如果你来一个用户ID,只需插入用户ID。pfcount和scard的用法是一样的,直接获取计数值。关键是非常节省空间。载入大量uv时,只占用12k空间。codeholeuser2(整数)1127.0.0.1:6379>pfcountcodehole(整数)2127.0.0.1:6379>pfaddcodeholeuser3(整数)1127.0.0.1:6379>pfcountcodehole(整数)3127.0.0.1:6379>userpfadd4codehole)1127.0.0.1:6379>pfcountcodehole(整数)4127.0.0.1:6379>pfaddcodeholeuser5(整数)1127.0.0.1:6379>pfcountcodehole(整数)5127.0.0.1:6379>pfaddcodeholeuser6(整数)1:6379>pfcountcodehole(integer)6127.0.0.1:6379>pfaddcodeholeuser7user8user9user10(integer)1127.0.0.1:6379>pfcountcodehole(integer)10经过简单的测试,发现还是比较准确的,并没有多少少一个。接下来,我们用脚本往里面倒更多的数据,看能不能继续准确,如果不能,差距有多大。让我们把数据增加到10w,看看总的差距有多大。公共类JedisTest{publicstaticvoidmain(String[]args){Jedisjedis=newJedis();for(inti=0;i<100000;i++){jedis.pfadd("codehole","user"+i);}longtotal=jedis.pfcount("codehole");System.out.printf("%d%d\n",100000,总计);绝地武士关闭();}}运行了大约半分钟后,我们看一下输出:>pythonpftest.py10000099723相差277,按百分比计算是0.277%。对于上面的UV统计要求,错误率不会太高。然后我们再次运行上面的脚本,相当于重复向一侧添加数据,查看输出结果,可以发现pfcount的结果没有变化,仍然是99723,说明确实有这个功能重复数据删除。