Redis HyperLogLog 实现 UV 统计
Redis HyperLogLog 实现 UV 统计:为什么 100 万用户也能用不到 16KB
本文整理自黑马点评 Redis 实战篇第 12 章「UV 统计」。这一章很短,但很重要:它讲的是 Redis HyperLogLog,适合解决海量去重计数问题,比如统计一个页面每天有多少独立访客。
1. 这篇文章解决什么问题
网站或 App 经常需要统计访问量。
常见指标有两个:
PV:页面访问次数
UV:独立访客数
PV 很好统计,每访问一次加 1 即可。
UV 麻烦一些,因为它要去重:
同一个用户一天访问 10 次,只能算 1 个 UV。
如果访问量很大,怎么在 Redis 中低成本统计 UV?这就是 HyperLogLog 的应用场景。
2. PV 和 UV 的区别
PV:Page View
PV 表示页面访问量。
用户每访问一次页面,就算一次。
例如:
用户 A 访问 5 次
用户 B 访问 2 次
用户 C 访问 1 次
PV 是:
5 + 2 + 1 = 8
UV:Unique Visitor
UV 表示独立访客数。
同一个用户一天内访问多次,只算一次。
上面的例子中,UV 是:
A, B, C 三个不同用户 = 3
所以:
PV 统计访问次数
UV 统计去重用户数
3. 为什么 UV 统计难
如果只是统计 PV,可以用 Redis 自增:
INCR pv:home:20260511
但 UV 需要去重,必须知道某个用户今天是否已经统计过。
最直接的方法是用 Set:
SADD uv:home:20260511 user_1
SADD uv:home:20260511 user_2
SADD uv:home:20260511 user_1
SCARD uv:home:20260511
Set 的好处是精确。
但问题是:它要保存每个用户标识。
如果一天有 1000 万 UV,Set 里就要存 1000 万个用户 id,内存开销很大。
这时就可以考虑 HyperLogLog。
4. HyperLogLog 是什么
HyperLogLog,简称 HLL,是一种概率型基数统计算法。
基数就是集合中不重复元素的数量。
例如:
{A, B, C} 的基数是 3
{A, A, B, C} 去重后还是 {A, B, C},基数也是 3
UV 统计本质上就是统计基数。
HLL 的特点是:
不保存所有元素
只估算不重复元素数量
内存极低
结果有小误差
Redis 中单个 HyperLogLog 的内存通常小于 16KB,误差小于约 0.81%。
对于 UV 报表来说,这点误差通常可以接受,因为 UV 更多用于趋势分析,不像订单金额那样必须一分不差。
5. HyperLogLog 是 Redis 新数据类型吗
从使用层看,Redis 提供了专门的 HLL 命令:
PFADD
PFCOUNT
PFMERGE
所以可以把它看成 Redis 的一种特殊统计结构。
但从底层实现看,Redis HLL 也是基于 String 编码的特殊结构。
这点和 BitMap 有点像:
BitMap:底层基于 String,用 SETBIT / GETBIT 操作
HLL:底层也基于 String,用 PFADD / PFCOUNT 操作
所以更准确的说法是:
HyperLogLog 是 Redis 提供的概率型基数统计结构,底层基于 String,但不能当普通字符串用,应该通过 PFADD、PFCOUNT、PFMERGE 操作。
6. 实际业务怎么用 HLL
假设要统计首页每日 UV。
可以设计 key:
uv:home:20260511
用户访问首页时:
String key = "uv:home:" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
String userId = UserHolder.getUser().getId().toString();
stringRedisTemplate.opsForHyperLogLog().add(key, userId);
对应 Redis:
PFADD uv:home:20260511 5
如果用户 5 今天访问首页 10 次,重复执行 PFADD,HLL 仍然会把它估算为一个独立访客。
后台报表查询 UV:
Long uv = stringRedisTemplate.opsForHyperLogLog().size("uv:home:20260511");
对应 Redis:
PFCOUNT uv:home:20260511
7. Java 里 HLL 对应什么
Spring Data Redis 中,HLL 的入口是:
stringRedisTemplate.opsForHyperLogLog()
常用方法:
add(key, values) // 对应 PFADD
size(key) // 对应 PFCOUNT
union(dest, keys) // 对应 PFMERGE
注意:Java 里不会让你 new HyperLogLog()。
HLL 结构存在 Redis 中,Java 只是通过 StringRedisTemplate 发送命令操作它。
8. 测试百万数据
项目测试代码:
@Test
void testHyperLogLog(){
String [] values = new String[1000];
int j=0;
for (int i=0;i<1000000;i++){
j=i%1000;
values[j]="user_"+i;
if (j==999){
stringRedisTemplate.opsForHyperLogLog().add("hl2",values);
}
}
Long count = stringRedisTemplate.opsForHyperLogLog().size("hl2");
System.out.println("count: "+count);
}
这段代码模拟向 HLL 中添加 100 万个不同用户:
user_0
user_1
...
user_999999
理论 UV 是:
1,000,000
但 HLL 返回的是估算值,可能接近 100 万,但不一定刚好等于 100 万。
9. 为什么要用数组批量 add
String [] values = new String[1000];
这里准备了一个长度为 1000 的数组。
循环中:
j = i % 1000;
values[j] = "user_" + i;
if (j == 999) {
stringRedisTemplate.opsForHyperLogLog().add("hl2", values);
}
意思是每攒够 1000 个用户,就批量写一次 Redis。
如果一个一个写:
100 万次 Redis 请求
批量写后:
100 万 / 1000 = 1000 次 Redis 请求
网络开销大幅降低。
10. add 和 size 分别对应什么
stringRedisTemplate.opsForHyperLogLog().add("hl2", values);
对应:
PFADD hl2 user_0 user_1 user_2 ...
它的作用是把用户标识加入 HLL 统计结构。
注意,它不是像 Set 那样完整保存每个用户,而是更新内部概率统计结构。
Long count = stringRedisTemplate.opsForHyperLogLog().size("hl2");
对应:
PFCOUNT hl2
它返回估算的去重数量。
11. HLL 和 Set 怎么选
用 Set 的场景
需要精确结果
需要判断某个用户是否存在
需要列出所有用户
数据量不大
用 HyperLogLog 的场景
只需要估算数量
数据量巨大
能接受小误差
不需要知道具体有哪些用户
UV 统计一般适合 HLL。
订单、支付、库存这类需要精确结果的业务绝对不适合 HLL。
12. 完整业务流程
文字版:
用户访问页面
-> PFADD uv:{page}:{date} userId
-> HLL 自动去重估算
-> 报表查询 PFCOUNT
-> 得到估算 UV
13. 易错点
1. HLL 不是精确统计
它是概率估算,有误差。
2. HLL 不能列出具体用户
它只能告诉你大概有多少个不同用户,不能告诉你具体是谁。
3. HLL 不适合需要强一致的业务
比如订单数、库存数、金额统计都不能用 HLL。
4. hl2 只是测试 key
真实业务应该设计成:
uv:home:20260511
uv:shop:{shopId}:20260511
5. Java 里没有直接 new HLL
通过:
stringRedisTemplate.opsForHyperLogLog()
操作 Redis 中的 HLL。
14. 面试怎么说
如果面试官问:Redis HyperLogLog 适合什么场景?
可以回答:
HyperLogLog 适合海量数据的去重计数,比如 UV、独立 IP 数、搜索关键词去重数量等。它不保存所有元素,而是用概率算法估算基数,单个 HLL 内存很小,Redis 中通常小于 16KB,但结果有小误差,大约小于 0.81%。所以它适合允许误差的统计场景,不适合订单金额、库存这类必须精确的业务。
如果问 Java 里怎么用:
可以通过
stringRedisTemplate.opsForHyperLogLog()操作。访问发生时用add(key, userId),对应PFADD;统计时用size(key),对应PFCOUNT。
15. 总结
第 12 章的核心是:
UV 统计的本质是海量去重计数。如果用 Set 保存所有用户,结果精确但内存高;HyperLogLog 不保存完整用户集合,而是用概率算法估算基数,用极低内存换取可接受的小误差。
更多推荐



所有评论(0)