- 用法
-
PFCOUNT key [ key ... ]
- 复杂度
- 当使用单个键调用时,复杂度为 O(1),平均常数时间非常小。当使用多个键调用时,复杂度为 O(N),其中 N 是键的数量,常数时间要大得多。
- 始于
- 2.8.9
- ACL 类别
- @hyperloglog, @read, @slow
当使用单个键调用时,返回存储在指定变量中的 HyperLogLog 数据结构计算出的近似基数。如果该变量不存在,则返回 0。
当使用多个键调用时,通过内部将所提供键中存储的 HyperLogLog 合并到一个临时 HyperLogLog 中,返回所传递的 HyperLogLog 集合的并集的近似基数。
HyperLogLog 数据结构可用于以少量固定内存计算集合中的唯一元素,具体来说,每个 HyperLogLog 仅需 12k 字节(加上键本身的几个字节)。
观测集合返回的基数不精确,但近似值具有 0.81% 的标准误差。
例如,为了统计一天内所有唯一的搜索查询,程序需要在每次处理查询时调用 PFADD
。随时可以使用 PFCOUNT
检索唯一查询的估计数量。
注意:作为调用此函数的副作用,HyperLogLog 可能会被修改,因为最后 8 个字节编码了为缓存目的而计算出的最新基数。因此,PFCOUNT
从技术上讲是一个写命令。
示例
127.0.0.1:6379> PFADD hll foo bar zap
(integer) 1
127.0.0.1:6379> PFADD hll zap zap zap
(integer) 0
127.0.0.1:6379> PFADD hll foo bar
(integer) 0
127.0.0.1:6379> PFCOUNT hll
(integer) 3
127.0.0.1:6379> PFADD some-other-hll 1 2 3
(integer) 1
127.0.0.1:6379> PFCOUNT hll some-other-hll
(integer) 6
性能
当使用单个键调用 PFCOUNT
时,即使理论上处理密集 HyperLogLog 的常数时间很高,性能也非常好。这是因为 PFCOUNT
使用缓存来记住先前计算的基数,该基数很少更改,因为大多数 PFADD
操作不会更新任何寄存器。每秒可以执行数百次操作。
当使用多个键调用 PFCOUNT
时,会进行 HyperLogLog 的即时合并,这很慢,此外,并集的基数无法缓存,因此当与多个键一起使用时,PFCOUNT
可能需要毫秒量级的时间,不应滥用。
用户应记住,此命令的单键和多键执行在语义上有所不同,并且性能也不同。
HyperLogLog 表示
HyperLogLog 使用双重表示:*稀疏*表示适用于计数少量元素(导致少量寄存器设置为非零值)的 HLL,以及适用于更高基数的*密集*表示。Valkey 在需要时会自动从稀疏表示切换到密集表示。
稀疏表示使用一种游程编码,经过优化,可以有效地存储大量设置为零的寄存器。密集表示是一个 12288 字节的字符串,用于存储 16384 个 6 位计数器。需要双重表示的原因是,对于较小的基数,使用 12k(这是密集表示的内存要求)来编码少数寄存器是极其次优的。
两种表示都带有一个 16 字节的头部,其中包含一个魔术字、一个编码/版本字段,以及以小端格式存储的缓存基数估算值(如果 HyperLogLog 自基数计算以来已更新,导致估算无效,则最高位为 1)。
HyperLogLog 作为一个字符串,可以使用 GET
命令检索,并使用 SET
命令恢复。使用损坏的 HyperLogLog 调用 PFADD
、PFCOUNT
或 PFMERGE
命令绝不是问题,它可能会返回随机值,但不会影响服务器的稳定性。大多数情况下,当稀疏表示被损坏时,服务器会识别出损坏并返回错误。
从处理器字长和字节序的角度来看,这种表示是中立的,因此 32 位和 64 位处理器、大端或小端都使用相同的表示。
有关 HyperLogLog 实现的更多详细信息,请参见这篇博客文章。hyperloglog.c
文件中实现的源代码也易于阅读和理解,并包含稀疏和密集表示所用确切编码的完整规范。
RESP2/RESP3 回复
整数回复:通过 PFADD
观察到的唯一元素的近似数量。