示例:计数器

除了 ID 生成器之外,计数器也是构建应用程序时必不可少的组件之一:网站的访客数量、用户执行某个操作的次数、某首歌或者某个视频的播放量、论坛帖子的回复数量等等,记录这些信息都需要用到计数器。实际上,计数器在互联网中几乎无处不在,因此如何简单高效地实现计数器一直都是构建应用程序时经常会遇到的一个问题。

代码清单 2-7 展示了一个计数器实现,这个程序把计数器的值储存在一个字符串键里面,并通过 INCRBY 命令和 DECRBY 命令,对计数器的值执行加法操作和减法操作;在有需要的时候,用户还可以通过调用 GETSET 方法来清零计数器并取得清零之前的旧值。


代码清单 2-7 使用字符串键实现的计数器:/string/counter.py

  1. class Counter:
  2.  
  3. def __init__(self, client, key):
  4. self.client = client
  5. self.key = key
  6.  
  7. def increase(self, n=1):
  8. """
  9. 将计数器的值加上 n ,然后返回计数器当前的值。
  10. 如果用户没有显式地指定 n ,那么将计数器的值加上一。
  11. """
  12. return self.client.incr(self.key, n)
  13.  
  14. def decrease(self, n=1):
  15. """
  16. 将计数器的值减去 n ,然后返回计数器当前的值。
  17. 如果用户没有显式地指定 n ,那么将计数器的值减去一。
  18. """
  19. return self.client.decr(self.key, n)
  20.  
  21. def get(self):
  22. """
  23. 返回计数器当前的值。
  24. """
  25. # 尝试获取计数器当前的值
  26. value = self.client.get(self.key)
  27. # 如果计数器并不存在,那么返回 0 作为计数器的默认值
  28. if value is None:
  29. return 0
  30. else:
  31. # 因为 redis-py 的 get() 方法返回的是字符串值
  32. # 所以这里需要使用 int() 函数,将字符串格式的数字转换为真正的数字类型
  33. # 比如将 "10" 转换为 10
  34. return int(value)
  35.  
  36. def reset(self):
  37. """
  38. 清零计数器,并返回计数器在被清零之前的值。
  39. """
  40. old_value = self.client.getset(self.key, 0)
  41. # 如果计数器之前并不存在,那么返回 0 作为它的旧值
  42. if old_value is None:
  43. return 0
  44. else:
  45. # 跟 redis-py 的 get() 方法一样, getset() 方法返回的也是字符串值
  46. # 所以程序在将计数器的旧值返回给调用者之前,需要先将它转换成真正的数字
  47. return int(old_value)

在这个程序中,increase() 方法和 decrease() 方法在定义时都使用了 Python 的参数默认值特性:

  1. def increase(self, n=1):
  1. def decrease(self, n=1):

以上定义表明,如果用户直接以无参数的方式调用 increase() 或者 decrease() ,那么参数 n 的值将会被设置为 1

在设置了参数 n 之后,increase() 方法和 decrease() 方法会分别调用 INCRBY 命令和 DECRBY 命令,根据参数 n 的值,对给定的键执行加法或减法操作:

  1. # increase() 方法
  2. return self.client.incr(self.key, n)
  1. # decrease() 方法
  2. return self.client.decr(self.key, n)

注意,increase() 方法在内部调用的是 incr() 方法而不是 incrby() 方法,并且 decrease() 方法在内部调用的也是 decr() 方法而不是 decrby() 方法,这是因为在 redis-py 客户端中,INCR 命令和 INCRBY 命令都是由 incr() 方法负责执行的:

  • 如果用户在调用 incr() 方法时没有给定增量,那么 incr() 方法就默认用户指定的增量为 1 ,并执行 INCR 命令;

  • 另一方面,如果用户在调用 incr() 方法时给定了增量,那么 incr() 方法就会执行 INCRBY 命令,并根据给定的增量执行加法操作;

decr() 方法的情况也与此类似,只是被调用的命令变成了 DECR 命令和 DECRBY 命令。

以下代码展示了这个计数器的使用方法:

  1. >>> from redis import Redis
  2. >>> from counter import Counter
  3. >>> client = Redis(decode_responses=True)
  4. >>> counter = Counter(client, "counter::page_view")
  5. >>> counter.increase() # 将计数器的值加上 1
  6. 1
  7. >>> counter.increase() # 将计数器的值加上 1
  8. 2
  9. >>> counter.increase(10) # 将计数器的值加上 10
  10. 12
  11. >>> counter.decrease() # 将计数器的值减去 1
  12. 11
  13. >>> counter.decrease(5) # 将计数器的值减去 5
  14. 6
  15. >>> counter.reset() # 重置计数器,并返回旧值
  16. 6
  17. >>> counter.get() # 返回计数器当前的值
  18. 0