Skip to content

UV统计

1.1 HyperLogLog

1.1.1 背景

  • UV :全称 Unique Visitor, 也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。 1 天内同一个用户多次访问该网站,只记录 1 次。

  • PV :全称 Page View, 也叫页面访问量或点击量,用户每访问网站的一个页面,记录 1 次 PV,用户多次打开页面,则记录多次 PV 。往往用来衡量网站的流量。

    UV 统计在服务端做会比较麻烦,因为要判断该用户是否已经统计过了,需要将统计过的用户信息保存。但是如果每个访问的用户都保存到 Redis 中,数据量非常恐怖。

1.1.2 HyperLogLog 介绍

  • Hyperloglog(HLL) 是从 Loglog 算法派生的概率算法,用于确定非常大的集合的基数,而不需要存储其所有值。相关算法原理大家可以参考: https://juejin.cn/post/6844903785744056333#heading-0
  • Redis 中的 HLL 是基于 string 结构实现的,单个 HLL 的内存永远小于 16kb ,内存占用低的令人发指!作为代价,其测量结果是概率性的,有小于 0.81 %的误差。不过对于 UV 统计来说,这完全可以忽略。

1.1.3 使用

命令描述
PFADD key element将指定的元素添加到 HyperLogLog 中。
PFCOUNT key返回 HyperLogLog 的近似基数。
PFMERGE destkey sourcekey1 sourcekey2将多个 HyperLogLog 合并为一个。

1.1.4 使用场景

  • **适用 **:
    • 统计网站每日独立访问量(UV)。
    • 统计搜索关键词的搜索次数。
    • 统计直播间在线人数峰值。
  • **不适用 **:
    • 需要获取具体的元素内容(HLL 不存储原始数据,只记录特征)。
    • 数据量非常小(如果只有几十个用户,直接用 Set 更精确且内存差异不大)。
    • 容不得 1% 误差的场景(比如涉及资金结算)。

1.2 测试百万数据的统计

1.2.1 实现UV统计

image-20260423162316485

1.2.2 代码实现

java
/**
 *
 * 测试HyperLogLog内存占用
 */
@Test
void testHyperLogLog() {
    // 1. 准备数组,装用户数据
    final int batchSize = 1000;
    List<String> users = new ArrayList<>(batchSize);
    // 2. 添加数据到HyperLogLog
    for (int i = 1; i <= 1000000; i++) {
        users.add("user" + i);
        // 按已装载的数据量分批写入,避免i=0时误触发
        if (users.size() == batchSize) {
            stringRedisTemplate.opsForHyperLogLog().add("hll1", users.toArray(new String[0]));
            users.clear();
        }
    }
    // 循环结束后补写最后一批不足batchSize的数据
    if (!users.isEmpty()) {
        stringRedisTemplate.opsForHyperLogLog().add("hll1", users.toArray(new String[0]));
    }
    // 3. 统计数量
    Long size = stringRedisTemplate.opsForHyperLogLog().size("hll1");
    System.out.println("size = " + size);
}