📊 Redis HyperLogLog

高效的基数统计数据结构

HyperLogLog 概述

HyperLogLog 是一种概率性数据结构,用于估算集合的基数(不重复元素的数量)。它的最大优势是在输入元素的数量或者体积非常大时,计算基数所需的空间总是固定的、并且是很小的。在 Redis 中,每个 HyperLogLog 键只需要花费 12KB 内存,就可以计算接近 2^64 个不同元素的基数。

HyperLogLog 工作原理

📥 输入元素

user123, user456, user789

🔢 哈希计算

计算每个元素的哈希值

🪣 分桶存储

根据前缀分配到不同桶

📊 基数估算

基于桶的统计估算总数

核心思想:通过观察哈希值的前导零个数来估算集合大小

🎯 HyperLogLog

12KB

固定内存占用

支持 2^64 个元素

标准误差 0.81%

📦 普通 Set

~GB

内存随元素增长

精确计数

无误差

🛠️ 基本命令

HyperLogLog 的核心操作命令:

命令 语法 描述 时间复杂度
PFADD PFADD key element [element ...] 添加元素到 HyperLogLog O(1)
PFCOUNT PFCOUNT key [key ...] 返回基数估算值 O(1)
PFMERGE PFMERGE destkey sourcekey [sourcekey ...] 合并多个 HyperLogLog O(N)

基本操作示例:

# 创建 HyperLogLog 并添加元素
PFADD website:uv user1 user2 user3 user4 user5
# 返回:(integer) 1

# 获取基数估算
PFCOUNT website:uv
# 返回:(integer) 5

# 添加更多元素(包括重复元素)
PFADD website:uv user3 user4 user6 user7 user8
# 返回:(integer) 1

# 再次获取基数(重复元素不会增加计数)
PFCOUNT website:uv
# 返回:(integer) 8

# 添加大量元素
for i in {1..10000}; do
    redis-cli PFADD website:uv "user$i"
done

# 查看基数估算
PFCOUNT website:uv
# 返回:约 10000(可能有小幅误差)

合并操作示例:

# 创建多个 HyperLogLog
PFADD page:home user1 user2 user3
PFADD page:about user2 user3 user4
PFADD page:contact user3 user4 user5

# 查看各页面独立访客数
PFCOUNT page:home     # 返回:3
PFCOUNT page:about    # 返回:3
PFCOUNT page:contact  # 返回:3

# 合并所有页面的访客数据
PFMERGE website:total page:home page:about page:contact

# 查看网站总独立访客数
PFCOUNT website:total # 返回:5(去重后的总数)

# 也可以直接计算多个 HyperLogLog 的并集
PFCOUNT page:home page:about page:contact # 返回:5

🎯 应用场景

HyperLogLog 在实际项目中的典型应用:

📈 网站独立访客统计

# 每日独立访客统计
PFADD uv:2024-01-15 user123
PFADD uv:2024-01-15 user456
PFADD uv:2024-01-15 user789

# 获取当日独立访客数
PFCOUNT uv:2024-01-15

# 周独立访客统计
PFMERGE uv:week:2024-w3 \
  uv:2024-01-15 \
  uv:2024-01-16 \
  uv:2024-01-17 \
  uv:2024-01-18 \
  uv:2024-01-19 \
  uv:2024-01-20 \
  uv:2024-01-21

PFCOUNT uv:week:2024-w3

优势:内存占用固定,适合大规模用户统计

🛒 商品浏览去重统计

# 商品浏览用户统计
PFADD product:123:viewers user1001
PFADD product:123:viewers user1002
PFADD product:123:viewers user1003

# 获取商品独立浏览用户数
PFCOUNT product:123:viewers

# 分类商品浏览统计
PFMERGE category:electronics:viewers \
  product:123:viewers \
  product:124:viewers \
  product:125:viewers

# 获取分类独立浏览用户数
PFCOUNT category:electronics:viewers

优势:快速统计商品热度和用户兴趣

📱 APP 活跃用户统计

# 每日活跃用户统计
PFADD dau:2024-01-15 user_a
PFADD dau:2024-01-15 user_b
PFADD dau:2024-01-15 user_c

# 获取日活跃用户数
PFCOUNT dau:2024-01-15

# 月活跃用户统计
for day in {01..31}; do
    PFMERGE mau:2024-01 dau:2024-01-$day
done

# 获取月活跃用户数
PFCOUNT mau:2024-01

优势:高效处理大量用户活跃度数据

🔍 搜索关键词去重统计

# 搜索关键词统计
PFADD search:keywords "redis tutorial"
PFADD search:keywords "database optimization"
PFADD search:keywords "cache strategy"

# 获取独立搜索关键词数量
PFCOUNT search:keywords

# 用户搜索行为统计
PFADD user:search:behavior user123
PFADD user:search:behavior user456

# 获取有搜索行为的独立用户数
PFCOUNT user:search:behavior

优势:分析用户搜索偏好和热门内容

🌐 IP 地址去重统计

# IP 访问统计
PFADD ip:access:2024-01-15 "192.168.1.100"
PFADD ip:access:2024-01-15 "10.0.0.50"
PFADD ip:access:2024-01-15 "172.16.0.200"

# 获取独立 IP 访问数
PFCOUNT ip:access:2024-01-15

# 地区 IP 统计
PFADD ip:region:beijing "192.168.1.100"
PFADD ip:region:shanghai "10.0.0.50"

# 获取各地区独立 IP 数
PFCOUNT ip:region:beijing
PFCOUNT ip:region:shanghai

优势:网络安全分析和地域访问统计

📊 实时数据流去重

# 实时事件去重统计
PFADD events:click:realtime event_id_1001
PFADD events:click:realtime event_id_1002
PFADD events:click:realtime event_id_1003

# 获取实时独立事件数
PFCOUNT events:click:realtime

# 滑动窗口统计(每分钟)
PFADD events:minute:$(date +%Y%m%d%H%M) event_data

# 获取当前分钟独立事件数
PFCOUNT events:minute:$(date +%Y%m%d%H%M)

优势:处理高频实时数据流的去重需求

⚡ 性能特性

HyperLogLog 的性能优势和限制:

🎯 精度分析:

  • 标准误差:0.81%(在大数据集上)
  • 误差范围:实际值的 ±2% 内(99% 置信度)
  • 小数据集:在元素数量较少时误差可能较大
  • 大数据集:元素数量越多,相对误差越小
数据规模 HyperLogLog 内存 Set 内存 内存节省比例 HyperLogLog 误差
1万 12KB ~400KB 97% ±1-3%
10万 12KB ~4MB 99.7% ±0.5-2%
100万 12KB ~40MB 99.97% ±0.3-1%
1000万 12KB ~400MB 99.997% ±0.2-0.8%
1亿 12KB ~4GB 99.9997% ±0.1-0.5%

性能测试示例:

# 性能测试脚本
#!/bin/bash

# 测试 HyperLogLog 性能
echo "开始 HyperLogLog 性能测试"
start_time=$(date +%s.%N)

# 添加 100万个元素
for i in {1..1000000}; do
    redis-cli PFADD test:hll "element_$i" > /dev/null
done

end_time=$(date +%s.%N)
hll_time=$(echo "$end_time - $start_time" | bc)

# 获取基数估算
hll_count=$(redis-cli PFCOUNT test:hll)

echo "HyperLogLog 结果:"
echo "- 添加时间: ${hll_time}s"
echo "- 估算基数: $hll_count"
echo "- 内存使用: $(redis-cli MEMORY USAGE test:hll) bytes"

# 测试普通 Set 性能(小规模测试)
echo "\n开始 Set 性能测试(10万元素)"
start_time=$(date +%s.%N)

# 添加 10万个元素
for i in {1..100000}; do
    redis-cli SADD test:set "element_$i" > /dev/null
done

end_time=$(date +%s.%N)
set_time=$(echo "$end_time - $start_time" | bc)

# 获取精确基数
set_count=$(redis-cli SCARD test:set)

echo "Set 结果:"
echo "- 添加时间: ${set_time}s"
echo "- 精确基数: $set_count"
echo "- 内存使用: $(redis-cli MEMORY USAGE test:set) bytes"

💡 最佳实践

使用 HyperLogLog 的建议和注意事项:

✅ 适用场景:

  • 大规模数据:元素数量超过万级别
  • 内存敏感:对内存使用有严格限制
  • 近似统计:可以接受小幅误差
  • 实时计算:需要快速响应的统计需求
  • 分布式统计:需要合并多个数据源

⚠️ 不适用场景:

  • 精确计数:需要100%准确的计数结果
  • 小数据集:元素数量很少(<1000)
  • 元素查询:需要判断特定元素是否存在
  • 元素遍历:需要获取具体的元素列表
  • 排序需求:需要对元素进行排序

实际项目应用模式:

# 1. 分层统计模式
# 小时级统计
PFADD uv:hour:2024011515 user123

# 日级统计(合并24小时数据)
PFMERGE uv:day:20240115 \
  uv:hour:2024011500 uv:hour:2024011501 ... uv:hour:2024011523

# 周级统计(合并7天数据)
PFMERGE uv:week:2024w03 \
  uv:day:20240115 uv:day:20240116 ... uv:day:20240121

# 2. 多维度统计模式
# 按渠道统计
PFADD uv:channel:organic user123
PFADD uv:channel:social user456
PFADD uv:channel:ads user789

# 按设备统计
PFADD uv:device:mobile user123
PFADD uv:device:desktop user456

# 交叉统计
PFMERGE uv:mobile:total uv:channel:organic uv:channel:social

# 3. 实时+历史统计模式
# 实时统计(当前小时)
PFADD uv:realtime:current user123

# 定期归档(每小时执行)
PFMERGE uv:hour:$(date +%Y%m%d%H) uv:realtime:current
DEL uv:realtime:current

# 4. 阈值告警模式
# 检查是否达到阈值
count=$(redis-cli PFCOUNT uv:realtime:current)
if [ $count -gt 10000 ]; then
    echo "实时访客数超过阈值: $count"
    # 发送告警
fi

与其他数据结构的配合使用:

# HyperLogLog + String:存储统计元数据
PFADD uv:today user123
INCR uv:today:pv  # 页面浏览量(精确)
SET uv:today:start_time "2024-01-15 00:00:00"

# HyperLogLog + Hash:多维度统计
PFADD uv:page:home user123
HINCRBY stats:page:home pv 1
HSET stats:page:home last_visit "2024-01-15 10:30:00"

# HyperLogLog + Sorted Set:排行榜
PFADD page:123:viewers user456
ZINCRBY page:popularity 1 "page:123"

# HyperLogLog + List:日志记录
PFADD api:users user789
LPUSH api:access:log "user789:2024-01-15 10:30:00"

# 综合查询
echo "页面独立访客: $(redis-cli PFCOUNT uv:page:home)"
echo "页面总浏览量: $(redis-cli HGET stats:page:home pv)"
echo "最后访问时间: $(redis-cli HGET stats:page:home last_visit)"

💡 总结要点:

  • 内存效率:固定12KB内存,支持海量数据统计
  • 性能优异:所有操作都是O(1)时间复杂度
  • 误差可控:标准误差0.81%,适合大多数业务场景
  • 易于合并:支持多个HyperLogLog的并集运算
  • 应用广泛:适合UV统计、去重计数等场景
  • 限制明确:不支持元素查询和精确计数