游戏开发工具

Redis的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

class IdGenerator:

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

    def produce(self):
        """
        生成并返回下一个ID。
        """
        return self.client.incr(self.key)

    def reserve(self, n):
        """
        保留前n个ID,使得之后执行的produce()方法产生的ID都大于n。为了避免produce()
        方法产生重复ID,这个方法只能在produce()方法和reserve()方法都没有执行过的情况下使
        用。这个方法在ID被成功保留时返回True,在produce()方法或reserve()方法已经执行
        过而导致保留失败时返回False
        """
        result = self.client.set(self.key, n, nx=True)
        return result is True


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


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


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


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

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


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

>>> from redis import Redis
>>> from id_generator import IdGenerator
>>> client = Redis(decode_responses=True)
>>> id_generator = IdGenerator(client, "user::id")
>>> id_generator.reserve(1000000)  # 保留前100万个ID
True
>>> id_generator.produce()         # 生成ID,这些ID的值都大于100万
1000001
>>> id_generator.produce()
1000002
>>> id_generator.produce()
1000003
>>> id_generator.reserve(1000)     # 键已经有值,无法再次执行reserve()方法
False