08 HyperLogLog 原理与使用
🎯 学习要点
- 了解 HyperLogLog 的使用场景与命令
- 掌握估算原理与误差特性、并集合并规则
- 在 Spring 中使用
RedisTemplate 进行 UV 统计与合并
📘 概述
- HyperLogLog 是一种“近似去重计数”结构,用极小内存(约 ~12KB/键)估算集合基数。
- 典型命令包括
PFADD(添加)、PFCOUNT(估算)、PFMERGE(并集合并);重复元素天然去重。 - 适用于 UV/DAU、唯一设备、跨分区合并等规模估算;不用于精确结算、审计与成员枚举。
📖 名词解释
HyperLogLog(HLL):一种基于概率的近似去重计数结构,极省内存,换取小误差。UV:Unique Visitors,独立访客数,常用近似去重统计。寄存器(register):HLL 内部用于记录前导零位阶的槽位数组。ρ(x):对哈希结果计算“首个 1 的位置”得到的阶值,用于估算基数。
🧭 学习方案
- 使用 HLL 记录每日 UV,统计、合并整月 UV 并对比 Set 内存占用。
- 压测不同规模下的误差并记录曲线,形成误差与性能的认知边界。
- 在服务内统一封装 HLL 操作,约定键命名规范与清理策略。
🗺️ 应用场景说明
- 网站/接口 UV 与 DAU 统计:近似去重、极省内存,满足分析与报表需要。
- 海量事件计数:如唯一设备、唯一订单来源等场景的规模估算。
- 多天/多分区数据合并:通过并集快速估算汇总规模。
🧪 使用场景与示例
1) 网站 UV:当日统计 + 月度汇总
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.data.redis.core.RedisTemplate;
@Service
public class UvDailyMonthly {
@Autowired
RedisTemplate<String, String> tpl;
public void visit(String day, String userId) {
tpl.opsForHyperLogLog().add("uv:" + day, userId);
}
public long countDay(String day) {
Long c = tpl.opsForHyperLogLog().size("uv:" + day);
return c == null ? 0 : c;
}
public long mergeMonth(String month, String... days) {
return tpl.opsForHyperLogLog().union("uv:" + month, days);
}
}
# 添加当日用户ID;重复元素不会增加计数
PFADD uv:2025-11-22 u1 u2 u3
# 估算当日 UV(近似值)
PFCOUNT uv:2025-11-22
# 并集合并:寄存器逐位取最大值,写入到月度键
PFMERGE uv:2025-11 uv:2025-11-21 uv:2025-11-22
# 估算当月 UV(近似值)
PFCOUNT uv:2025-11
通俗说明
- 日 UV 用
PFADD/COUNT 记录与估算;月度 UV 用 PFMERGE 汇总多个日 HLL。
详细解释
- 键命名建议
uv:{yyyy-MM-dd} 与 uv:{yyyy-MM} 分层管理;合并按寄存器取最大值。 - 近似值不用于结算;报表与趋势分析可接受误差(约 0.81%)。
2) 多端合并:Web + App + 小程序 UV 汇总
# Web 端记录当日 UV(近似去重)
PFADD uv:web:2025-11-22 u1 u2
# App 端记录当日 UV;u2 重复不会重复计数
PFADD uv:app:2025-11-22 u2 u3
# 小程序端记录当日 UV
PFADD uv:mini:2025-11-22 u1 u4
# 跨端并集合并为当日总 UV
PFMERGE uv:all:2025-11-22 uv:web:2025-11-22 uv:app:2025-11-22 uv:mini:2025-11-22
# 估算当日总 UV(近似值)
PFCOUNT uv:all:2025-11-22
通俗说明
- 不同端各自记录,按日做并集合并得出总 UV,当月再做跨日合并。
详细解释
- 成员重复不会重复计数;并集适合跨端、跨区域与分片汇总。
- 建议统一用户标识(如账户 ID),避免设备/匿名标识产生统计偏差。
速记口诀
3) 广告曝光独立用户估算(Campaign 维度)
# 广告 Campaign cmp123 当日曝光记录(近似去重)
PFADD uv:ad:cmp123:2025-11-22 u1 u2 u5
# 估算当日独立受众规模(近似值)
PFCOUNT uv:ad:cmp123:2025-11-22
# 合并多日曝光到月度键
PFMERGE uv:ad:cmp123:2025-11 uv:ad:cmp123:2025-11-21 uv:ad:cmp123:2025-11-22
# 估算 Campaign 月度独立受众规模(近似值)
PFCOUNT uv:ad:cmp123:2025-11
通俗说明
- 每次曝光写入 Campaign 对应的 HLL;按日、月估算独立受众规模。
详细解释
- 适合媒体投放规模评估与频次分析的基数参考;不支持频控与精准去重流程。
- 若需频控(如 N 次上限),需配合 Set 或数据库实现策略,HLL 仅承担规模估算。
⚠️ 注意事项
- HLL 无法枚举成员与删除单个成员,只能整体删除键。
- 结果为近似值,误差约为
~0.81%(Redis 默认参数),不用于精确结算与审计。 - 同一键重复添加同一元素不会影响结果(天然去重)。
- 合并是寄存器按位取最大值,跨天/分区并集时注意键规范与来源一致。
🔬 原理详解
- 哈希映射:将元素进行 64 位哈希,前
p 位作为寄存器索引,剩余位用于计算 ρ 值(前导零个数+1)。 - 寄存器数组:共有
m = 2^p 个寄存器,Redis 采用 p=14,因此 m=16384;每个寄存器存储遇到的最大 ρ 值。 - 估算公式:
E = α_m * m^2 / Σ(2^{-M[i]}),其中 M[i] 为第 i 个寄存器的最大 ρ 值,α_m 为常数校正因子。- 小范围修正:当存在未使用寄存器数
V>0 时,用线性计数 E* = m * ln(m / V) 提升低基数准确性。 - 大范围修正:在极大基数时进行上界修正,避免估算偏移。
- 并集合并:将多个 HLL 的寄存器逐一取最大值(按位合并),得到并集估算。
- 误差特性:相对误差近似为
≈ 1.04 / √m,Redis 默认约 0.81%,适合规模与趋势分析。
💻 代码示例(Spring Data Redis)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.data.redis.core.RedisTemplate;
@Service
public class HyperLogLogService {
@Autowired
RedisTemplate<String, String> tpl;
public void recordVisit(String day, String userId) {
tpl.opsForHyperLogLog().add("uv:" + day, userId);
}
public long uv(String day) {
Long c = tpl.opsForHyperLogLog().size("uv:" + day);
return c == null ? 0 : c;
}
public long mergeMonth(String month, String... days) {
return tpl.opsForHyperLogLog().union("uv:" + month, days);
}
}
通俗说明
add 记录唯一访问;size 近似计数;union 合并多个日 UV 到月 UV。- 键命名建议
uv:{yyyy-MM-dd} 与 uv:{yyyy-MM},便于管理与清理。
详细解释
union(dest, sources...) 会把多个 HLL 按寄存器位取最大值,结果写入目标键并返回近似规模。- 计数读取为近似,重复用户不会增加计数;适合报表与趋势分析。
速记口诀
🧾 命令示例(CLI)
# 添加元素到当日 HLL,重复不影响计数
PFADD uv:2025-11-22 u1 u2 u3
# 估算当日 UV(近似值)
PFCOUNT uv:2025-11-22
# 合并多日 HLL 到月度键(寄存器取最大值)
PFMERGE uv:2025-11 uv:2025-11-21 uv:2025-11-22
# 估算当月 UV(近似值)
PFCOUNT uv:2025-11
通俗说明
PFADD 添加唯一元素;PFCOUNT 获取近似规模;PFMERGE 并集合并多个 HLL。
详细解释
- 重复元素不会影响计数;并集合并用于跨天、跨分区汇总统计。
速记口诀
- 口诀:PFADD 记录,PFCOUNT 估算,PFMERGE 汇总
📐 误差与性能
- 误差:默认
m=16384,相对误差约 0.81%;规模越大、越适合趋势分析与监控。 - 内存:HLL 键约占
~12KB 左右,远小于同规模 Set;合并与计数操作开销低。 - 性能:添加与计数近似
O(1),适合高并发、海量数据场景。
通俗说明
速记口诀
❓ 常见问题
- 能不能删除单个元素?不能;HLL 不支持成员枚举与单删,只能
DEL 键。 - 会不会把重复用户算多次?不会;重复添加同一元素不影响计数。
- 与 Set 的区别?Set 精确、可枚举但内存大;HLL 近似、不可枚举但内存极省。
- 合并是否会重复计数?不会;并集合并按寄存器最大值近似去重汇总。
🔍 小结
- 键命名:建议
uv:{yyyy-MM-dd} 与 uv:{yyyy-MM} 分层管理,方便清理与归档。 - 清理策略:按时间窗口归档与删除旧键,避免键长期占用。
- 使用边界:用于规模估算与报表,不用于精确计费与审计;必要时用 Set 或数据库做精确采样与校验。