Redis - Hyperloglog类型
Hyperloglog
用来估算集合中的元素个数!
Set 有一个应用场景:统计服务器的 UV(用户访问的次数)。
使用 Set 当然可以统计 UV,但是最大的问题在于,如果 UV 数据量非常大,Set 就会消耗很多的内存空间:
- 假设 Set 存储 userId,每个 userId 按照 8 个字节算
- 1 亿 UV => 8 亿字节 ➡️ 0.8G => 800MB
之所以消耗这么大的空间,是因为 Set 需要存储每个元素。
➡️ HyperLogLog 可以最多使用 12KB 空间,实现上述效果。
- HyperLogLog 不存储元素的内容
- 但能够记录 “元素的特征”,从而在新增元素时,判断该元素是已存在还是首次出现
- 用于计数(记录当前集合中不同元素的数量)
- 无法获取具体元素内容
Redis HyperLogLog 是一种用于高效统计集合中不重复元素数量(基数)的数据结构,它以极小的内存消耗(约 12KB)实现对海量数据的基数估算,非常适合需要进行去重计数但不需要精确结果的场景。
🔍 核心特性与概念
HyperLogLog 的设计围绕 “基数估算” 问题,关键特性如下:
- 空间效率:无论统计的元素数量是多少(从几个到数十亿),单个 HyperLogLog 键仅占用约 12KB 内存。
- 估算精度:存在约 0.81% 的标准误差,但对于大多数业务场景(如 UV 统计)已足够。
- 合并操作:支持多个 HyperLogLog 集合的合并,可快速计算总基数。
- 不存储原始数据:仅记录估算基数所需的统计信息,无法恢复原始元素。
- 核心思路:位操作
📝 核心命令分类(附 C++ 示例)
以下示例基于 hiredis 客户端,需先安装依赖(sudo apt install libhiredis-dev
)。
基础操作(添加与统计)
命令功能 | Redis 命令格式 | C++ 代码示例 |
---|---|---|
添加元素到集合 | PFADD key element [element ...] | 向 user_visits 添加多个用户 ID(如 UV 统计) |
估算基数(不重复元素数) | PFCOUNT key [key ...] | 计算 user_visits 中的不重复用户数 |
合并多个 HyperLogLog | PFMERGE destkey sourcekey [sourcekey ...] | 将多个集合的基数合并到新集合,用于计算总基数(如多天 UV 合并) |
代码示例:用户访问量统计
#include <hiredis/hiredis.h>
#include <iostream>
#include <string>
using namespace std;int main() {// 1. 连接 RedisredisContext* ctx = redisConnect("127.0.0.1", 6379);if (ctx->err) {cerr << "连接失败: " << ctx->errstr << endl;return 1;}// 2. 添加访问用户(PFADD user_visits 1001 1002 1003 1001 1004)// 注意:1001 重复添加,但只会被统计一次redisReply* reply = (redisReply*)redisCommand(ctx, "PFADD user_visits 1001 1002 1003 1001 1004");cout << "添加成功(1=成功,0=失败): " << reply->integer << endl; // 输出 1freeReplyObject(reply);// 3. 估算不重复用户数(PFCOUNT user_visits)reply = (redisReply*)redisCommand(ctx, "PFCOUNT user_visits");cout << "估算不重复用户数: " << reply->integer << endl; // 理论结果应为 4(误差范围内)freeReplyObject(reply);// 4. 合并多天数据(如合并 10 月 1 日和 10 月 2 日的 UV)// 先添加 10 月 2 日的用户(void)redisCommand(ctx, "PFADD user_visits_1002 1003 1005 1006");// 合并到 user_visits_totalreply = (redisReply*)redisCommand(ctx, "PFMERGE user_visits_total user_visits user_visits_1002");cout << "合并结果(1=成功): " << reply->integer << endl; // 输出 1freeReplyObject(reply);// 5. 计算合并后的总 UVreply = (redisReply*)redisCommand(ctx, "PFCOUNT user_visits_total");cout << "两天合并后的估算 UV: " << reply->integer << endl; // 理论结果应为 6(误差范围内)freeReplyObject(reply);// 6. 断开连接redisFree(ctx);return 0;
}
🎯 典型应用场景
HyperLogLog 适合需要 去重计数但可接受一定误差 的场景,具体包括:
网站 / APP 访问统计(UV)
- 统计每日 / 每月访问网站的独立用户数,无需存储用户 ID 列表,12KB 即可支持上亿用户的统计。
- 优势:相比 Set 类型(存储所有用户 ID),内存占用极低,尤其适合大数据量场景。
搜索关键词去重
- 统计用户搜索过的不重复关键词数量,分析用户兴趣分布,无需精确记录每个关键词。
数据埋点与分析
- 统计某个按钮的独立点击用户数、某篇文章的独立阅读用户数等,轻量且高效。
合并统计场景
- 合并多天、多渠道的 UV 数据(如合并 PC 端和移动端的总 UV),通过
PFMERGE
快速实现。
📌 C++ 开发注意事项
- 精度权衡:明确业务是否接受 0.81% 的误差,高精度场景(如财务数据)需使用 Set 或 Hash 替代。
- 元素类型:
PFADD
接受字符串类型的元素,数值需转换为字符串(如示例中的用户 ID)。 - 内存占用:单个 HyperLogLog 固定占用约 12KB,与元素数量无关,适合海量数据。
- 合并操作:
PFMERGE
的结果是一个新的 HyperLogLog,源数据不会被修改。 - 错误处理:
hiredis
操作后需检查返回值类型(如redisReply->type
)和上下文错误(ctx->err
)。