导读:本期聚焦于吴凌云创作的《Redis HyperLogLog是什么?如何用它高效统计海量数据基数?》,敬请观看详情。统计网站每日独立访客数、页面UV这类基数问题时,直接用Set存储往往要消耗大量内存,数据量一上来服务器就吃不消。Redis提供的HyperLogLog是一种概率型数据结构,最多只占用约12KB内存,就能对上亿级别的数据进行基数估算,标准误差约为百分之零点八一。本文将从基数统计的常见痛点说起,详细讲解HyperLogLog的底层原理、PFADD、PFCOUNT、PFMERGE三个核心命令的用法,并给出Spring Boot中的实战代码示例,同时对比Set、Bitmap与HyperLogLog三种方案的适用场景,帮助你根据业务精度要求选择合适的统计方案。

在数据统计场景中,有一类问题看似简单却暗藏玄机:一个电商平台想知道今天有多少独立用户访问了首页,一个内容社区想统计某篇文章被多少不同的人阅读过,一个广告系统要计算某次投放触达了多少独立设备。这类问题的本质都是基数统计,也就是去重计数。如果用最朴素的思路,把每个用户ID都存进Set里再取长度,一千万用户可能就要消耗几百MB内存,数据量再往上翻,单机内存直接告急。Redis提供的HyperLogLog就是为解决这类问题而生的,它用固定且极小的内存空间完成了海量数据的基数估算,下面我们详细展开。

Redis HyperLogLog是什么?如何用它高效统计海量数据基数?

一、什么是基数问题,为什么传统方案不够用

基数(Cardinality)指的是一个集合中不重复元素的个数。比如数据集{1, 3, 5, 3, 7, 1}的基数是4,因为去重后只有1、3、5、7这四个元素。基数统计在互联网业务中无处不在:日活用户数(DAU)、UV、PV去重、搜索关键词去重数量等等,本质上都是基数问题。

传统的精确统计方案主要有两种。第一种是使用Redis的Set结构,每来一个用户ID就执行一次SADD,最后用SCARD获取基数。这种方案是精确的,但内存开销与元素数量成正比,一个Set存储一亿个用户ID,按每个ID占用几十字节计算,内存消耗可能达到数GB,对于只需要一个数字的业务来说代价太高。第二种是数据库层面的SELECT COUNT(DISTINCT user_id),这种查询在大表上性能极差,容易把数据库拖垮。

还有一点值得注意:很多业务场景其实并不要求绝对精确。运营报表上显示今日UV是1,002,384还是1,010,000,对业务决策几乎没有影响。正是这种对误差的容忍度,催生了概率型数据结构的用武之地,HyperLogLog就是其中最经典的代表。

二、HyperLogLog的底层原理通俗解读

HyperLogLog这个名字听起来唬人,但核心思想并不难理解。它最早由Flajolet等人在2007年的论文中提出,Redis从2.8.9版本开始支持。它的基本思路是:通过对元素做哈希,把任意输入映射为一串均匀分布的二进制比特串,然后利用比特串中出现的规律性特征来估算基数。

具体来说,HyperLogLog会关注哈希值二进制表示中从最低位开始连续出现0的个数(也叫低位连续零的长度,记作rank)。直观理解是:如果一个集合里有N个不同的元素,它们的哈希值会均匀散布在值域空间中。元素越多,越有可能出现某个哈希值的低位有很长的连续0。反过来,观测到的最大rank值与基数N之间存在数学关系,大致满足N约等于2的rank次方。这就像抛硬币:抛的次数越多,越容易出现连续很多次正面朝上的情况。

为了降低单次观测带来的误差,HyperLogLog把哈希空间分成多个桶(Redis中是16384个桶),每个桶独立记录最大rank值,最后通过调和平均数把所有桶的结果综合起来。Redis的实现中,无论输入多少个元素,HyperLogLog占用的内存最多约12KB,标准误差约为0.81%。换句话说,用12KB的固定内存就能估算上亿甚至更多元素的基数,这是精确方案完全无法企及的性价比。

三、三个核心命令的用法详解

Redis为HyperLogLog提供了三个命令,命令前缀统一是PF,是为了纪念算法提出者Philippe Flajolet。

第一个是PFADD,用于添加元素。语法为PFADD key element [element ...]。可以一次添加一个元素,也可以批量添加。当HyperLogLog的内部状态因此发生变化时返回1,否则返回0。实际应用中通常不需要关心返回值,直接添加即可。

第二个是PFCOUNT,用于获取基数估算值。语法为PFCOUNT key [key ...]。如果传入多个key,返回的是这些HyperLogLog并集的基数估算值,Redis会在内部自动完成合并计算而不修改原始key,这一点在做多天数据汇总时非常方便。

第三个是PFMERGE,用于合并多个HyperLogLog。语法为PFMERGE destkey sourcekey [sourcekey ...],把多个源key合并到目标key中。比如把每天的用户访问记录合并成一周的记录,就用得上它。

redis> PFADD uv:2024-01-01 "user:1001" "user:1002" "user:1003"
(integer) 1
redis> PFADD uv:2024-01-01 "user:1001"
(integer) 0
redis> PFCOUNT uv:2024-01-01
(integer) 3
redis> PFADD uv:2024-01-02 "user:1002" "user:1004"
(integer) 1
# 直接统计两天的独立用户总数,无需真正合并
redis> PFCOUNT uv:2024-01-01 uv:2024-01-02
(integer) 4
# 也可以显式合并成一个新key
redis> PFMERGE uv:two-days uv:2024-01-01 uv:2024-01-02
OK
redis> PFCOUNT uv:two-days
(integer) 4

需要注意,HyperLogLog中只存的是估算结构,无法反推出具体有哪些元素,也不能删除单个元素。如果业务需要回溯成员明细,HyperLogLog就无能为力了,这是设计上的取舍而非性能缺陷。

四、在Spring Boot中实战UV统计

在Java项目中,通过Spring Data Redis操作HyperLogLog非常简单,HyperLogLogOperations接口封装了完整的操作。下面是一段可直接运行的示例代码。

@Service
public class UvStatService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 记录一次页面访问,userId可以是用户ID或设备指纹
     */
    public void recordVisit(String date, String userId) {
        String key = "uv:" + date;
        stringRedisTemplate.opsForHyperLogLog()
                .add(key, userId);
        // 给key设置过期时间,避免历史数据无限堆积
        stringRedisTemplate.expire(key, Duration.ofDays(30));
    }

    /**
     * 查询某天的独立访客数
     */
    public long getUv(String date) {
        return stringRedisTemplate.opsForHyperLogLog()
                .size("uv:" + date);
    }

    /**
     * 统计某几天的总独立访客数
     */
    public long getUvRange(List<String> dates) {
        List<String> keys = dates.stream()
                .map(d -> "uv:" + d)
                .collect(Collectors.toList());
        return stringRedisTemplate.opsForHyperLogLog()
                .size(keys.toArray(new String[0]));
    }
}

实际落地时还有几个细节建议。第一,key的命名要带上时间维度,比如uv:2024-01-01,方便按天统计和设置过期。第二,一定要给key设置TTL,统计完的历史数据留着没有意义。第三,如果对精度要求较高,可以在写入时同时落一份明细日志到数据仓库,HyperLogLog负责实时展示,离线链路负责精确对账,两者互补。

五、Set、Bitmap与HyperLogLog如何选型

同样是去重统计,Redis其实提供了三种常用方案,它们的特性差异很大,选型前要想清楚业务需求。三者的核心对比见下表。

简单总结选型思路:如果统计对象是上亿量级的用户且只需要一个估算数字,HyperLogLog是毫无疑问的首选;如果用户ID是紧凑的整数(比如自增主键),Bitmap能以更低的内存做到精确统计,一亿用户也只需约12MB;如果既需要精确又需要拿到成员列表,或者元素规模只有几十万以内,老老实实用Set或者数据库即可。

另外提醒一点,HyperLogLog在Redis Cluster环境下跨slot的多个key执行PFCOUNT时会涉及跨节点操作,性能会有损耗,建议统计相关的key通过hash tag(比如用{uv}:2024-01-01这种写法)固定到同一个slot上,避免生产环境踩坑。

总的来说,HyperLogLog用极小的内存代价换取了对海量数据基数的快速估算,是典型的工程思维产物。理解它的适用边界,在合适的场景大胆使用,能让你用一台服务器的资源扛住原本需要集群才能处理的统计需求。

方案内存占用是否精确能否回溯元素适用场景
Set随元素数增长精确可以元素数量少,需要成员明细
Bitmap与ID范围相关精确可以(按位判断)用户ID为连续整数且范围可控
HyperLogLog固定约12KB约0.81%误差不可以海量数据、只需一个数字

RedisHyperLogLog基数统计修改时间:2026-09-12 12:12:44

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260912/55319.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。