适合,HyperLogLog是Redis官方推荐的每日独立IP统计方案,误差率约0.81%,内存恒为12KB,支持PFMERGE跨日去重合并,但需注意IP标准化、key分天命名、时区统一及TTL清理等生产细节。

HyperLogLog 适合统计每日独立 IP 数量吗
适合,而且是 Redis 官方推荐场景。HyperLogLog 的误差率约 0.81%,内存固定只需 12 KB,无论你存 1 万还是 1 亿个 IP,都用这点内存。它不存原始数据,只存概率指纹,所以不能查“哪些 IP 来过”,只能回答“大概多少个不同 IP 来过”。
常见错误现象:PFADD daily:ip:2024-06-15 192.168.1.100 后立刻 PFCOUNT daily:ip:2024-06-15 返回 0 —— 多半是客户端没刷新连接或命令发错库;也可能是用了 Redis Cluster 但 key 没带 {...} 前缀导致路由到不同节点。
- 每天必须用独立 key,比如
daily:ip:2024-06-15,别用daily:ip累加,否则无法回溯某天数据 - IP 字符串建议标准化:IPv4 去掉前导零(
192.168.001.001→192.168.1.1),IPv6 统一转小写并压缩(2001:DB8::1) - 不要对同一个
IP频繁重复PFADD,虽然幂等,但会增加网络开销;业务层可先做简单布隆过滤或本地缓存去重
如何合并多日的独立 IP 总数(去重后)
不能直接加总 PFCOUNT 结果——那只是各日基数之和,不是跨日去重总数。得用 PFMERGE 把多个 HyperLogLog 合成一个临时结构再统计。
使用场景:算“近 7 天全站独立访客数”,或对比“上周 vs 本周活跃度”。
示例:
PFMERGE weekly:ip:2024-06-09-15 daily:ip:2024-06-09 daily:ip:2024-06-10 ... daily:ip:2024-06-15 PFCOUNT weekly:ip:2024-06-09-15
-
PFMERGE是原子操作,但目标 key 若已存在,会覆盖原有值;建议加时间戳后缀避免冲突,如weekly:ip:2024-06-09-15:tmp123 - 合并超过 100 个 key 时,Redis 可能阻塞较久(毫秒级),线上慎在高峰时段批量执行;可拆成每 20 个一组分批 merge
- 注意内存:merge 后的结构仍只占约 12 KB,但临时 key 不删会累积——记得用
DEL清理,或设EXPIRE
为什么不用 Set 存原始 IP
因为太贵。100 万独立 IP 存 Set,至少占 100+ MB 内存(每个字符串额外有 dict 开销),而 HyperLogLog 只要 12 KB。更关键的是,SCARD 虽准确,但 SADD 在大数据量下延迟波动大,且无法高效做跨日去重统计(SUNIONSTORE 会阻塞、爆内存)。
- 如果你需要“查某个 IP 是否在某天出现过”,Set 或 Sorted Set 更合适,但那是另一个需求,和“基数统计”不互斥
- 有些团队用 Lua 脚本把 IP 哈希后存到 bitset(如
SETBIT),看似省内存,但碰撞率难控、扩容麻烦,不如直接信 HyperLogLog 的工程实现 - 别为了“绝对精确”强行用 Set +
SCARD—— 除非你明确知道误差 0.81% 会导致商业决策错误,否则这是典型的过早优化
生产环境容易漏掉的关键细节
IP 来源不可靠、时间粒度错位、key 生命周期混乱,这三处出问题比算法本身更常见。
- 反向代理(Nginx / CDN)没透传真实
X-Forwarded-For,导致所有请求都记成同一个内网 IP;务必在接入层统一提取并清洗remote_addr或头字段 - 按天切分 key,但业务服务器时区不一致,或日志落盘延迟,导致同一天的请求被写进两个日期 key;建议统一用 UTC 时间生成 key,并校准所有服务时钟
- 没设过期策略,
daily:ip:2023-01-01这种 key 永远留在内存里;用EXPIRE设 90 天 TTL,或定期用SCAN+DEL清理旧 key
真正卡住人的,往往不是 HyperLogLog 怎么用,而是 IP 怎么取、时间怎么对、key 怎么管——这些事不落地,再准的算法也没意义。










