Redis 如何统计独立用户访问量?

张开发
2026/4/15 12:06:07 15 分钟阅读

分享文章

Redis 如何统计独立用户访问量?
Redis 如何统计独立用户访问量UV 统计的 4 种方案在网站分析、广告监测、推荐系统等场景中独立用户访问量UVUnique Visitor是一个核心指标。UV 的关键在于去重——同一个用户多次访问只计一次。Redis 提供了多种数据结构来高效实现 UV 统计各有优劣。本文将详细对比Set、Bitmap、HyperLogLog、incr 日期维度即用户提到的两种方式四种方案并通过流程图和代码示例帮助你选型。一、方案概览附选型流程图整数且连续如user_id1~1亿字符串或稀疏ID如uuid/手机号允许0.81%误差如大屏展示必须精确需要每日独立统计需要统计UV用户ID类型?Bitmap方案内存极小, 准确是否允许微小误差?HyperLogLog方案内存12KB, 性能极高Set方案内存随基数增长能否预聚合?incr 日期维度辅助计数, 需配合Set/HLL二、方案一Set 集合精确去重最直观的方法每个统计周期如一天维护一个 Set将每个访问过的用户 ID 加入 Set最后用SCARD获取基数。# 示例用户 1001 访问首页redis.sadd(uv:home:2025-04-15,user_1001)# 获取当天 UVuvredis.scard(uv:home:2025-04-15)优点精确、支持用户 ID 任意类型字符串/整数。缺点内存占用高每个用户 ID 都需要存储一份例如 1000 万用户每个 ID 按 30 字节算需约 300MB。适用用户量小 百万级或必须精确统计的场景。三、方案二Bitmap位图法精确且内存极省当用户 ID 是整数且相对连续如自增 user_id时可以用 Bitmap 将每个 user_id 映射到位偏移量存在则置 1。# 用户 ID1001 访问设置第 1001 位为 1redis.setbit(uv:home:2025-04-15,1001,1)# 统计当日 UV统计 1 的个数uvredis.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)# 获取近似 UVuvredis.pfcount(uv:home:2025-04-15)原理通过哈希函数将元素映射为二进制串观察低位连续零的个数来估计基数。优点内存固定12KB性能极高O(1) 添加适合海量数据。缺点不精确误差 ±0.81%无法取出具体有哪些用户只能计数不适合敏感计费场景。适用大屏展示、趋势分析、非精准营销统计等可容忍误差的场景。五、方案四incr 日期维度你提到的“incr自增”严格来说单纯使用INCR无法实现独立用户去重因为INCR是累加计数器每次访问都 1得到的是 PV页面访问量不是 UV。# 这样得到的是 PV不是 UVredis.incr(pv:home:2025-04-15)如何用 incr 辅助 UV通常做法是incr Set/Bitmap/HLL 组合用 Set 或 HLL 存储独立用户保证去重同时用 incr 记录总访问次数PV# 记录 PVredis.incr(pv:home:2025-04-15)# 记录 UV使用 HLLredis.pfadd(uv:home:2025-04-15,user_id)所以你提到的“incr 通过自增方式判断用户的访问量”并不适用于 UV应理解为 PV 统计。但为了贴合你的原文我们修正说明incr 适合 PVUV 必须依赖去重结构。六、四种方案对比表方案内存占用精确性支持用户ID类型时间复杂度写入典型应用SetO(N)每个元素完整存储精确任意O(1)小规模精确统计BitmapO(max_id) 位连续整数时极省精确非负整数O(1)亿级整数ID如手机号后几位HyperLogLog固定 12KB近似误差 0.81%任意需哈希O(1)海量UV快速估算incrPV固定每个key一个整数精确无只是计数O(1)页面访问总量非UV七、实战选型建议你的用户 ID 是整数且密集如 user_id 从 1 到 5000 万 首选Bitmap精确且内存最小。用户 ID 是字符串如 UUID、手机号且允许 0.81% 误差 首选HyperLogLog12KB 内存统计上亿 UV。必须精确统计且用户量较小 500 万 用Set简单可靠。既要 PV 又要 UV 组合INCR记录 PV PFADD记录 UVHLL或SADDSet。数据敏感场景如计费、反作弊❌ 不能用 HyperLogLog必须用 Bitmap 或 Set。八、代码示例三种方案对比Python Redisimportredis rredis.Redis(decode_responsesTrue)# 模拟 100 万个用户 ID字符串user_ids[fuser_{i}foriinrange(1_000_000)]# 1. Set 方式key_setuv:setr.delete(key_set)foruidinuser_ids:r.sadd(key_set,uid)print(fSet 精确 UV:{r.scard(key_set)})print(fSet 内存:{r.memory_usage(key_set)/1024/1024:.2f}MB)# 2. HyperLogLog 方式key_hlluv:hllr.delete(key_hll)foruidinuser_ids:r.pfadd(key_hll,uid)print(fHLL 近似 UV:{r.pfcount(key_hll)})print(fHLL 内存:{r.memory_usage(key_hll)}字节)# 固定约 12KB# 3. Bitmap 方式假设 user_id 转为整数此处用 i 模拟key_bituv:bitmapr.delete(key_bit)foriinrange(1,1_000_001):r.setbit(key_bit,i,1)print(fBitmap 精确 UV:{r.bitcount(key_bit)})print(fBitmap 内存:{r.memory_usage(key_bit)/1024/1024:.2f}MB)运行结果参考百万级Set内存约 30~40 MBHLL12 KBBitmap0.12 MB100 万 bit 0.125 MB九、总结你的原始说法修正/补充“incr 通过自增方式判断用户的访问量”incr 得到的是 PV总访问次数不是 UV。UV 需要去重。“HyperLogLog 用来做基数统计误差很小不适合数据敏感场景”✅ 正确。误差约 0.81%内存固定 12KB适合海量近似统计。最终结论对精度要求不高、数据量极大 →HyperLogLog需要精确、用户 ID 为整数 →Bitmap需要精确、用户 ID 为字符串且量小 →Set想要统计 PV →incr合理选择数据结构能让你的 UV 统计既快又省内存。 扩展思考如果既要精确又要省内存可以结合分段 Bitmap 冷热分离但会增加复杂度。有没有更好的思路

更多文章