HINCRBYFLOAT:对字段存储的数字值执行浮点数加法或减法操作

HINCRBYFLOAT 命令的作用和 HINCRBY 命令的作用类似,它们之间的主要区别在于 HINCRBYFLOAT 命令不仅可以使用整数作为增量,还可以使用浮点数作为增量:

HINCRBYFLOAT hash field increment

HINCRBYFLOAT 命令在成功执行加法操作之后,将返回给定字段的当前值作为结果。

举个例子,通过执行以下 HINCRBYFLOAT 命令,我们可以将 geo::peter 散列 longitude 字段的值从原来的 100.0099647 修改为 113.2099647:

redis> HGET geo::peter longitude
"100.0099647"
redis> HINCRBYFLOAT geo::peter longitude 13.2 -- 将字段的值加上13.2
"113.2099647"

增量和字段值的类型限制

正如之前所说,HINCRBYFLOAT 命令不仅可以使用浮点数作为增量,还可以使用整数作为增量:

redis> HGET number float
"3.14"
redis> HINCRBYFLOAT number float 10086 -- 整数增量
"10089.13999999999999968"

此外,不仅存储浮点数的字段可以执行 HINCRBYFLOAT 命令,存储整数的字段也一样可以执行 HINCRBYFLOAT 命令:

redis> HGET number int -- 存储整数的字段
"100"
redis> HINCRBYFLOAT number int 2.56
"102.56"

最后,如果加法计算的结果能够被表示为整数,那么 HINCRBYFLOAT 命令将使用整数作为计算结果:

redis> HGET number sum
"1.5"
redis> HINCRBYFLOAT number sum 3.5
"5" -- 结果表示为整数5

执行减法操作

与 HINCRBY 命令一样,Redis 也没有为 HINCRBYFLOAT 命令提供对应的减法操作命令,因此如果我们想要对字段存储的数字值执行浮点数减法操作,那么只能通过向 HINCRBYFLOAT 命令传入负值浮点数来实现:

redis> HGET geo::peter longitude
"113.2099647"
redis> HINCRBYFLOAT geo::peter longitude -50 -- 将字段的值减去50
"63.2099647"

其它信息

  • 复杂度:O(1)。

  • 版本要求:HINCRBYFLOAT 命令从 Redis 2.0.0 版本开始可用。

示例:使用散列键重新实现计数器

第 2 章曾经展示过如何使用 INCRBY 命令和 DECRBY 命令去构建一个计数器程序,在学习了 HINCRBY 命令之后,我们同样可以通过类似的原理来构建一个使用散列实现的计数器程序,就像代码清单 3-3 展示的那样。

代码清单3-3 使用散列实现的计数器:/hash/counter.py
class Counter:

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

    def increase(self, n=1):
        """
        将计数器的值加上 n ,然后返回计数器当前的值。
        如果用户没有显式地指定 n ,那么将计数器的值加上一。
        """
        return self.client.hincrby(self.hash_key, self.counter_name, n)

    def decrease(self, n=1):
        """
        将计数器的值减去 n ,然后返回计数器当前的值。
        如果用户没有显式地指定 n ,那么将计数器的值减去一。
        """
        return self.client.hincrby(self.hash_key, self.counter_name, -n)

    def get(self):
        """
        返回计数器的当前值。
        """
        value = self.client.hget(self.hash_key, self.counter_name)
        # 如果计数器并不存在,那么返回 0 作为默认值。
        if value is None:
            return 0
        else:
            return int(value)

    def reset(self):
        """
        将计数器的值重置为 0 。
        """
        self.client.hset(self.hash_key, self.counter_name, 0)

这个计数器实现充分地发挥了散列的优势:

  • 它允许用户将多个相关联的计数器存储到同一个散列键中实行集中管理,而不必像字符串计数器那样,为每个计数器单独设置一个字符串键。

  • 与此同时,通过对散列中的不同字段执行 HINCRBY 命令,程序可以对指定的计数器执行加法操作和减法操作,而不会影响到存储在同一散列中的其他计数器。

作为例子,以下代码展示了如何将 3 个页面的浏览次数计数器存储到同一个散列中:

>>> from redis import Redis
>>> from counter import Counter
>>> client = Redis(decode_responses=True)
>>> # 创建一个计数器,用于记录页面/user/peter被访问的次数
>>> user_peter_counter = Counter(client, "page_view_counters", "/user/peter")
>>> user_peter_counter.increase()
1L
>>> user_peter_counter.increase()
2L
>>> # 创建一个计数器,用于记录页面/product/256被访问的次数
>>> product_256_counter = Counter(client, "page_view_counters", "/product/256")
>>> product_256_counter.increase(100)
100L
>>> # 创建一个计数器,用于记录页面/product/512被访问的次数
>>> product_512_counter = Counter(client, "page_view_counters", "/product/512")
>>> product_512_counter.increase(300)
300L

因为 user_peter_counter、product_256_counter 和 product_512_counter 这 3 个计数器都是用来记录页面浏览次数的,所以这些计数器都被放到了 page_view_counters 这个散列中。与此类似,如果我们要创建一些用途完全不一样的计数器,那么只需要把新的计数器放到其他散列里面就可以了。

比如,以下代码就展示了如何将文件 dragon_rises.mp3 和文件 redisbook.pdf 的下载次数计数器放到 download_counters 散列中:

>>> dragon_rises_counter = Counter(client, "download_counters", "dragon_rise.mp3")
>>> dragon_rises_counter.increase(10086)
10086L
>>> redisbook_counter = Counter(client, "download_counters", "redisbook.pdf")
>>> redisbook_counter.increase(65535)
65535L

图 3-11 展示了 page_view_counters 和 download_counters 这两个散列以及它们包含的各个计数器。

image 2025 01 02 17 56 51 710
Figure 1. 图3-11 散列计数器数据结构示意图

通过使用不同的散列存储不同类型的计数器,程序能够让代码生成的数据结构变得更容易理解,并且在针对某种类型的计数器执行批量操作时也会变得更加方便。比如,当我们不再需要下载计数器的时候,只要把 download_counters 散列删除就可以移除所有下载计数器了。