示例:ID 生成器

在构建应用程序的时候,我们经常会用到各式各样的 ID (identifier,标识符)。比如说,储存用户信息的程序在每次出现一个新用户的时候就需要创建一个新的用户 ID ,而博客程序在作者每次发表一篇新文章的时候也需要创建一个新的文章 ID ,诸如此类。

ID 通常会以数字形式出现,并且通过递增的方式来创建出新的 ID 。比如说,如果当前最新的 ID 值为 10086 ,那么下一个 ID 就应该是 10087 ,而再下一个 ID 则是 10088 ,以此类推。

代码清单 2-6 展示了一个使用字符串键实现的 ID 生成器,这个生成器通过执行 INCR 命令来产生新的 ID ,并且它还可以通过执行 SET 命令来保留指定数字之前的 ID ,从而避免用户为了得到某个指定的 ID 而生成大量无效 ID 。


代码清单 2-6 使用字符串键实现的 ID 生成器:/string/id_generator.py

  1. class IdGenerator:
  2.  
  3. def __init__(self, client, key):
  4. self.client = client
  5. self.key = key
  6.  
  7. def produce(self):
  8. """
  9. 生成并返回下一个 ID 。
  10. """
  11. return self.client.incr(self.key)
  12.  
  13. def reserve(self, n):
  14. """
  15. 保留前 n 个 ID ,使得之后执行的 produce() 方法产生的 ID 都大于 n 。
  16. 为了避免 produce() 方法产生重复 ID ,
  17. 这个方法只能在 produce() 方法和 reserve() 方法都没有执行过的情况下使用。
  18. 这个方法在 ID 被成功保留时返回 True ,
  19. 在 produce() 方法或 reserve() 方法已经执行过而导致保留失败时返回 False 。
  20. """
  21. result = self.client.set(self.key, n, nx=True)
  22. return result is True

在这个 ID 生成器程序中,produce() 方法要做的就是调用 INCR 命令,对字符串键储存的整数值执行加一操作,并将执行加法操作之后得到的新值用作 ID 。

另一方面,用于保留指定 ID 的 reserve() 方法是通过执行 SET 命令为键设置值来实现的:当用户把一个字符串键的值设置为 N 之后,对这个键执行 INCR 命令总是会返回比 N 更大的值,因此在效果上就相当于把所有小于等于 N 的 ID 都保留下来了。

需要注意的是,这种保留 ID 的方法只能在字符串键还没有值的情况下使用,如果用户已经使用过 produce() 方法来生成 ID ,又或者已经执行过 reserve() 方法来保留 ID ,那么再使用 SET 命令去设置 ID 值可能就会导致 produce() 方法产生出一些已经用过的 ID ,并因此引发 ID 冲突。

为此,reserve() 方法在设置字符串键时使用了带有 NX 选项的 SET 命令,从而确保了对键的设置操作只会在键不存在的情况下执行:

  1. self.client.set(self.key, n, nx=True)

以下代码展示了这个 ID 生成器的使用方法:

  1. >>> from redis import Redis
  2. >>> from id_generator import IdGenerator
  3. >>> client = Redis(decode_responses=True)
  4. >>> id_generator = IdGenerator(client, "user::id")
  5. >>> id_generator.reserve(1000000) # 保留前一百万个 ID
  6. True
  7. >>> id_generator.produce() # 生成 ID ,这些 ID 的值都大于一百万
  8. 1000001
  9. >>> id_generator.produce()
  10. 1000002
  11. >>> id_generator.produce()
  12. 1000003
  13. >>> id_generator.reserve(1000) # 键已经有值,无法再次执行 reserve() 方法
  14. False