HyperLogLog
HyperLogLog 是一种概率型数据结构,用于估计集合的基数。
HyperLogLog 是一种概率型数据结构,用于估计集合的基数。作为一种概率型数据结构,HyperLogLog 以牺牲完美的准确性换取高效的空间利用率。
Redis HyperLogLog 实现最多使用 12 KB 内存,提供 0.81% 的标准误差。
统计唯一项通常需要与要统计的项数成比例的内存量,因为你需要记住过去已经看到的元素,以避免重复计数。然而,存在一系列算法,它们以内存换取精度:这些算法返回带有标准误差的估计值,对于 Redis 的 HyperLogLog 实现,标准误差小于 1%。这种算法的奇妙之处在于,你不再需要使用与统计的项数成比例的内存量,而是可以使用恒定的内存量;最坏情况下为 12k 字节,如果你的 HyperLogLog(我们以后简称 HLL)看到的元素很少,则使用的内存会少很多。
Redis 中的 HLL,虽然技术上是一种不同的数据结构,但被编码为 Redis 字符串,因此你可以调用 GET
来序列化 HLL,并调用 SET
将其反序列化回服务器。
从概念上讲,HLL 的 API 类似于使用集合来完成相同的任务。你会将每个观察到的元素 SADD
到一个集合中,并使用 SCARD
来检查集合中的元素数量,这些元素是唯一的,因为 SADD
不会重新添加已存在的元素。
虽然你并没有真正地将项添加到 HLL 中,因为该数据结构只包含一个不包含实际元素的状态,但 API 是相同的
- 每次看到新元素时,使用
PFADD
将其添加到计数中。
- 当你想获取使用
PFADD
命令添加的唯一元素的当前近似值时,可以使用 PFCOUNT
命令。如果你需要合并两个不同的 HLL,可以使用 PFMERGE
命令。由于 HLL 提供唯一元素的近似计数,合并的结果将为你提供两个源 HLL 中唯一元素总数的近似值。
> PFADD bikes Hyperion Deimos Phoebe Quaoar
(integer) 1
> PFCOUNT bikes
(integer) 4
> PFADD commuter_bikes Salacia Mimas Quaoar
(integer) 1
> PFMERGE all_bikes bikes commuter_bikes
OK
> PFCOUNT all_bikes
(integer) 6
"""
Code samples for HyperLogLog doc pages:
https://redis.ac.cn/docs/latest/develop/data-types/probabilistic/hyperloglogs/
"""
import redis
r = redis.Redis(decode_responses=True)
res1 = r.pfadd("bikes", "Hyperion", "Deimos", "Phoebe", "Quaoar")
print(res1) # >>> 1
res2 = r.pfcount("bikes")
print(res2) # >>> 4
res3 = r.pfadd("commuter_bikes", "Salacia", "Mimas", "Quaoar")
print(res3) # >>> 1
res4 = r.pfmerge("all_bikes", "bikes", "commuter_bikes")
print(res4) # >>> True
res5 = r.pfcount("all_bikes")
print(res5) # >>> 6
import assert from 'assert';
import { createClient } from 'redis';
const client = createClient();
await client.connect();
const res1 = await client.pfAdd('bikes', ['Hyperion', 'Deimos', 'Phoebe', 'Quaoar']);
console.log(res1); // >>> true
const res2 = await client.pfCount('bikes');
console.log(res2); // >>> 4
const res3 = await client.pfAdd('commuter_bikes', ['Salacia', 'Mimas', 'Quaoar']);
console.log(res3); // >>> true
const res4 = await client.pfMerge('all_bikes', ['bikes', 'commuter_bikes']);
console.log(res4); // >>> OK
const res5 = await client.pfCount('all_bikes');
console.log(res5); // >>> 6
package io.redis.examples;
import org.junit.jupiter.api.Test;
import redis.clients.jedis.UnifiedJedis;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class HyperLogLogExample {
public void run() {
UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379");
long res1 = jedis.pfadd("bikes", "Hyperion", "Deimos", "Phoebe", "Quaoar");
System.out.println(res1); // >>> 1
long res2 = jedis.pfcount("bikes");
System.out.println(res2); // >>> 4
long res3 = jedis.pfadd("commuter_bikes", "Salacia", "Mimas", "Quaoar");
System.out.println(res3); // >>> 1
String res4 = jedis.pfmerge("all_bikes", "bikes", "commuter_bikes");
System.out.println(res4); // >>> OK
long res5 = jedis.pfcount("all_bikes");
System.out.println(res5); // >>> 6
jedis.close();
}
}
package example_commands_test
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
func ExampleClient_pfadd() {
ctx := context.Background()
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password docs
DB: 0, // use default DB
})
res1, err := rdb.PFAdd(ctx, "bikes", "Hyperion", "Deimos", "Phoebe", "Quaoar").Result()
if err != nil {
panic(err)
}
fmt.Println(res1) // 1
res2, err := rdb.PFCount(ctx, "bikes").Result()
if err != nil {
panic(err)
}
fmt.Println(res2) // 4
res3, err := rdb.PFAdd(ctx, "commuter_bikes", "Salacia", "Mimas", "Quaoar").Result()
if err != nil {
panic(err)
}
fmt.Println(res3) // 1
res4, err := rdb.PFMerge(ctx, "all_bikes", "bikes", "commuter_bikes").Result()
if err != nil {
panic(err)
}
fmt.Println(res4) // OK
res5, err := rdb.PFCount(ctx, "all_bikes").Result()
if err != nil {
panic(err)
}
fmt.Println(res5) // 6
}
using NRedisStack.Tests;
using StackExchange.Redis;
public class Hll_tutorial
{
public void run()
{
var muxer = ConnectionMultiplexer.Connect("localhost:6379");
var db = muxer.GetDatabase();
bool res1 = db.HyperLogLogAdd("{bikes}", new RedisValue[] { "Hyperion", "Deimos", "Phoebe", "Quaoar" });
Console.WriteLine(res1); // >>> True
long res2 = db.HyperLogLogLength("{bikes}");
Console.WriteLine(res2); // >>> 4
bool res3 = db.HyperLogLogAdd("commuter_{bikes}", new RedisValue[] { "Salacia", "Mimas", "Quaoar" });
Console.WriteLine(res3); // >>> True
db.HyperLogLogMerge("all_{bikes}", "{bikes}", "commuter_{bikes}");
long res4 = db.HyperLogLogLength("all_{bikes}");
Console.WriteLine(res4); // >>> 6
// Tests for 'pfadd' step.
}
}
这种数据结构的一些用例示例包括统计用户每天在搜索表单中执行的唯一查询数、网页的唯一访问者数以及其他类似情况。
Redis 也能够对 HLL 执行并集操作,更多信息请查阅完整文档。
用例
网页的匿名独立访问量(SaaS,分析工具)
此应用可以回答这些问题
- 此页面今天有多少独立访问量?
- 有多少独立用户播放过这首歌?
- 有多少独立用户观看过此视频?
注意
在某些国家/地区,存储 IP 地址或任何其他类型的个人标识符是违法的,这使得无法获取网站的唯一访问者统计信息。
每个页面(视频/歌曲)在每个时间段创建一个 HyperLogLog,每次访问时将每个 IP/标识符添加到其中。
基本命令
查看HyperLogLog 命令完整列表。
对 HyperLogLog 的写入 (PFADD
) 和读取 (PFCOUNT
) 操作在恒定的时间和空间内完成。合并 HLL 的时间复杂度为 O(n),其中 n 是草图(sketches)的数量。
限制
HyperLogLog 可以估计最多包含 18,446,744,073,709,551,616 (2^64) 个成员的集合的基数。
了解更多