从 Hash 到 HyperLogLog:Redis 海量 UV 统计的 3 种高阶玩法

📅 发布时间:2026/7/9 1:08:05 👁️ 浏览次数:
从 Hash 到 HyperLogLog:Redis 海量 UV 统计的 3 种高阶玩法
真实业务场景假设我们正在开发一个电商大促活动页产品经理提了一个“简单”的需求我们需要实时显示“当前正在浏览商品的用户数”。这个数字每秒可能变化数万次。在 10 万级并发下传统关系型数据库会面临什么问题经典错误场景// Java 初学者的常见做法 public synchronized void addCount() { int count getFromMySQL(); updateMySQL(count 1); }上述代码在高并发下会导致数据库连接池过载超时错误行锁竞争死锁风险CPU 飙升至 100%性能崩溃这正是我们需要设计 Redis 统计方案的核心原因——在这种场景下关系型数据库的 ACID 特性反而成了负担。Redis 方案的演进阶段 1Hash 方案——精确统计的起点原理使用哈希表记录每个独立访客适用于对精度要求高、中小型规模的场景。// 将用户访问记录添加到 Hash Jedis jedis new Jedis(localhost, 6379); String pageKey page_visit_count:examplePage; // 页面访问计数 Key String userId user_123; // 已登录用户 String visitorId visitor_abc123; // 未登录用户 // 使用 HSET 命令添加数据值简单设为 1 jedis.hset(pageKey, userId, 1); jedis.hset(pageKey, visitorId, 1); // 统计访问量直接使用 HLEN 命令获取字段数量 long visitCount jedis.hlen(pageKey); System.out.println(The page visit count is: visitCount);这种方法的优点是简单直接——实现容易查询方便且精度极高。但缺点也显而易见。随着访问页面的增加Key 像滚雪球一样膨胀链表变长内存占用迅速飙升性能逐渐下降。因此这种方法更适合流量相对较低的页面作为初期的尝试。阶段 2Bitmap 方案——空间与性能的平衡当用户基数变大时使用 Hash 在空间上就显得有些浪费了。这正是 Bitmap 大显身手的时候它是空间效率的大师。它巧妙地利用了 32 位 int 类型——不再存储一个完整的用户 ID而是拆解 32 位每一位代表一个用户直接节省了 32 倍的空间。对于已登录用户其 ID 直接映射到 Bitmap 中的特定位。对于未登录用户通过哈希算法将随机字符串标识符转换为哈希值再映射到特定位。例如用户 ID 为 5对应 Bitmap 第 5 位随机字符串哈希值为 10对应第 10 位。// 使用 Bitmap 统计页面访问量 Jedis jedis new Jedis(localhost, 6379); String pageKey bitmap_visit_count:examplePage; // Bitmap 访问计数 Key String loginUserId 5; // 已登录用户 ID String nonLoginUserKey random_str_123; // 未登录用户的随机 Key // 对于已登录用户直接设置对应位 jedis.setbit(pageKey, Long.parseLong(loginUserId), true); // 对于未登录用户先对 Key 进行哈希再设置位 String hashValue DigestUtils.md5DigestAsHex(nonLoginUserKey.getBytes()); long hashBitIndex Long.parseLong(hashValue.substring(0, 16), 16) % Long.MAX_VALUE; jedis.setbit(pageKey, hashBitIndex, true); // 使用 BITCOUNT 命令统计访问量 long visitCount jedis.bitcount(pageKey); System.out.println(The page visit count is: visitCount);这种方法的优势在于极小的内存占用和便捷的查询甚至可以检查特定用户是否访问过页面。但也存在明显的缺点。多个用户可能会碰撞到同一位上导致统计略有偏差。此外如果用户分布稀疏——例如用户 ID 是 1 亿——则需要分配 1 亿个位可能会造成内存浪费。因此这种方法更适合用户分布相对密集的场景。阶段 3HyperLogLog 方案——超大数据量的“量子”统计突破性思维通常系统不需要绝对精确的数据可以用可控的误差换取内存和性能的巨大提升。它不再追求记录每个用户的确切计数而是使用概率算法估算近似访问量误差控制在 0.81% 以内这在实际应用中通常已经足够。// 使用 HyperLogLog 统计页面访问量 Jedis jedis new Jedis(localhost, 6379); String pageKey hyperloglog_visit_count:examplePage; // HyperLogLog 访问计数 Key String userId user_456; // 用户标识可以是 ID 或其他唯一标识 // 使用 PFADD 命令添加用户访问记录 jedis.pfadd(pageKey, userId); // 使用 PFCOUNT 命令统计访问量 long approxVisitCount jedis.pfcount(pageKey); System.out.println(The approximate page visit count is: approxVisitCount);总结这三种方案各有所长。Hash 就像一本细致的账本适用于小规模、高精度的统计。Bitmap 是空间压缩的大师适合用户分布密集的才中等规模场景。HyperLogLog 则是概率统计专家专为超高并发、高流量的网站设计。在实际项目中我们需要根据业务场景灵活选择从而在并发战场中掌控全局精准高效地统计用户访问量。