前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java统计网站PV、UV

Java统计网站PV、UV

原创
作者头像
BLACK595
修改2024-09-20 09:44:36
770
修改2024-09-20 09:44:36

前言

当一个系统上线后,基本都需要统计用户活跃度,活跃度一般有两个指标,一个是PV(Page View)页面浏览量,一个是UV(Unique Visitor)唯一用户量,比如微信小程序后台中就有每小时UV的统计。

什么是PV,UV

  • PV(Page View)页面浏览量,当页面被加载刷新一次,PV就会记录一次,一般PV越高,UV也会越高;但如果网站被爬虫或者被疯狂刷新,PV就会非常高。
  • UV(Unique Visitor)独立用户量,一天当中访问网站的用户数,不管是上午访问还是下午访问,一个用户都只记录一次。

比如你在上午访问了腾讯社区2次,下午访问了腾讯社区3次,那么PV就是2 + 3 = 5次,UV为1次。

为什么需要统计PV,UV

  1. 分析知道哪些页面是用户经常访问的,缓存常用数据,针对性的提升某些接口效率。如果某些页面访问量远远高于其他页面,我们还可以单独部署一台服务器给这些高访问页面使用。
  2. 监控系统,防止被爬虫或盗刷。
  3. 预计为广告主投放广告带来的流量。核心讲解PV统计相对简单,使用Redis,以日期为key,value为每天的访问量,用户每访问一次value就+1,统计PV时,读取PV值即可。

UV统计,同样日期为key,value为唯一标识用户的ID或IP的Set集合(本文使用用户IP来作为唯一标识),用户访问时如果Set中不存在当前访问用户IP,则UV+1,并将IP加入Set中;当我们读取UV时,即读取Set中元素个数。

如果不想在Redis中保存太多数据,我们可以把每天的PV、UV数据落库一次。

功能实现

这里使用RedisTemplate访问redis,使用Hutool的ServletUtil获取用户ip。

  • 🎉INCR命令统计PV,INCR key,将 key 中储存的数字值增一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
代码语言:java
复制
@Resource
private RedisTemplate redisTemplate;
//redis的pv和uv前缀
final static String PV\_PREFIX = "pv\_";
final static String UV\_PREFIX = "uv\_";

/\*\*
 \* 统计pv,uv
 \* @return 返回统计后的pv,uv值
 \*/
@GetMapping()
public AjaxResult statist(HttpServletRequest request) {
    // 获取yyyy-MM-dd格式的日期
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    String today = sdf.format(date);
    String pvKey = PV\_PREFIX + today;
    String uvKey = UV\_PREFIX + today;
    // pv + 1, incr命令:将 key 中储存的数字值增一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
    Long pvNum = redisTemplate.opsForValue().increment(pvKey);
    // hutool获取用户ip
    String clientIP = ServletUtil.getClientIP(request, null);
    // 将ip放到redis的set中
    redisTemplate.opsForSet().add(uvKey, clientIP); //"SADD myset hello world"
    Long uvNum = redisTemplate.opsForSet().size(uvKey); // "SCARD myset"
    AjaxResult ans = AjaxResult.success();
    ans.put("pv",pvNum);
    ans.put("uv",uvNum);
    return ans;
}

HyperLogLog统计UV为什么使用HyperLogLog在统计UV时我们刚刚使用的是Set保存全部的IP,它本身是去重的,最终Set元素的个数就是我们需要的值,用户量不多时还是可以接受的,但当用户人数上去时,达到百万,千万级时,保存全部ip还是非常占内存的。

  • 🎉SADD命令统计UV,SADD key value1 value2,将value1和value2添加进set中。SCARD key获取key的长度。

那有没有什么办法能够减少内存使用?Redis有提供HyperLogLog的算法,它是根据统计学的基数估算算法,用最多12k的内存空间进行基数统计,但由于它是估算的算法,会有一定的误差,误差率约为0.81%

HyperLogLog的UV统计

关于HyperLogLog的命令我们主要使用以下三个:

  • PFADD key value1 value2: 用于数据添加,可以一次性添加多个。添加的重复记录会去重,和Set一样。
  • PFCOUNT key: 对 key 进行统计计数。
  • PFMERGE destkey sourcekey1 sourcekey2: 合并多个统计结果,在合并的过程中,会自动去重多个集合中重复的元素。

Redis使用HyperLogLog统计UV:

代码语言:java
复制
// 将ip放到redis的HyperLogLog中
redisTemplate.opsForHyperLogLog().add(uvKey,clientIP); //PFADD mypf ip1 ip2
Long uvNum = redisTemplate.opsForHyperLogLog().size(uvKey); //PFCOUNT mypf

HyperLogLog的误差率

我们实际体验下,在Set和HyperLogLog中都放入10w条数据,比较他们的误差率。

代码语言:java
复制
@Resource
private RedisTemplate redisTemplate;
String uvSetKey = "uv_set_2024-07-13";
String uvPFKey = "uv_pf_2024-07-13";

long num = 100000;

@Test
public void initData(){
    // 初始化添加100w条数据
    for (int i = 1; i <= num; i++) {
        redisTemplate.opsForSet().add(uvSetKey,i);
        redisTemplate.opsForHyperLogLog().add(uvPFKey,i);
    }
}

@Test
public void getData(){
    // 获取误差率
    Long setSize = redisTemplate.opsForSet().size(uvSetKey);
    Long pfSize = redisTemplate.opsForHyperLogLog().size(uvPFKey);
    DecimalFormat format = new DecimalFormat("##.00%");
    String setFormat = format.format((double) setSize / (double) num);
    String pfFormat = format.format((double) pfSize / (double) num);
    System.out.println("set: " + setFormat);
    System.out.println("pf: " + pfFormat);
}

结果输出:

代码语言:sql
复制
set: 100.00%
pf: 99.56%

可以看到Set的是完全没有误差的,本次HyperLogLog的误差率为0.44%,对于统计UV这种数据时,我们一般都是有一定容忍度的,我们更专注服务器的资源使用情况,0.81%左右的误差我们是可以接受的。

HyperLogLog的内存使用

在Navicat中我们可以看到10w条数据的set占用内存为4M,而HyperLogLog只占用了12k

此外,我们可以通过Redis的命令debug object key查看某个key的序列化后的长度。返回的参数有以下五个:

➢ Value at :key 的内存地址

➢ refcount :引用次数

➢ encoding :编码类型

➢ serializedlength:序列化长度(单位是 Bytes)

➢ lru_seconds_idle:空闲时间

返回的serializedlength 仅仅代表 key 序列化后的长度,key 在内存中实际占用的内存会比这个值大。不过,它也侧面反应了一个 key 所占用的内存。

演示

完整代码

前端(vue3):https://gitee.com/HT3902LY/writing_front

后端(Java):https://gitee.com/HT3902LY/writing_back

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 什么是PV,UV
      • 为什么需要统计PV,UV
      • 功能实现
        • HyperLogLog的UV统计
          • HyperLogLog的误差率
            • HyperLogLog的内存使用
            • 演示
            • 完整代码
            相关产品与服务
            云数据库 Redis
            腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档