Redis 的 HyperLogLog:用 12KB 数清银河系星星的魔法计数器

Redis 的 HyperLogLog:用 12KB 数清银河系星星的魔法计数器

副标题:当精度遇见效率,一场统计学与内存的完美共舞!

引言:数星星的烦恼与统计学的救赎

想象一下,你开了一家超级火爆的线上商城(就叫它"宇宙大卖场"吧)。每天,来自银河系各个角落的顾客(可能是人类、外星人、甚至是会网购的AI)蜂拥而至。作为精明的老板,你迫切想知道:

  1. 今天到底有多少 独立 顾客访问了你的网站?(独立访客数,Unique Visitors - UV)
  2. 这个月有多少 独立 顾客搜索了"反重力滑板"?(独立搜索关键词数)

最"老实巴交"的做法是:给每个顾客分配一个唯一ID(比如他们的星际护照号),然后用一个 Set 数据结构来存储这些ID。每来一个新顾客,就把他的ID SADD 到 Set 里。最后用 SCARD 命令看看 Set 的大小,就知道 UV 了。

  • 优点: 精确!绝对精确!
  • 缺点: 内存爆炸! 想象一下,如果你的网站像淘宝双十一那样火爆,一天有上亿甚至十亿级别的访问量。一个 ID 就算只占 16 字节(已经很保守了),1 亿用户就需要 1.6GB 内存!十亿用户?16GB!这还只是存储一天的 UV!一个月呢?一年呢?服务器内存再大也扛不住啊!而且 SCARD 在 Set 很大时,计算时间也可能变长(虽然 O(1),但常数因子大)。

这时,你仰望星空,发出了灵魂拷问:我真的需要知道 精确 到个位数的 UV 吗?还是说,知道一个非常接近真实值、误差很小的估计值就足够了?

统计学微微一笑:"朋友,你需要的,是 HyperLogLog!"

一、HyperLogLog 是什么?------ 空间扭曲者

HyperLogLog (HLL) 是 Redis 提供的一种 概率性基数估算算法 。它的核心使命就是用 极其微小的内存代价 (固定最大约 12KB ),估算一个集合中 不重复元素的数量(基数 - Cardinality)。

  • 概率性: 它估算的值不是精确的,但误差率非常低(标准误差率约为 0.81%)。
  • 基数估算: 专门用来解决"有多少个不同的元素?"这类问题。
  • 极小内存: 无论你要估算的是 100 个用户还是 10 亿个用户,一个 HyperLogLog 结构最多只需要约 12KB 内存!是的,你没看错,是 KB,不是 MB 或 GB!这简直就是内存界的纳米技术!

用一句话概括 HyperLogLog:它用 12KB 的魔法口袋,装下了你对"多少种不同东西"这个问题的答案(一个精度相当不错的估计值),哪怕口袋里的东西多到像银河系的星星!

二、HyperLogLog 怎么用?------ Redis 的三板斧

Redis 为 HyperLogLog 提供了三个简洁而强大的命令:

  1. PFADD key element [element ...]

    • 作用: 将一个或多个元素添加到指定的 HyperLogLog 结构中。
    • 返回值:
      • 如果添加导致 HyperLogLog 内部结构被修改(即添加了新的、之前未记录的元素),返回 1
      • 如果添加的元素都是已记录过的(或者没有导致估计值变化),返回 0
    • 注意: 如果 key 不存在,Redis 会自动创建一个新的 HyperLogLog。如果 key 存在但不是 HyperLogLog 类型,会报错。
  2. PFCOUNT key [key ...]

    • 作用: 估算一个或多个 HyperLogLog 的基数(独立元素数量)。
    • 单 Key: 返回该 HyperLogLog 的基数估计值。
    • 多 Key: 返回这些 HyperLogLog 合并后(取并集)的基数估计值。这是 HLL 最强大的特性之一! 允许你将分散在不同 Key 的数据合并统计。
    • 返回值: 估算的基数(整数)。
  3. PFMERGE destkey sourcekey [sourcekey ...]

    • 作用: 将多个 HyperLogLog 合并(取并集)的结果存储到一个新的 HyperLogLog (destkey) 中。
    • 返回值: 成功返回 OK
    • 注意: 如果 destkey 已存在,它会被覆盖。sourcekey 可以是一个或多个。

生动案例代码时间!(Python + Redis-Py)

假设我们运行"宇宙大卖场",要统计今天的独立访客(UV)和独立搜索"反重力滑板"的用户数。

python 复制代码
import redis

# 连接到 Redis
r = redis.Redis(host='localhost', port=6379, db=0)

# 案例 1: 统计今日 UV (假设用户ID是字符串)
# 模拟一些用户访问
user_ids_today = ['user_001', 'user_002', 'user_003', 'user_001', 'user_004', 'user_002']

# 使用 PFADD 添加用户到 "uv:today" 这个 HyperLogLog
for user_id in user_ids_today:
    r.pfadd('uv:today', user_id)  # 注意:重复添加 user_001, user_002 会被 HLL 忽略其重复性

# 获取今日 UV 估计值
uv_today = r.pfcount('uv:today')
print(f"今日独立访客估计数: {uv_today}")  # 输出应该是 4 (实际独立用户是 user_001,002,003,004)

# 案例 2: 统计搜索 "反重力滑板" 的独立用户
search_users = ['user_002', 'user_005', 'user_001', 'user_005', 'user_006']

for user_id in search_users:
    r.pfadd('search:hoverboard', user_id)

uv_hoverboard = r.pfcount('search:hoverboard')
print(f"搜索'反重力滑板'的独立用户估计数: {uv_hoverboard}")  # 输出应该是 4 (实际是 user_001,002,005,006)

# 案例 3: 合并统计 - 想知道今天访问过网站 *或* 搜索过"反重力滑板"的总独立用户数
# 方法 1: 直接用 PFCOUNT 合并多个 Key
total_unique_visitors_or_searchers = r.pfcount('uv:today', 'search:hoverboard')
print(f"今日访问或搜索过'反重力滑板'的总独立用户估计数 (PFCOUNT合并): {total_unique_visitors_or_searchers}")

# 方法 2: 使用 PFMERGE 创建一个新的持久化的合并 HLL
r.pfmerge('total:uv_or_hoverboard', 'uv:today', 'search:hoverboard')
total_merged = r.pfcount('total:uv_or_hoverboard')
print(f"今日访问或搜索过'反重力滑板'的总独立用户估计数 (PFMERGE后): {total_merged}")

# 案例 4: 分时统计与合并 - 统计本周每日UV,然后计算整周UV
days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday']
all_day_keys = []

# 模拟每天的数据 (简化版,实际每天数据量巨大)
for day in days:
    key = f'uv:{day}'
    all_day_keys.append(key)
    # 假设每天有 10000 个用户,其中 20% 是跨天重复的 (模拟)
    for i in range(1, 10001):
        user_id = f'user_{day}_{i}'  # 大部分用户按天不同
        if i % 5 == 0:  # 20% 的用户是跨天重复的 (用固定前缀标识)
            user_id = f'common_user_{i//5}'
        r.pfadd(key, user_id)

# 计算整周UV (合并每天的数据)
weekly_uv = r.pfcount(*all_day_keys)  # Python 的 * 解包列表
print(f"本周独立访客估计数 (合并每日Key): {weekly_uv}")

# 或者用 PFMERGE 持久化周数据
r.pfmerge('uv:weekly', *all_day_keys)
weekly_uv_merged = r.pfcount('uv:weekly')
print(f"本周独立访客估计数 (持久化合并Key): {weekly_uv_merged}")

Node.js 版本 (使用 ioredis):

javascript 复制代码
const Redis = require('ioredis');
const redis = new Redis();

async function runHyperLogLogDemo() {
    // ... (模拟数据定义与上面Python类似)

    // PFADD
    for (const user_id of user_ids_today) {
        await redis.pfadd('uv:today', user_id);
    }

    // PFCOUNT
    const uv_today = await redis.pfcount('uv:today');
    console.log(`今日独立访客估计数: ${uv_today}`);

    // ... (其他操作,如search, PFCOUNT多key, PFMERGE, 分时统计等,逻辑同Python)
    // 注意: ioredis的 pfcount 接受多个key作为独立参数,或者一个数组。 pfmerge 类似。
    const totalEstimate = await redis.pfcount('uv:today', 'search:hoverboard');
    console.log(`合并估计: ${totalEstimate}`);

    await redis.pfmerge('total:merged', 'uv:today', 'search:hoverboard');
    const mergedCount = await redis.pfcount('total:merged');
    console.log(`持久化合并后估计: ${mergedCount}`);

    // 分时统计合并
    const weeklyEstimate = await redis.pfcount(...all_day_keys); // ES6 spread
    console.log(`周估计 (动态合并): ${weeklyEstimate}`);

    await redis.pfmerge('uv:weekly', ...all_day_keys);
    const weeklyPersisted = await redis.pfcount('uv:weekly');
    console.log(`周估计 (持久化Key): ${weeklyPersisted}`);

    redis.quit();
}

runHyperLogLogDemo().catch(console.error);

三、HyperLogLog 的原理揭秘------抛硬币、数零和调和平均数

HyperLogLog 的核心思想堪称巧妙,它利用了哈希函数 的均匀分布性和概率统计的原理。让我们一步步揭开它的神秘面纱:

  1. 哈希化 (Hash Everything!):

    • 当你要添加一个元素 element 到 HLL 时,Redis 首先用一个优秀的哈希函数(如 MurmurHash)计算 element 的哈希值 hash。这个哈希值是一个非常长的二进制位串(比如 64 位),并且可以近似看作是在 02^64 - 1 之间均匀分布的随机数。均匀分布是后续统计的基础!
  2. 分桶观察 (Bucket Brigade):

    • Redis 的 HLL 实现默认使用 2^14 = 16384 个桶 (bucketregister)。为什么是这个数?后面会解释。
    • 取哈希值 hash 的前 b 位(对于 16384 桶,b = 14)作为桶的索引号 index (0 <= index < 16384)。这决定了这个元素由哪个"观察员"(桶)负责记录信息。
    • 剩下的位(64 - b = 50 位)用来做关键观察。
  3. 数前导零 (Counting Leading Zeros - The Bernoulli Trial):

    • 观察剩下的 50 位二进制串。我们关心的是从这个串的最低位 (最右边)开始,连续出现 0 的个数(直到遇到第一个 1 为止),记作 run。这个 run 值加 1(即遇到第一个 1 的位置),记为 ρ (rho)。
    • 为什么数零? 想象你不停地抛一枚公平的硬币(正面=1,反面=0)。你连续抛出 k 个反面的概率是 (1/2)^k。这是一个伯努利试验 。连续抛到 k 次反面才出现第一次正面的概率很小(k 越大,概率越小)。反过来,如果你在大量的抛硬币序列中,观察到某个序列连续出现了 k 个零 ,那就意味着这个序列对应的试验中,你抛了很多次才出现正面。这种"长串零"的出现是稀有事件,但它携带了关于试验规模(或者说,你大概抛了多少次)的信息。 HLL 就是利用所有桶观察到的这种"稀有事件"的最大值来估算整体规模(基数)。
    • 重要: 对于每个桶,HLL 只记录该桶所有元素计算出的 ρ 值中的最大值 max_run。这个 max_run 是这个桶"见证"过的最长前导零序列(加1后的值)。
  4. 调和平均数与估算 (Harmonic Mean Saves the Day):

    • 现在我们有 16384 个桶,每个桶记录了一个 max_run
    • 如果直接用所有桶 max_run 的算术平均数来估算基数,结果会很不稳定,容易受到极端值(一个非常大的 max_run)的影响。
    • HyperLogLog 的精髓在于使用了调和平均数(Harmonic Mean)。调和平均数对大的离群值不那么敏感,能提供更稳定的估计。
    • 估算公式的简化版思想:
      • 先计算所有桶 max_run 值的调和平均数 H
      • 基数估计值 E = (α_m * m^2) / H,其中 m 是桶的数量 (16384),α_m 是一个根据 m 计算得到的修正因子(用于校正偏差)。
    • Redis 的实现还包含了一些更复杂的修正步骤(如对小范围基数使用线性计数,对极端情况如空桶或超大值进行校正),以进一步提高精度。
  5. 为什么是 16384 桶和 12KB?

    • 每个桶只需要存储一个 max_run 值。ρ 最大能有多大?理论上,在 50 位中,ρ 最大是 51(连续 50 个零)。存储 051 的数字需要多少位? 6 位就够了(2^6 = 64 > 51)。
    • 桶数 m = 16384
    • 总内存 = m * 6 bits = 16384 * 6 bits = 98304 bits
    • 转换为字节:98304 bits / 8 = 12288 bytes ≈ 12 KB
    • 这就是 12KB 魔法口袋 的由来!桶越多 (m 越大),估计越精确(误差率 ≈ 1.04 / sqrt(m))。16384 桶 (m=16384) 对应的理论误差率约为 1.04 / sqrt(16384) = 1.04 / 128 ≈ 0.008125,即 0.8125% 。这是一个内存(12KB)和精度(~0.81%)的绝佳平衡点。Redis 选择固定使用 16384 桶,简化了实现和用户使用(你无需配置桶数)。
    • 稀疏表示: 对于基数非常小的情况,Redis HLL 内部使用了一种更节省内存的稀疏表示(Sparse Representation),只有当基数增长到一定程度(或显式执行 PFMERGE/PFADD 导致转换)时,才会转换为标准的 12KB 稠密表示(Dense Representation)。这使得在小数据量时也能省内存。

原理总结一句话:HyperLogLog 把元素均匀分配到很多桶里,每个桶只记住自己见过的"最长前导零序列有多长"这个最震撼的信息。最后,它用一种聪明的数学方法(调和平均数),把这些桶看到的"震撼程度"汇总起来,估算出总共有多少不同的元素来过。精度虽非完美,但内存省得令人发指!

四、HyperLogLog 与其他方案的对比------华山论剑

特性 HyperLogLog (HLL) Set Bitmap (位图) Bloom Filter (布隆过滤器)
精确度 估算 (~0.81% 误差) 精确 精确 存在性检查有假阳性
内存占用 极小且固定 (≈12KB) 巨大且线性增长 (O(n)) 依赖基数/范围 (O(n) 或 O(max)) 比 Set 小,但线性增长 (O(n))
核心操作 PFADD, PFCOUNT, PFMERGE SADD, SISMEMBER, SCARD SETBIT, GETBIT, BITCOUNT BF.ADD, BF.EXISTS
解决核心问题 基数估算 (有多少不同元素?) 成员存在性 + 精确基数 成员存在性 + 精确基数 (特定范围) 成员存在性 (概率性)
是否可合并 完美支持 (PFMERGE) 困难 (需取并集,内存消耗大) 支持 (BITOP OR) 困难 (需设计特殊变种)
获取原始数据 不可能 可以 (SMEMBERS) 可以 (扫描位) 不可能
典型应用场景 海量数据 UV 统计、独立搜索词统计 精确去重、白名单、黑名单 (小/中规模) 用户签到、活跃用户统计 (ID连续) 缓存穿透防护、垃圾邮件过滤
1亿元素内存示例 ≈12 KB (不变!) ≈1.6 GB (16字节/ID) ≈12.5 MB (若ID范围1亿) ≈114 MB (1% 误判率)

结论:

  • 要精确,不怕内存:Set (小规模) 或 Bitmap (ID 密集连续)。
  • 要存在性检查,防穿透:Bloom Filter
  • 要海量数据独立计数,省内存是王道: HyperLogLog 是当之无愧的王者!它的 PFMERGE 能力更是锦上添花。

五、HyperLogLog 避坑指南------魔法的边界

再强大的魔法也有其限制和注意事项:

  1. 不是精确计数器! 最重要的一点! HLL 提供的是估计值 ,误差率大约 0.81%。如果你的业务严格要求精确到个位数(比如金融交易流水号计数),绝对不要用 HLL!用 Set 或基于数据库的唯一计数。
  2. 不能获取元素本身! HLL 只存储用于估算基数的摘要信息。一旦一个元素被 PFADD,你就无法知道它是否真的在里面,也无法列出所有元素。它只关心"有多少个不同的",不关心"具体是哪些"。
  3. "添加"不一定修改 (PFADD 返回值): PFADD 返回 1 表示这次添加可能 导致了内部状态更新(即新元素)。返回 0 表示元素很可能已经存在(或者添加没有改变估算值)。不要依赖这个返回值做精确的"是否新元素"判断,因为它基于概率摘要。
  4. PFCOUNT 单 Key vs 多 Key:
    • PFCOUNT key:估算单个 HLL 的基数。
    • PFCOUNT key1 key2 ...:估算的是 key1, key2, ... 这几个 HLL 合并后(并集) 的基数。这个操作不会 修改任何 Key,只是在内存中临时合并计算。误差率: 合并计算本身的误差率与单个 HLL 的误差率在同一量级(约 0.81%),并不会因为合并多个 Key 就显著增加误差。HLL 的合并算法(PFMERGE 也是基于此)在理论上是可合并的(mergeable)。
  5. PFMERGE 的成本: 合并多个 HLL 需要计算资源(CPU)。虽然算法高效(O(n)),但如果合并非常大量的 HLL 或者 HLL 本身很大(已用稠密表示),仍可能引起延迟。避免在极高QPS的路径上频繁合并大 HLL。
  6. Key 过期与删除: 和任何 Redis Key 一样,记得给 HLL Key 设置合理的过期时间(EXPIRE)或在不需要时删除(DEL),避免内存(虽然只有 12KB)和 Key 空间的浪费。PFMERGE 创建的新 Key 也需要管理。
  7. 稀疏表示的陷阱 (较少遇到):
    • 在基数非常小的时候,HLL 使用稀疏表示,内存可能小于 12KB。
    • 执行 PFMERGE 或添加大量元素后,可能会触发从稀疏表示到稠密表示(12KB)的转换。
    • 如果你 PFMERGE 的目标 Key (destkey) 原本是稀疏的,而源 Key 是稠密的,合并后 destkey 也会变成稠密的(12KB)。如果后续不再需要稠密表示的大内存,这可能是个小浪费(但在 12KB 的尺度下,通常影响不大)。
  8. 单 Key 上限: HLL 结构本身没有元素数量上限(得益于固定大小),但 Redis 对单个 String 值(HLL 底层是 String 存储的)有大小限制(默认 512MB)。12KB 的 HLL 远远远远远低于这个限制,完全不用担心。

六、HyperLogLog 最佳实践------把魔法用好

  1. 明确接受估算值: 在架构设计之初就确认业务是否能容忍 ~0.81% 的误差。适合场景:监控大盘、实时大屏、A/B 测试效果(独立用户对比)、大规模去重分析(如广告曝光独立用户数)。
  2. 拥抱 PFMERGE 这是 HLL 的杀手锏!用于:
    • 分时统计合并: 按小时/天存储 HLL,轻松合并出周/月/年统计。内存恒定!
    • 分片/分维度统计: 按用户属性、地理位置、设备类型等维度存储多个 HLL,可以灵活合并计算任意组合的总独立用户数。
    • 降低单 Key 压力: 避免所有流量都写同一个 HLL Key(虽然 HLL 写入效率很高,PFADD O(1)),可以按分钟/小时先写入临时 Key,再定时 PFMERGE 到总 Key。
  3. 内存无压力,大胆用: 相比于其他方案动辄 GB 的内存消耗,12KB 几乎可以忽略不计。在需要海量去重计数的场景,可以大量使用 HLL。
  4. 结合过期策略:
    • 对于临时统计(如实时大屏),设置较短的过期时间(如几分钟)。
    • 对于需要持久化的统计(如每日 UV),每天创建一个新 Key (e.g., uv:20240529),并设置一个较长的过期时间(如 30 天或永久)。旧 Key 按需清理。
  5. 理解稀疏表示: 虽然通常无需干预,但知道小基数时内存更少是好事。避免对小 HLL 频繁进行可能导致其转成稠密表示的 PFMERGE(除非必要)。
  6. 监控与报警: 监控 HLL 相关 Key 的数量和内存占用(虽然总量通常很小)。监控 PFADD/PFCOUNT/PFMERGE 的延迟和错误,确保服务正常。
  7. 命名规范: 使用清晰、一致的 Key 命名方案,方便管理和合并。例如:uv:{date}, search:unique:{keyword}:{date}, active_users:{region}:{hour}

七、面试考点及解析------征服面试官

  1. Q:HyperLogLog 是用来解决什么问题的?

    • A: 解决海量数据 场景下,估算一个集合中不重复元素个数(基数) 的问题,其核心优势是在固定且极小内存(约12KB) 下,提供可接受的误差率(约0.81%)
  2. Q:为什么 HyperLogLog 只需要 12KB 内存?误差率怎么来的?

    • A:
      • 内存: Redis HLL 默认使用 16384 (2^14) 个桶。每个桶存储一个 max_run 值(记录该桶所见元素哈希值的最长前导零位数+1)。存储 0~51 的数字需要 6 bits。总内存 = 16384 buckets * 6 bits/bucket = 98304 bits ≈ 12288 bytes = 12KB
      • 误差率: 基于概率统计理论,HLL 的标准误差率公式是 1.04 / sqrt(m),其中 m 是桶数。代入 m=16384sqrt(m)=1281.04/128 ≈ 0.008125 = 0.8125%。这是一个理论平均值。
  3. Q:Redis 提供了哪几个 HyperLogLog 命令?分别是什么作用?

    • A:
      • PFADD key element [element ...]:添加元素到 HLL。Key 不存在则创建。返回 1 表示内部可能被修改(新元素),0 表示可能未修改。
      • PFCOUNT key [key ...]:估算单个 HLL 或多个 HLL 合并后(并集)的基数。
      • PFMERGE destkey sourcekey [sourcekey ...]:将多个源 HLL 合并(并集)的结果存储到目标 destkey 中。
  4. Q:PFCOUNT key1 key2 和先 PFMERGEPFCOUNT 结果一样吗?哪个更好?

    • A: 估算结果在理论上应该是非常接近 的,因为它们计算的都是 key1key2 的并集基数。
    • 区别:
      • PFCOUNT key1 key2不修改 任何 Key,在内存中临时合并计算,返回估算值。更快更轻量,适合临时查询。
      • PFMERGE + PFCOUNT destkey:会将合并结果持久化存储destkey 中(消耗 12KB 内存),然后对 destkey 计数。适合需要重复查询 合并结果或进行链式合并(如合并周数据到月数据)的场景。
    • 选择: 如果只需要一次性的合并结果查询,用 PFCOUNT 多 Key。如果需要保存合并结果供后续多次使用,用 PFMERGE
  5. Q:HyperLogLog 和 Set/Bitmap/Bloom Filter 的主要区别是什么?

    • A: (参考前面的对比表,抓住核心)
      • vs Set: HLL 估算基数,内存极小固定;Set 精确存储元素和基数,内存 O(n)。HLL 不能取元素。
      • vs Bitmap: Bitmap 精确计数(适合 ID 连续密集),内存取决于最大 ID;HLL 估算计数,内存固定极小,不要求 ID 连续。Bitmap 能判断存在性,HLL 不能。
      • vs Bloom Filter: Bloom Filter 用于概率性判断元素是否存在 (有假阳性),内存 O(n);HLL 用于估算有多少不同元素,内存固定极小。两者都不能获取元素本身。
  6. Q:什么场景下绝对不能用 HyperLogLog?

    • A: 需要精确计数 的场景(如订单号计数、账户流水计数、精确去重要求结果 100% 准确)。需要获取元素本身判断某个特定元素是否存在的场景。
  7. Q:(进阶)HLL 的 PFADD 操作复杂度是多少?为什么?

    • A: O(1) 。原因:添加元素时,计算哈希值(O(1)),根据前 14 位定位桶(O(1)),计算剩下位的 ρ 值(前导零,位操作也是 O(1)),然后只需要更新该桶的 max_run 值(如果新 ρ 更大,一次赋值 O(1))。整个过程不依赖于已有元素数量。
  8. Q:(进阶)HLL 合并 (PFMERGE/PFCOUNT 多 Key) 的原理是什么?

    • A: 合并非常简单高效!对于合并的多个 HLL,取相同桶索引 (index) 上的 max_run 值,然后保留最大值 。例如,合并 HLL_AHLL_B 得到 HLL_Merge,对于第 i 个桶:HLL_Merge[i].max_run = max(HLL_A[i].max_run, HLL_B[i].max_run)。这个操作是 O(m) 的(m 是桶数,固定 16384),非常快。合并后的 HLL 再进行 PFCOUNT 估算。

八、总结------拥抱概率的智慧

Redis 的 HyperLogLog 是一个工程与统计学结合的典范。它用巧妙的算法和固定的 12KB 内存,打破了海量数据精确计数对内存的贪婪吞噬,为我们打开了一扇新的大门。

  • 核心价值: 极小内存(12KB) + 高效合并(PFMERGE) = 海量独立计数。
  • 核心牺牲: 精度(~0.81% 误差) + 无法获取元素细节。
  • 适用场景: UV/PV (独立访客/页面)、独立搜索词、独立事件追踪、大规模 A/B 测试用户分组计数、实时大屏去重指标等一切需要知道"有多少种不同的 X"且能容忍小误差的场景。
  • 灵魂启迪: 在分布式系统和大数据的浩瀚宇宙中,绝对的精确有时是昂贵的奢侈品。拥抱概率,善用估算,往往能用极小的资源消耗,换取洞察全局的能力。 HyperLogLog 正是这种智慧的闪耀结晶。下次当你面对需要统计银河系星星数量的需求时,别忘了这个神奇的 12KB 小口袋!

所以,大胆地去用 HyperLogLog 吧!让那些曾经因为内存限制而无法实现的统计梦想,重新闪耀! (别忘了给你的 Redis 实例留足那宝贵的 12KB 哦! 😉)

相关推荐
KK溜了溜了5 小时前
JAVA-springboot 整合Redis
java·spring boot·redis
爱上语文10 小时前
Redis基础(6):SpringDataRedis
数据库·redis·后端
Java初学者小白10 小时前
秋招Day14 - Redis - 应用
java·数据库·redis·缓存
奈斯ing11 小时前
【Redis篇】数据库架构演进中Redis缓存的技术必然性—高并发场景下穿透、击穿、雪崩的体系化解决方案
运维·redis·缓存·数据库架构
一眼万年0411 小时前
Redis Cluster模式
redis·微服务
deriva13 小时前
.netcore+ef+redis+rabbitmq+dotcap先同步后异步再同步的方法,亲测有效
redis·rabbitmq·.netcore
爱上语文14 小时前
Redis基础(5):Redis的Java客户端
java·开发语言·数据库·redis·后端
Java初学者小白17 小时前
秋招Day15 - Redis - 缓存设计
java·数据库·redis·缓存
都叫我大帅哥20 小时前
Redis GEO全解:从入门到精通,让你的应用“空间觉醒”
redis