31. 如何用 Redis 统计海量 UV?
大约 4 分钟
使用 Redis 统计海量 UV(Unique Visitors,独立访客)是一个常见的需求,尤其在需要处理高并发和大规模数据的场景中。Redis 提供了多种数据结构和方法,可以高效地完成 UV 统计。以下是几种常用的实现方式:
1. 使用 Redis 的 Set 数据类型统计 UV
Redis 的 Set 数据类型可以存储唯一的不重复元素,适合用于统计 UV。
1.1 实现步骤
- 使用 Set 存储用户标识:每当一个用户访问时,将用户的唯一标识(如用户 ID 或 IP 地址)添加到 Set 中。
- 获取 UV 数量:通过
SCARD
命令获取 Set 中元素的数量,即为当前的 UV 数量。
1.2 示例代码
import redis.clients.jedis.Jedis;
public class UVCounterUsingSet {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final String UV_KEY = "site:uv:20230813";
public static void main(String[] args) {
Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
// 模拟用户访问
jedis.sadd(UV_KEY, "user_1");
jedis.sadd(UV_KEY, "user_2");
jedis.sadd(UV_KEY, "user_3");
// 获取 UV 数量
long uvCount = jedis.scard(UV_KEY);
System.out.println("Unique Visitors (UV): " + uvCount);
jedis.close();
}
}
1.3 优点与局限性
- 优点:实现简单,Redis 的 Set 可以确保唯一性,并且操作时间复杂度为 O(1)。
- 局限性:当用户标识(如 IP 地址或用户 ID)非常多时,Set 可能会占用大量内存。
2. 使用 Redis 的 HyperLogLog 数据结构统计 UV
HyperLogLog 是 Redis 提供的一种用于基数统计的概率数据结构,它在大规模数据下内存占用非常少,适合用于海量 UV 统计。
2.1 实现步骤
- 使用 HyperLogLog 存储用户标识:每当一个用户访问时,将用户的唯一标识添加到 HyperLogLog 中。
- 获取 UV 数量:通过
PFCOUNT
命令获取 HyperLogLog 估算的 UV 数量。
2.2 示例代码
import redis.clients.jedis.Jedis;
public class UVCounterUsingHyperLogLog {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static final String UV_KEY = "site:uv:hll:20230813";
public static void main(String[] args) {
Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
// 模拟用户访问
jedis.pfadd(UV_KEY, "user_1");
jedis.pfadd(UV_KEY, "user_2");
jedis.pfadd(UV_KEY, "user_3");
// 获取 UV 数量
long uvCount = jedis.pfcount(UV_KEY);
System.out.println("Estimated Unique Visitors (UV): " + uvCount);
jedis.close();
}
}
2.3 优点与局限性
- 优点:内存占用非常小,通常只需要 12 KB,即使是处理数十亿个元素。
- 局限性:由于 HyperLogLog 是一种概率算法,结果是估算值,存在一定的误差(误差率约为 0.81%)。
3. 使用布隆过滤器(Bloom Filter)去重统计 UV
布隆过滤器是一种空间效率高的概率数据结构,可以用于判断某个元素是否存在。在统计 UV 时,可以用布隆过滤器进行去重。
3.1 实现步骤
- 使用布隆过滤器进行去重:每当一个用户访问时,将用户的唯一标识加入布隆过滤器。如果布隆过滤器返回用户已存在,则不增加 UV 计数,否则增加 UV 计数。
- 获取 UV 数量:通过记录的 UV 计数器获取 UV 数量。
3.2 示例代码
布隆过滤器需要在 Redis 之外实现或者使用 Redis Modules 提供的布隆过滤器支持,这里是一个简单的示例,使用 Guava 实现布隆过滤器:
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import redis.clients.jedis.Jedis;
public class UVCounterUsingBloomFilter {
private static final String REDIS_HOST = "localhost";
private static final int REDIS_PORT = 6379;
private static BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(), 1000000);
private static int uvCount = 0;
public static void main(String[] args) {
Jedis jedis = new Jedis(REDIS_HOST, REDIS_PORT);
// 模拟用户访问
if (addUser("user_1")) uvCount++;
if (addUser("user_2")) uvCount++;
if (addUser("user_3")) uvCount++;
// 获取 UV 数量
System.out.println("Unique Visitors (UV): " + uvCount);
jedis.close();
}
private static boolean addUser(String userId) {
if (!bloomFilter.mightContain(userId)) {
bloomFilter.put(userId);
return true;
}
return false;
}
}
3.3 优点与局限性
- 优点:内存效率高,可以处理大量数据。
- 局限性:布隆过滤器有一定的误判率,可能会误认为一个用户已经存在(但实际不存在),导致 UV 统计偏小。
4. 总结
在实际应用中,选择合适的 UV 统计方法取决于业务场景和资源限制:
- Set 数据结构适合在内存允许的情况下精确统计,但不适合超大规模的数据。
- HyperLogLog 非常适合海量数据的 UV 统计,内存占用低且性能好,适合在精度要求不高的场景中使用。
- 布隆过滤器 可以用于在内存紧张且需要进行去重的场景,但需要接受误判的风险。
在 Java 应用中,可以结合 Redis 的这些特性,选择合适的方案来统计海量 UV,并根据实际需求对方案进行调整和优化。