《Redis应用实例》书摘(12):唯一计数器(使用HyperLogLog实现)

用集合实现的唯一计数器虽然能够满足需求,但它并非完美无缺:这个实现需要将被计数的所有元素都添加到集合里面,当需要计数的元素数量非常多,又或者需要进行大量计数的时候,使用集合实现的唯一计数器将消耗大量内存。

为了解决这个问题,我们可以修改唯一计数器的程序实现,改用HyperLogLog而不是集合作为底层结构。 代码清单 CODE_HLL_UNIQUE_COUNTER 展示了基于HyperLogLog实现的唯一计数器。


代码清单 CODE_HLL_UNIQUE_COUNTER 使用HyperLogLog实现的唯一计数器 hll_unique_counter.py

class HllUniqueCounter:

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

    def include(self, item):
        """
        尝试对给定元素进行计数。
        如果该元素之前没有被计数过,那么返回True,否则返回False。
        """
        return self.client.pfadd(self.key, item) == 1

    def exclude(self, item):
        """
        尝试将被计数的元素移出计数器。
        移除成功返回True,因元素尚未被计数而导致移除失败则返回False。
        """
        raise NotImplementedError

    def count(self):
        """
        返回计数器当前已计数的元素数量。
        如果计数器为空,那么返回0作为结果。
        """
        return self.client.pfcount(self.key)

因为HyperLogLog无法撤销对给定元素的计数,所以这个计数器也没有实现相应的exclude()方法。

作为例子,以下代码展示了这个计数器程序的使用方法:

>>> from redis import Redis
>>> from hll_unique_counter import HllUniqueCounter
>>> client = Redis(decode_responses=True)
>>> counter = HllUniqueCounter(client, "HllVisitCounter")
>>> counter.include("Peter")    # 计数元素
True
>>> counter.include("Jack")
True
>>> counter.include("Tom")
True
>>> counter.include("Tom")    # 已计数的元素没有被计数
False
>>> counter.count()    # 获取当前计数结果
3

Tip

本文摘录自《Redis应用实例》一书。
欢迎访问书本主页以了解更多Redis使用案例:huangz.works/rediscookbook/
../_images/rediscookbook-banner.png