Redis-08 HyperLogLog原理与使用

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; // 模板用于操作 HLL 键

    public void visit(String day, String userId) {
        // 记录当日唯一访问(近似去重),键形如 uv:{yyyy-MM-dd}
        tpl.opsForHyperLogLog().add("uv:" + day, userId);
    }

    public long countDay(String day) {
        // 统计当日 UV 近似规模;为 null 时返回 0
        Long c = tpl.opsForHyperLogLog().size("uv:" + day);
        return c == null ? 0 : c;
    }

    public long mergeMonth(String month, String... days) {
        // 合并多日 HLL 到月度键,寄存器按位取最大值并返回近似规模
        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) {
        // 获取近似 UV 规模
        Long c = tpl.opsForHyperLogLog().size("uv:" + day); 
        return c == null ? 0 : c;
    }

    public long mergeMonth(String month, String... days) {
    	// 合并多日 HLL 得到月度 UV
        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 或数据库做精确采样与校验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值