PFMERGE:计算多个 HyperLogLog 的并集

PFMERGE 命令可以对多个给定的 HyperLogLog 执行并集计算,然后把计算得出的并集 HyperLogLog 保存到指定的键中:

PFMERGE destination hyperloglog [hyperloglog ...]

如果指定的键已经存在,那么 PFMERGE 命令将覆盖已有的键。PFMERGE 命令在成功执行并集计算之后将返回 OK 作为结果。

HyperLogLog 并集计算的近似基数接近于所有给定 HyperLogLog 的被计数集合的并集基数。举个例子,假如有 h1、h2、h33 个 HyperLogLog,它们分别对集合 s1、s2、s3 进行计数,那么 h1、h2、h3 这 3 个 HyperLogLog 的并集所计算出的近似基数将接近于 s1、s2、s3 这 3 个集合的并集的基数。

以下代码展示了如何使用 PFMERGE 计算 numbers1、numbers2 和 numbers3 这 3 个 HyperLogLog 的并集,并将其存储到 union-numbers 键中:

redis> PFADD numbers1 128 256 512
(integer) 1
redis> PFADD numbers2 128 256 512
(integer) 1
redis> PFADD numbers3 128 512 1024
(integer) 1
redis> PFMERGE union-numbers numbers1 numbers2 numbers3
OK
redis> PFCOUNT union-numbers
(integer) 4

PFCOUNT 与 PFMERGE

PFCOUNT 命令在计算多个 HyperLogLog 的近似基数时会执行以下操作:

  1. 在内部调用 PFMERGE 命令,计算所有给定 HyperLogLog 的并集,并将这个并集存储到一个临时的 HyperLogLog 中。

  2. 对临时 HyperLogLog 执行 PFCOUNT 命令,得到它的近似基数(因为这是针对单个 HyperLogLog 的 PFCOUNT,所以这个操作不会引起循环调用)。

  3. 删除临时 HyperLogLog。

  4. 向用户返回之前得到的近似基数。

举个例子,当我们执行以下命令的时候:

redis> PFCOUNT numbers1 numbers2 numbers3
(integer) 4

PFCOUNT 将执行以下以下操作:

  1. 执行 PFMERGE<temp-hyperloglog>numbers1numbers2numbers3,把 3 个给定 HyperLogLog 的并集结果存储到临时 HyperLogLog 中。

  2. 执行 PFCOUNT<temp-hyperloglog>,取得并集 HyperLogLog 的近似基 数 4。

  3. 执行 DEL<temp-hyperloglog>,删除临时 HyperLogLog。

  4. 向用户返回之前得到的近似基数 4。

基于上述原因,当程序需要对多个 HyperLogLog 调用 PFCOUNT 命令,并且这个调用可能会重复执行多次时,我们可以考虑把这一调用替换成相应的 PFMERGE 命令调用:通过把并集的计算结果存储到指定的 HyperLogLog 中而不是每次都重新计算并集,程序可以最大程度地减少不必要的并集计算。

其它信息

  • 复杂度:O(N),其中 N 为用户给定的 HyperLogLog 数量。

  • 版本要求:PFMERGE 命令从 Redis 2.8.9 版本开始可用。

示例:实现每周/月度/年度计数器

本章前面介绍了如何使用 PFADD 命令和 PFCOUNT 命令实现 HyperLogLog 版本的唯一计数器,在学习了 PFMERGE 命令之后我们可以通过使用这个命令对多个 HyperLogLog 实现的唯一计数器执行并集计算,从而实现每周/月度/年度计数器:

  • 通过对一周内每天的唯一访客 IP 计数器执行 PFMERGE 命令,我们可以计算出那一周的唯一访客 IP 数量。

  • 通过对一个月内每天的唯一访客 IP 计数器执行 PFMERGE 命令,我们可以计算出那一个月的唯一访客 IP 数量。

  • 年度甚至更长时间的唯一访客 IP 数量也可以按照类似的方法计算。

代码清单7-3 展示了一个能够对多个唯一计数器执行并集计算,并将结果存储到指定键的程序。

代码清单7-3 唯一计数器合并程序:/hyperloglog/unique_counter_merger.py
class UniqueCounterMerger:

    def __init__(self, client):
        self.client = client

    def merge(self, destination, *hyperloglogs):
        self.client.pfmerge(destination, *hyperloglogs)

UniqueCounterMerger 的定义非常简单,它使用类将 PFMERGE 命令封装了一下,以下代码展示了使用这个合并程序计算一周唯一访客 IP 数量的方法:

>>> from redis import Redis
>>> from unique_counter_merger import UniqueCounterMerger
>>> client = Redis(decode_responses=True)
>>> merger = UniqueCounterMerger(client)
>>> counters = [
... 'unique_ip_counter::8-10', # 本周7天的计数器键名
... 'unique_ip_counter::8-11',
... 'unique_ip_counter::8-12',
... 'unique_ip_counter::8-13',
... 'unique_ip_counter::8-14',
... 'unique_ip_counter::8-15',
... 'unique_ip_counter::8-16'
... ]
>>> merger.merge('unique_ip_counter::No_33_week', *counters) # 计算并存储本周的唯
一访客IP数量
>>> client.pfcount('unique_ip_counter::No_33_week') # 获取本周的唯一访客IP数量
47463