如何使用 Redis 统计大量用户唯一访问量(UV)?

Sherwin.Wei Lv7

如何使用 Redis 统计大量用户唯一访问量(UV)?

回答重点

Redis 中 HyperLogLog 结构,可以快速实现网页 UV 、PV 等统计场景。它是一种基数估算算法的概率性数据结构,可以用极少的内存统计海量用户唯一访问量的近似值。

Set 也可以实现,用于精确统计唯一用户访问量,但是但当用户数非常大时,内存开销较高。

扩展知识

HyperLogLog 使用介绍

HyperLogLog 具有极小的内存占用(每个 HyperLogLog 结构约 12 KB),而允许一定的误差(通常在 0.81% 以内)。即使统计数百万用户,内存开销依然是恒定的(约 12 KB),非常适合海量数据场景。

HyperLogLog 的基本操作:

  • PFADD key element [element …]:将一个或多个元素添加到 HyperLogLog 数据结构中。
  • PFCOUNT key [key …]:返回 HyperLogLog 结构中不重复元素的近似数量。
  • PFMERGE destkey sourcekey [sourcekey …]:将多个 HyperLogLog 合并为一个。

以下是一个示例代码,展示在 Java 中如何使用 Redis 的 HyperLogLog 实现页面 UV 统计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import redis.clients.jedis.Jedis;

public class RedisHyperLogLogUV {

private static final String UV_KEY = "uv";

private Jedis jedis;

public RedisHyperLogLogUV() {
// 连接到 Redis
this.jedis = new Jedis("localhost", 6379);
}

// 添加用户访问记录
public void addUserVisit(String userId) {
jedis.pfadd(UV_KEY, userId);
}

// 获取独立用户访问量
public long getUniqueVisitorCount() {
return jedis.pfcount(UV_KEY);
}

// 关闭连接
public void close() {
jedis.close();
}

public static void main(String[] args) {
RedisHyperLogLogUV uvCounter = new RedisHyperLogLogUV();

// 模拟用户访问
uvCounter.addUserVisit("user1");
uvCounter.addUserVisit("user2");
uvCounter.addUserVisit("user3");
uvCounter.addUserVisit("user1"); // 重复访问
uvCounter.addUserVisit("user4");

// 获取独立用户访问量
long uniqueVisitors = uvCounter.getUniqueVisitorCount();
System.out.println("Unique Visitors: " + uniqueVisitors); // 输出: 4

// 关闭连接
uvCounter.close();
}
}

HyperLogLog 原理(简单了解即可)

HyperLogLog 基于概率算法来进行基数估算,其核心思想是通过哈希函数和最大零前缀长度来估算集合中唯一元素的数量。

以下是 HyperLogLog 的实现原理:

1. 哈希函数

  • HyperLogLog 通过一个哈希函数将输入元素映射到一个大的哈希空间。例如,将一个元素 x 使用哈希函数 h(x) 映射到一个非常大的数。
  • 这个哈希空间通常会是 2^32 或者更高。哈希函数的目的是将输入均匀地分布在哈希空间中。

2. 最大零前缀长度

  • HyperLogLog 的核心思想基于一个统计特性:哈希值中前导零的长度可以反映集合的基数。哈希值的前导零越长,意味着集合中元素的数量越多。
  • 假设有一系列元素被哈希,哈希结果是 0001...,则前导零是 3。通过记录这些哈希值的最大零前缀长度,可以推算出基数。

3. 分桶(Register Binning)

  • 为了提高估算的准确性,HyperLogLog 将哈希值划分到多个桶中,每个桶独立计算前导零的长度。
  • HyperLogLog 会将整个哈希空间划分为 2^p 个子集合,每个子集合称为一个 桶(register),并且每个桶都存储最大零前缀的长度(例如,哈希值的前 p 位用来决定应该存储在哪个桶中,后续的位用于计算前导零的长度)。
  • 通过分桶,可以减小估算误差。每个桶保存该桶中所有元素的最大前导零长度。

4. 基数估算(Harmonic Mean)

  • 一旦所有元素被哈希并存储在相应的桶中,HyperLogLog 就会通过这些桶中的前导零长度来计算集合的基数。
  • HyperLogLog 使用了一种称为 调和平均数(Harmonic Mean) 的方法来对这些前导零长度进行求平均,并通过一个复杂的数学公式(估算函数)来推算出整体集合的基数(这种方式可以有效减少由于单个极值而导致的偏差)。

HyperLogLog 的实现步骤:

1)数据哈希和映射

  • 每个输入元素通过哈希函数映射为一个 64 位的二进制值。
  • 前 p 位用于确定该元素映射到哪个桶,后续的位用于计算前导零。

2)前导零统计

  • 每个桶记录映射到该桶的元素的最大前导零的长度。
  • 对所有桶的数据进行调和平均来估算整体的基数。

3)基数估算

  • Redis 使用一些估算函数来根据调和平均的结果推算出集合的基数。

HyperLogLog 的优点和局限性

优点

1)内存占用小:HyperLogLog 的内存消耗几乎是固定的,为 12 KB,不管元素的数量有多大,这使它非常适合用于需要统计大规模数据集合的场景。

2)高效:对于插入和基数查询操作的时间复杂度都是 O(1),因为插入一个新元素只涉及到哈希和更新桶的操作。

3)适用于去重计数:HyperLogLog 非常适用于统计网站访问量、唯一用户数等需要去重的数据统计场景。

局限性

1)不支持精确计数:HyperLogLog 是一种近似算法,存在一定的误差,误差率为 **0.81%**。它不适用于需要精确统计的场景。

2)无法删除元素:HyperLogLog 无法从集合中删除元素,因为它只记录了桶中的最大前导零数,因此删除操作会使得统计失去意义。

3)只能用于基数估算:HyperLogLog 只适合用于统计集合的基数(也就是唯一元素的数量),无法获取集合的具体内容或其他信息。

Comments