java+redis hyperloglog实现千万级uv统计的核心是:每天一个hll key(如uv:home:20260728),用pfadd写入用户标识(支持去重与批量),用pfcount读取误差≤0.81%的估算值;单key仅占12kb,支持高并发原子写入,跨天统计需pfmerge合并后计算。

用 Java + Redis HyperLogLog 做千万级 UV 统计,核心就三点:每天一个 HLL key、用 PFADD 写入用户标识、用 PFCOUNT 读取估算值。它不存原始 ID,只占固定 12KB 内存,比 Set 节省上百倍空间,误差控制在 0.81% 以内,完全适配日活千万的业务场景。
按天建 key,自动去重写入
每个日期对应唯一 key,比如 uv:home:20260728 表示首页今日 UV。用户访问时,把用户 ID(或 device_id、token 等稳定标识)传给 PFADD:
- 同一用户多次调用
PFADD不会重复计数,底层靠哈希+分桶自动判重 - 推荐使用字符串 ID(如
"uid_123456"),避免数字类型引发序列化歧义 - 不要用随机 UUID 或高熵 token——虽然能用,但哈希分布更散,可能略微抬高误差下限
Java 客户端调用示例(Lettuce / Jedis)
以主流 Lettuce 为例(Jedis 接口类似):
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
// 初始化连接(Lettuce)
RedisClient client = RedisClient.create("redis://localhost:6379");
StatefulRedisConnection<string string> connection = client.connect();
RedisCommands<string string> sync = connection.sync();
// 写入:首页今日 UV,添加三个用户(含重复)
long result = sync.pfadd("uv:home:20260728", "uid_1001", "uid_1002", "uid_1001");
// 返回 1 表示内部状态更新(即有新元素加入)
// 读取:获取今日估算 UV
long uvCount = sync.pfcount("uv:home:20260728"); // 返回类似 9982341(误差 ±0.81%)
</string></string>
- 注意:
pfadd返回值是 long 类型,1 表示基数可能变化,0 表示无新增——可用来做轻量埋点判断 - 高并发写入无需加锁,PFADD 是原子操作,天然线程安全
- 单次最多支持 1024 个元素批量写入,减少网络往返
跨天合并与滚动统计
要查“近 7 天独立访客”,不能简单相加,必须用 PFMERGE 合并后再 PFCOUNT:
- 先合并:`sync.pfmerge("uv:home:7day", "uv:home:20260722", ..., "uv:home:20260728")`
- 再统计:`sync.pfcount("uv:home:7day")` —— 这才是真正去重后的总人数
- 合并结果可持久化,适合凌晨定时任务生成周报 key;也可用临时 key 避免写脏数据
- 注意:HLL 不支持删除单个用户,所以无法剔除测试流量或黑名单用户——这类需求需前置过滤
内存与精度的务实取舍
千万级 UV 场景下,HLL 的优势非常明确:
- 一个 key 固定占用约 12KB,哪怕你存 1 亿用户,还是 12KB;Set 存同样数据至少 160MB+
- 误差率实测通常 ≤ 0.5%,远低于标称的 0.81%;对“日活 992 万”这种结果,业务完全可接受
- 没有 GC 压力、不序列化大对象、命令复杂度 O(1),吞吐轻松破 10 万 QPS
- 若需要精确值(如财务结算),HLL 不适用;此时应考虑分段采样+离线校准,或改用 ClickHouse 等 OLAP 方案
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










