游乐游手机版
首页/数据库/文章详情

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

时间:2026-04-25 14:04
在网站分析、广告监测、推荐系统等场景中,独立用户访问量(UV,Unique Visitor)是一个核心指标。UV 的关键在于去重——同一个用户多次访问只计一次。 Redis 提供了多种数据结构来高效实现 UV 统计,各有优劣。本文将详细对比 Set、Bitmap、HyperLogLog、incr +

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

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

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

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

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

最直观的方法是什么?没错,就是为每个统计周期(比如一天)维护一个 Set,把每个访问过的用户 ID 都塞进去,最后用 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 万用户,Bitmap 依然会预先分配 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),非常适合海量数据场景。

缺点:结果不精确;无法回溯具体有哪些用户访问过;因此,在涉及计费、对账等数据敏感的场景中需要慎用。

适用场景:大屏实时展示、趋势分析、非精准的营销活动统计等可以容忍一定误差的场景。

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

这里需要特别澄清一下。严格来说,单纯使用 INCR 命令无法实现独立用户去重。因为 INCR 是一个纯粹的累加计数器,每次访问都会 +1,得到的是 PV(页面访问量),而不是 UV。

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

那么,incr 如何辅助 UV 统计呢?

常见的做法是组合使用

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

所以,原文中提到的“incr 通过自增方式判断用户的访问量”更适用于 PV 统计。为了表述准确,我们修正一下:incr 适合统计 PV,而 UV 必须依赖具备去重能力的数据结构

六、四种方案对比表

方案 内存占用 精确性 支持用户ID类型 时间复杂度(写入) 典型应用
Set O(N)(每个元素完整存储) 精确 任意 O(1) 小规模精确统计
Bitmap O(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统计访问量的3种实现方式
  • Redis如何统计用户访问量
  • SpringBoot整合Redis实现访问量统计的示例代码
来源:https://www.jb51.net/database/3622569wh.htm
上一篇MySQL设置数据格为空白或NULL问题及解决 下一篇SQL如何高效查询最近更新记录?索引与排序优化策略
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
金仓数据库逻辑备份实战:全库导出与模式替换全流程
数据库 · 2026-07-03

金仓数据库逻辑备份实战:全库导出与模式替换全流程

在长期的运维实践中,我越来越体会到,备份就像一份保险——平时看似无用,但关键时刻却是唯一的救命稻草。逻辑备份看似简单,可真正执行恢复时,各种陷阱接连浮现:表名大小写不一致、Schema 未正确切换、Owner 属性未同步修改……任何一个环节处理不当,最终恢复出的数据库就会与预期相去甚远。 本文将深入

金仓数据库sys_rman物理备份全流程演练与误覆盖恢复
数据库 · 2026-07-03

金仓数据库sys_rman物理备份全流程演练与误覆盖恢复

干运维这行,逻辑备份和物理备份我都接触过,但说句实在话,真正能在生产环境里扛住事儿的,还得是物理备份。逻辑备份导出的是 SQL 语句,数据量一大,那速度慢得让人抓狂,而且最关键的是,它没法做时间点恢复。物理备份不一样,它直接拷贝数据文件,再配上 WAL 归档日志,想恢复到过去哪一秒都行,这是它最硬核

Windows下将MySQL注册为系统自启服务教程
数据库 · 2026-07-03

Windows下将MySQL注册为系统自启服务教程

先说一个关键前提:务必以管理员身份运行终端,否则 mysqld --install 这条命令几乎不可能成功。问题不在于命令写错,而是 Windows 系统的用户账户控制(UAC)机制会在中途拦截——在普通 CMD 或 PowerShell 窗口执行这条命令,要么直接提示 Access is deni

Mac版Navicat中快速对比两个数据库的表结构异同
数据库 · 2026-07-03

Mac版Navicat中快速对比两个数据库的表结构异同

直接说结论:Mac 版 Navicat 和 Windows 版在表结构比对逻辑上完全一致。但默认配置下,它确实无法承受“全库一键比对上万张表”的压力。要想避免卡死、内存溢出、进度条永远停在 0%,你必须手动将表分批处理,或者利用前缀过滤来控制扫描范围。 为什么 Mac 上点击「结构同步」后界面会卡住

MySQL中UNION操作推荐用UNION ALL的原因
数据库 · 2026-07-03

MySQL中UNION操作推荐用UNION ALL的原因

MySQL中UNION与UNION ALL性能对比:别再被“保险”迷惑,差距远超预期 先给出核心结论:UNION ALL 的性能通常比 UNION 高出不止一个数量级。原因在于,UNION 在合并结果集后会自动触发去重操作,这往往伴随着隐式排序,进而产生临时表和文件排序。而 UNION ALL 则直