Redis统计独立用户访问量的四种方案

 更新时间:2026年04月16日 08:39:11   作者:身如柳絮随风扬  
Redis 提供了多种数据结构来高效实现 UV 统计,各有优劣,本文将详细对比 Set、Bitmap、HyperLogLog、incr + 日期维度(即用户提到的两种方式)四种方案,并通过流程图和代码示例帮助你选型,需要的朋友可以参考下

在网站分析、广告监测、推荐系统等场景中,独立用户访问量(UV,Unique Visitor) 是一个核心指标。UV 的关键在于去重——同一个用户多次访问只计一次。

Redis 提供了多种数据结构来高效实现 UV 统计,各有优劣。本文将详细对比 Set、Bitmap、HyperLogLog、incr + 日期维度(即用户提到的两种方式)四种方案,并通过流程图和代码示例帮助你选型。

一、方案概览(附选型流程图)

二、方案一:Set 集合(精确去重)

最直观的方法:每个统计周期(如一天)维护一个 Set,将每个访问过的用户 ID 加入 Set,最后用 SCARD 获取基数。

# 示例:用户 1001 访问首页
redis.sadd("uv:home:2025-04-15", "user_1001")
# 获取当天 UV
uv = redis.scard("uv:home:2025-04-15")

优点:精确、支持用户 ID 任意类型(字符串/整数)。
缺点:内存占用高,每个用户 ID 都需要存储一份(例如 1000 万用户,每个 ID 按 30 字节算,需约 300MB)。
适用:用户量小(< 百万级)或必须精确统计的场景。

三、方案二:Bitmap(位图法,精确且内存极省)

当用户 ID 是整数且相对连续(如自增 user_id)时,可以用 Bitmap 将每个 user_id 映射到位偏移量,存在则置 1。

# 用户 ID=1001 访问,设置第 1001 位为 1
redis.setbit("uv:home:2025-04-15", 1001, 1)
# 统计当日 UV(统计 1 的个数)
uv = redis.bitcount("uv:home:2025-04-15")

内存计算:如果有 1 亿用户,只需 1亿 bit ≈ 12 MB,比 Set 节省数十倍。
优点:精确、内存极小、性能高(bitcount 时间复杂度 O(n) 但 Redis 做了优化)。
缺点:用户 ID 必须为整数且不太稀疏(若 ID 最大为 10 亿,但实际只有 100 万用户,依然会占用 125MB 的连续空间,造成浪费)。
适用:用户 ID 是自增整数、最大 ID 可控(如 2^32 以内)、对内存敏感且要求精确的场景。

四、方案三:HyperLogLog(近似去重,误差 0.81%)

你提到的 HyperLogLog 是一种概率性数据结构,用 12KB 固定内存即可统计上亿级别的 UV,误差率约为 0.81%。

# 添加元素
redis.pfadd("uv:home:2025-04-15", "user_1001", "user_1002")
# 获取近似 UV
uv = redis.pfcount("uv:home:2025-04-15")

原理:通过哈希函数将元素映射为二进制串,观察低位连续零的个数来估计基数。
优点:内存固定(12KB),性能极高(O(1) 添加),适合海量数据。
缺点:不精确(误差 ±0.81%),无法取出具体有哪些用户(只能计数),不适合敏感计费场景。
适用:大屏展示、趋势分析、非精准营销统计等可容忍误差的场景。

五、方案四:incr + 日期维度(你提到的“incr自增”)

严格来说,单纯使用 INCR 无法实现独立用户去重,因为 INCR 是累加计数器,每次访问都 +1,得到的是 PV(页面访问量),不是 UV。

# 这样得到的是 PV,不是 UV
redis.incr("pv:home:2025-04-15")

如何用 incr 辅助 UV?
通常做法是 incr + Set/Bitmap/HLL 组合

  • 用 Set 或 HLL 存储独立用户(保证去重)
  • 同时用 incr 记录总访问次数(PV)
# 记录 PV
redis.incr("pv:home:2025-04-15")
# 记录 UV(使用 HLL)
redis.pfadd("uv:home:2025-04-15", user_id)

所以,你提到的“incr 通过自增方式判断用户的访问量”并不适用于 UV,应理解为 PV 统计。但为了贴合你的原文,我们修正说明:incr 适合 PV,UV 必须依赖去重结构

六、四种方案对比表

方案内存占用精确性支持用户ID类型时间复杂度(写入)典型应用
SetO(N)(每个元素完整存储)精确任意O(1)小规模精确统计
BitmapO(max_id) 位,连续整数时极省精确非负整数O(1)亿级整数ID,如手机号后几位
HyperLogLog固定 12KB近似(误差 0.81%)任意(需哈希)O(1)海量UV快速估算
incr(PV)固定(每个key一个整数)精确无(只是计数)O(1)页面访问总量(非UV)

七、实战选型建议

你的用户 ID 是整数且密集(如 user_id 从 1 到 5000 万)
👉 首选 Bitmap,精确且内存最小。

用户 ID 是字符串(如 UUID、手机号),且允许 0.81% 误差
👉 首选 HyperLogLog,12KB 内存统计上亿 UV。

必须精确统计,且用户量较小(< 500 万)
👉 用 Set,简单可靠。

既要 PV 又要 UV
👉 组合:INCR 记录 PV + PFADD 记录 UV(HLL)或 SADD(Set)。

数据敏感场景(如计费、反 作弊)
❌ 不能用 HyperLogLog,必须用 Bitmap 或 Set。

八、代码示例:三种方案对比(Python + Redis)

import redis
r = redis.Redis(decode_responses=True)

# 模拟 100 万个用户 ID(字符串)
user_ids = [f"user_{i}" for i in range(1_000_000)]

# 1. Set 方式
key_set = "uv:set"
r.delete(key_set)
for uid in user_ids:
    r.sadd(key_set, uid)
print(f"Set 精确 UV: {r.scard(key_set)}")
print(f"Set 内存: {r.memory_usage(key_set) / 1024 / 1024:.2f} MB")

# 2. HyperLogLog 方式
key_hll = "uv:hll"
r.delete(key_hll)
for uid in user_ids:
    r.pfadd(key_hll, uid)
print(f"HLL 近似 UV: {r.pfcount(key_hll)}")
print(f"HLL 内存: {r.memory_usage(key_hll)} 字节")  # 固定约 12KB

# 3. Bitmap 方式(假设 user_id 转为整数,此处用 i 模拟)
key_bit = "uv:bitmap"
r.delete(key_bit)
for i in range(1, 1_000_001):
    r.setbit(key_bit, i, 1)
print(f"Bitmap 精确 UV: {r.bitcount(key_bit)}")
print(f"Bitmap 内存: {r.memory_usage(key_bit) / 1024 / 1024:.2f} MB")

运行结果参考(百万级):

  • Set:内存约 30~40 MB
  • HLL:12 KB
  • Bitmap:0.12 MB(100 万 bit = 0.125 MB)

九、总结

你的原始说法修正/补充
“incr 通过自增方式判断用户的访问量”incr 得到的是 PV(总访问次数),不是 UV。UV 需要去重。
“HyperLogLog 用来做基数统计,误差很小,不适合数据敏感场景”✅ 正确。误差约 0.81%,内存固定 12KB,适合海量近似统计。

最终结论

  • 对精度要求不高、数据量极大 → HyperLogLog
  • 需要精确、用户 ID 为整数 → Bitmap
  • 需要精确、用户 ID 为字符串且量小 → Set
  • 想要统计 PV → incr

合理选择数据结构,能让你的 UV 统计既快又省内存。

以上就是Redis统计独立用户访问量的四种方案的详细内容,更多关于Redis统计独立用户访问量的资料请关注脚本之家其它相关文章!

相关文章

  • Redis实现分布式锁的五种方法详解

    Redis实现分布式锁的五种方法详解

    在分布式架构中,我们同样会遇到数据共享操作问题,本文章使用Redis来解决分布式架构中的数据一致性问题,需要的小伙伴可以参考一下
    2022-06-06
  • Redis数据库的键管理示例详解

    Redis数据库的键管理示例详解

    这篇文章主要为大家介绍了Redis数据库的键管理示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • 详解Redis复制原理

    详解Redis复制原理

    与大多数db一样,Redis也提供了复制机制,以满足故障恢复和负载均衡等需求。复制也是Redis高可用的基础,哨兵和集群都是建立在复制基础上实现高可用的。复制不仅提高了整个系统的容错能力,还可以水平扩展,通过增加多个Redis只读从实例来减轻主实例的压力。
    2021-06-06
  • Redis整合SpringBoot的RedisTemplate实现类(实例详解)

    Redis整合SpringBoot的RedisTemplate实现类(实例详解)

    这篇文章主要介绍了Redis整合SpringBoot的RedisTemplate实现类,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • Redis存储的列表分页和检索的实现方法

    Redis存储的列表分页和检索的实现方法

    在 Redis 中,列表(List)是一种有序的数据结构,通常用于存储一系列元素,由于列表是有序的,可以通过索引来访问元素,因此可以很方便地实现分页和检索功能,以下是 Redis 列表的分页和检索的实现方法,需要的朋友可以参考下
    2025-02-02
  • Redis三种集群搭建配置(主从集群、哨兵集群、分片集群)

    Redis三种集群搭建配置(主从集群、哨兵集群、分片集群)

    本文主要介绍了Redis三种集群搭建配置,包括主从集群、哨兵集群、分片集群,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • Redis消息队列实现异步秒杀功能

    Redis消息队列实现异步秒杀功能

    在高并发场景下,为了提高秒杀业务的性能,可将部分工作交给 Redis 处理,并通过异步方式执行,Redis 提供了多种数据结构来实现消息队列,总结三种,本文详细介绍Redis消息队列实现异步秒杀功能,感兴趣的朋友一起看看吧
    2025-04-04
  • 解析Redis 数据结构之简单动态字符串sds

    解析Redis 数据结构之简单动态字符串sds

    Redis 的 string 类型为何使用sds而不是 C 字符串,本文主要介绍 string 的数据结构—— 简单动态字符串(Simple Dynamic String) 简称sds的相关知识,需要的朋友可以参考下
    2021-11-11
  • Redis优雅地实现延迟队列的方法分享

    Redis优雅地实现延迟队列的方法分享

    Redisson是Redis服务器上的分布式可伸缩Java数据结构,这篇文中主要为大家介绍了Redisson实现的优雅的延迟队列的方法,需要的可以参考一下
    2023-02-02
  • redis中key的设置方法步骤

    redis中key的设置方法步骤

    在本篇文章里小编给大家分享了关于redis中key的设置方法步骤以及相关知识点,有兴趣的朋友们学习参考下。
    2019-07-07

最新评论