PV、UV的定义
- PV(
访问量
):Page View, 用户每次
访问即被计算一次,即页面浏览量
或点击量
- UV(
独立访客
):Unique Visitor,一天内相同的客户端只会
被计算一次
基数计数基本概念
- 基数计数(cardinality counting)用来统计一个集合中不重复的元素个数,例如统计某个网站的UV,或者用户搜索网站的关键词数量。
- 实现基数计数,最简单的做法是记录集合中所有不重复的元素集合
- 当统计的数据量变大时,相应的存储内存也会线性增长
- 当集合变大,判断其是否包含新加入元素的成本变大
- 实际上目前还没有发现更好的在
大数据
场景中准确计算基数的高效算法,因此在不追求绝对准确的情况下,使用概率算法
是一个不错的解决方案。
概率算法
不直接存储数据集合本身,通过一定的概率统计方法预估基数值,这种方法可以大大节省内存,同时保证误差控制在一定范围内
- 注:
HyperLogLog采用概率算法统计模型,允许有一定的误差
。
为什么一个hyperloglog键的大小固定为12KB?
struct hllhdr {
char magic[4];
uint8_t encoding;
uint8_t notused[3];
uint8_t card[8];
uint8_t registers[];
};
复制代码
- Redis的hyperloglog结构中,card数组为64位bit,理论上可以存储近2^64个值。
- Redis规定分桶为 2^14 个桶,即是个数为16384个,每一个桶内的数据采用6bit大小来存放,Redis存放的是第一次出现“1”的位置的值,6bit可以表示0~64位,即可以支持2^64个基数。
- registers部分占用内存为(16384*6+7)/8 = 12288个字节,另外加上HLL结构的头占用了16个字节,加起来一共12304个字节,也就是说一个hyperloglog键占用了12KB左右的大小,最多可以计算2^64个不同元素的基数。
HyperLogLog 命令
- HyperLogLog 目前只支持3个命令,PFADD、PFCOUNT、PFMERGE
PFADD-添加
- 将元素加入到HyperLogLog数据结构中,如果 HyperLogLog 的基数估算值在命令执行之后出现了变化,那么命令返回1,否则返回0。
PFCOUNT-统计基数
127.0.0.1:6379> pfadd uv 127.0.0.1 127.0.0.2 127.0.0.3 127.0.0.4
(integer) 1
127.0.0.1:6379> pfadd uv 127.0.0.1
(integer) 0
127.0.0.1:6379> pfcount uv
(integer) 4
复制代码
PFMERGE-合并HLL
- 把多个HyperLogLog合并为一个HyperLogLog,合并后的HyperLogLog的基数是通过所有的HyperLogLog进行并集后,得出来的。
127.0.0.1:6379> pfadd uv1 127.0.0.1 127.0.0.2 127.0.0.3 127.0.0.4
(integer) 1
127.0.0.1:6379> pfcount uv1
(integer) 4
127.0.0.1:6379> pfadd uv2 127.0.0.1 127.0.1.2 127.0.1.3 127.0.1.4
(integer) 1
127.0.0.1:6379> pfadd uv3 127.0.0.1 127.0.2.2 127.0.2.3 127.0.2.4
(integer) 1
127.0.0.1:6379> pfmerge newuv uv1 uv2 uv3
OK
127.0.0.1:6379> pfcount newuv
(integer) 10
复制代码
- 这个命令很重要,例如你可以统计
一周
或 一个月
的uv 就可以使用此命令来轻易实现。
案例实战:基于Redis的UV计算
步骤1:模拟UV访问
@Service
@Slf4j
public class TaskService {
@Autowired
private RedisTemplate redisTemplate;
@PostConstruct
public void init(){
log.info("模拟UV访问 ..........");
new Thread(()->this.refreshData()).start();
}
public void refreshDay(){
Random rand = new Random();
String ip=rand.nextInt(999)+"."+
rand.nextInt(999)+"."+
rand.nextInt(999)+"."+
rand.nextInt(999);
long n=this.redisTemplate.opsForHyperLogLog().add(Constants.PV_KEY,ip);
log.debug("ip={} , returen={}",ip,n);
}
public void refreshData(){
while (true){
this.refreshDay();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
复制代码
步骤2:实现UV统计功能
@RestController
@Slf4j
public class Controller {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping(value = "/uv")
public long uv() {
long size=this.redisTemplate.opsForHyperLogLog().size(Constants.PV_KEY);
return size;
}
}
复制代码
redis分布式缓存系列文章
上一篇:redis分布式缓存(二十四)一一 微关系计算解决方案
近期评论