wordpress 站点错误,做网站有前景吗,wordpress判断手机,成品网站价格表使用 Redis 统计网站 UV 的方法(概率算法)
文章目录 前言思路HyperLogLog 使用 Redis 命令操作使用 Java 代码操作 HyperLogLog 实现原理及特点使用 Java 实现 HyperLogLog小结
前言
网站 UV 就是指网站的独立用户访问量Unique Visitor#xff0c;即相同用户的多次访问需要…使用 Redis 统计网站 UV 的方法(概率算法)
文章目录 前言思路HyperLogLog 使用 Redis 命令操作使用 Java 代码操作 HyperLogLog 实现原理及特点使用 Java 实现 HyperLogLog小结
前言
网站 UV 就是指网站的独立用户访问量Unique Visitor即相同用户的多次访问需要去重。
思路
提到 UV 去重猜大家都会想到Set集合类。
使用Set集合是一个不错的办法Set里面存储用户的id。每一个用户访问页面的时候我们直接把id存入Set最终获取Set的size即可。问题就是Set的容量需要设置多大呢如果应用是分布式的是否需要合并操作第一个问题其实可以通过计算来估计如果用户量上亿的话存储空间也是需要非常大的第二个问题其实可以通过 Redis、DB 等存储如 Redis 的Set结构DB 的唯一键。我们上面提到的 DB 也是一种解决方案不过写入量很大时数据库压力会比较大。用户如果很多则row也相应的多且可能需要对每天的数据进行分表。在用户访问量小的情况下可以采用该处理方式。
上面两种方式虽然可以实现统计网站 UV 的功能但是一个比较占用内存一个比较占用数据库资源。那我们该如何规避这两个问题呢在这里我们就介绍另外一种实现方法即使用 Redis 里面的HyperLogLog结构且仅占用12k的空间。
HyperLogLog
HyperLogLog的使用比较简单实现略复杂。我们先看一下如何利用HyperLogLog来进行页面 UV 的统计。
使用 Redis 命令操作
# 添加元素
127.0.0.1:6379 pfadd user zhangsan lisi wangwu
# 添加成功返回1添加失败返回0
(integer) 1
# 统计数量
127.0.0.1:6379 pfcount user
# 返回现在数量
(integer) 3
# 再生成一个pfkey
127.0.0.1:6379 pfadd user2 zhangsan2 lisi2 wangwu
(integer) 1
127.0.0.1:6379 pfcount user2
(integer) 3
# pfmerge会将后面pfkey中的值合并到前面的pfkey中
127.0.0.1:6379 pfmerge user2 user
OK
# 查看merge后的user2
127.0.0.1:6379 pfcount user2
(integer) 5使用 Java 代码操作
import org.springframework.data.redis.core.HyperLogLogOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
Service
public class RedisService {Resourceprivate RedisTemplate String, String redisTemplate;/*** 记录用户访问** param user*/public long statistic(String Key, String user) {HyperLogLogOperationsString,StringhyperLogLogredisTemplate.opsForHyperLogLog();return hyperLogLog.add(Key, user);}/*** 统计当前 UV** return*/public long size(String Key) {HyperLogLogOperationsString,StringhyperLogLogredisTemplate.opsForHyperLogLog();return hyperLogLog.size(Key);}/*** 删除当前 key*/public void clear(String Key) {HyperLogLogOperations String,StringhyperLogLogredisTemplate.opsForHyperLogLog();hyperLogLog.delete(Key);}
}HyperLogLog 实现原理及特点
原理其实这是个概率问题。举个 Java 的例子我们每次将一个字符串放入HyperLogLog其实是把字符串转换成了一个值可以把它当成hash值将这个值转换成 2 进制从后向前看第一个 1 出现的位置。那么 1 出现在第三个位置的时候xxxx x100概率是多少呢(1/2)^31/8也就是大概有八个数字进到这个数据结构时第一个 1 曾出现在第三个的位置的可能会比较大所以我们只需要维护一个 1 出现位置的最大值暂且称之为max position我们就可以知道整个HyperLogLog数量是多少了。去重我们上面讲到hash值其实整个算法就是将一个固定的value固定的映射成一个数字就可以解决重复的问题了。如zhangsan对应8那么max position4再来一个zhangsan还是对应8则max position不变。特点因为是概率问题总会出现不准确的情况所以你在使用HyperLogLog时可以将user数量设置大一些如 100W。但是其结果有可能你看到的是不到 100W也有可能计算出来的 UV 还比 100W 大。
使用 Java 实现 HyperLogLog
public class HyperLogLogSelf {static class BitKeeper {private int maxBits;public void random() {// 这里的随机数可以当成一个对象的hashCode。// long value new Object().hashCode() ^ (2 32);long value ThreadLocalRandom.current().nextLong(2L 32);int bits lowZeros(value);if (bits this.maxBits) {this.maxBits bits;}}/*** 低位有多少个连续0* 思路上 ≈ 倒数第一个1的位置** param value* return*/private int lowZeros(long value) {int i 1;for (; i 32; i) {if (value i i ! value) {break;}}return i - 1;}}static class Experiment {private int n;private BitKeeper keeper;public Experiment(int n) {this.n n;this.keeper new BitKeeper();}public void work() {for (int i 0; i n; i) {this.keeper.random();}}public void debug() {double v Math.log(this.n) / Math.log(2);System.out.printf(%d %.2f %d\n, this.n, v, this.keeper.maxBits);}}public static void main(String[] args) {for (int i 10000; i 1000000; i 10000) {Experiment exp new Experiment(i);exp.work();exp.debug();}}
}如上述代码所示如果只有一个BitKeeper那么精度很难控制BitKeeper越多则越精确所以 Redis 在设置HyperLogLog的时候设置了16384个桶也就是2^14每个桶的maxbits需要 6 个bit来存储最大可以表示maxbits63于是总共占用内存就是2^14 * 6 / 8 12k字节。
小结
我们从应用场景开始讲述了HyperLogLog的使用方法和实现原理还给出了HyperLogLog的 Java 简单实现。
最后我们在使用HyperLogLog的时候需要注意
HyperLogLog需要占用12k内存的数据量大的时候所以HyperLogLog不适合单独存储一个user相关的信息HyperLogLog是有一定精度损失的可能比真实数量多也可能比真实数量少但基本上都在n‰0n10以内。