UV统计
约 615 字大约 2 分钟
2025-07-11
12.1 HyperLogLog
在网站访问量衡量中,有两个关键的统计指标:
- UV (Unique Visitor):独立访客量,指通过互联网访问或浏览特定网页的独立自然人。在一天内,同一用户多次访问同一网站只被记录一次。
- PV (Page View):页面访问量或点击量,用户每次访问网站的一个页面,PV 计数增加一次。用户多次打开同一页面,PV 会被多次记录。
在服务端实现 UV 统计较为复杂,因为需要判断用户是否已被统计过,并将已统计的用户信息进行保存。如果将每个访问用户的详细信息都存储到 Redis 中,数据量将会非常庞大。那么,如何解决这个问题呢?
HyperLogLog 是一种基于 Loglog 算法的概率算法,用于在不需要存储集合中所有元素的前提下,估算非常大的集合的基数 (cardinality)。
Redis 中的 HyperLogLog 是基于 String 结构实现的。单个 HyperLogLog 的内存占用永远小于 16KB,具有极高的内存效率。但作为代价,HLL 的测量结果是概率性的,存在小于 0.81% 的误差。不过,对于 UV 统计而言,这种程度的误差是可以接受的。
常用的 HyperLogLog 命令如下:
命令 | 描述 |
---|---|
PFADD key element [element ...] | 将指定的元素添加到指定的 HyperLogLog 中。 |
PFCOUNT key [key ...] | 返回 HyperLogLog 中集合的近似基数。 |
PFMERGE destkey sourcekey [sourcekey ...] | 将 N 个不同的 HyperLogLog 合并为一个。 |
12.2 测试百万数据的统计
为了验证 HyperLogLog 在处理大量数据时的性能和准确性,可以使用单元测试。
通过单元测试,向 HyperLogLog 中添加 100 万条数据,观察内存占用情况和统计效果。以下是一个 Java 单元测试示例:
@Test
void testHyperLogLog() {
String key = "uv:20231029";
// 准备 100 万个用户 id
int j = 0;
for (int i = 0; i < 1000000; i++) {
j = new Random().nextInt(1000000);
stringRedisTemplate.opsForHyperLogLog().add(key, "user:" + j);
}
// 统计数量
Long count = stringRedisTemplate.opsForHyperLogLog().size(key);
System.out.println("count = " + count);
}
经过测试,我们发现误差在允许范围内,并且内存占用极小。这验证了 HyperLogLog 在 UV 统计方面的优势。
总而言之,HyperLogLog 提供了一种高效且节省内存的 UV 统计方案,在对精度要求不高的场景下,是一个非常实用的选择。