• 当用户想要注册成为博客的作者时,程序就需要把这位作者的名字、账号、密码、注册时间等多项信息储存起来,并在用户登录的时候取出这些信息。

    • 又比如说,当博客的作者想要撰写一篇新文章的时候,程序就需要把文章的标题、内容、作者、发表时间等多项信息储存起来,并在用户阅读文章的时候取出这些信息。

    通过使用 命令、 MSETNX 命令以及 MGET 命令,我们可以实现上面提到的这些批量设置操作和批量获取操作。比如代码清单 2-3 就展示了一个文章储存程序,这个程序使用 MSET 命令和 MSETNX 命令将文章的标题、内容、作者、发表时间等多项信息储存到不同的字符串键里面,并通过 MGET 命令从这些键里面获取文章的各项信息。


    代码清单 2-3 文章储存程序:/string/article.py


    这个文章储存程序比较长,让我们来逐个分析它的各项功能。首先,Article 类的初始化方法 init() 接受一个 Redis 客户端和一个文章 ID 作为参数,并将文章 ID 从数字转换为字符串:

    1. self.id = str(article_id)

    接着程序会使用这个字符串格式的文章 ID ,构建出用于储存文章各项信息的字符串键的键名:

    1. self.title_key = "article::" + self.id + "::title"
    2. self.content_key = "article::" + self.id + "::content"
    3. self.author_key = "article::" + self.id + "::author"
    4. self.create_at_key = "article::" + self.id + "::create_at"

    在这些键当中,第一个键将用于储存文章的标题,第二个键将用于储存文章的内容,第三个键将用于储存文章的作者,而第四个键则会用于储存文章的创建时间。

    当用户想要根据给定的文章 ID 创建具体的文章时,他就需要调用 create() 方法,并传入文章的标题、内容以及作者作为参数。create() 方法会把以上这些信息以及当前的 UNIX 时间戳放入到一个 Python 字典里面:

    article_data 字典的键储存了代表文章各项信息的字符串键的键名,而与这些键相关联的则是这些字符串键将要被设置的值。接下来,程序会调用 MSETNX 命令,对字典中给定的字符串键进行设置:

    1. self.client.msetnx(article_data)

    因为 create() 方法的设置操作是通过 MSETNX 命令来进行的,所以这一操作只会在所有给定字符串键都不存在的情况下进行:

    • 如果给定的字符串键已经有值了,那么说明与给定 ID 相对应的文章已经存在。在这种情况下,MSETNX 命令将放弃执行设置操作,并且 create() 方法也会向调用者返回 False 表示文章创建失败。

    在成功创建文章之后,用户就可以使用 get() 方法去获取文章的各项信息了。get() 方法会调用 MGET 命令,从各个字符串键里面取出文章的标题、内容、作者等信息,并把这些信息储存到 列表中:

    1. result = self.client.mget(self.title_key,
    2. self.content_key,
    3. self.create_at_key)

    为了让用户可以更方便地访问文章的各项信息,get() 方法会将储存在 result 列表里面的文章信息放入到一个字典里面,然后再返回给用户:

    这样做的好处有两点:

    • 它隐藏了 get() 方法由 MGET 命令实现这一底层细节。如果程序直接向用户返回 result 列表,那么用户就必须知道列表中的各个元素代表文章的哪一项信息,然后通过列表索引来访问文章的各项信息。这种做法非常不方便,而且也非常容易出错。

    • 返回一个字典可以让用户以 dict[key] 这样的方式去访问文章的各个属性,比如使用 article["title"] 去访问文章的标题,使用 article["content"] 去访问文章的内容,诸如此类,这使得针对文章数据的各项操作可以更方便地进行。

    另外要注意的一点是,虽然用户可以通过访问 Article 类的 id 属性来获得文章的 ID ,但是为了方便起见,get() 方法在返回文章信息的时候也会将文章的 ID 包含在字典里面一并返回。

    对文章信息进行更新的 update() 方法是整个程序最复杂的部分。首先,为了让用户可以自由选择需要更新的信息项,这个函数在定义时使用了 Python 的具名参数特性:

    1. def update(self, title=None, content=None, author=None):

    通过具名参数,用户可以根据自己想要更新的文章信息项来决定传入哪个参数,而不需要更新的信息项则会被赋予默认值 None

    • 比如说,如果用户只想要更新文章的标题,那么只需要调用 update(title=new_title) 即可;

    • 又比如说,如果用户想要同时更新文章的内容和作者,那么只需要调用 update(content=new_content, author=new_author) 即可;

    诸如此类。

    1. article_data = {}
    2. if title is not None:
    3. article_data[self.title_key] = title
    4. if content is not None:
    5. article_data[self.content_key] = content
    6. if author is not None:

    article_data 字典中的键就是需要更新的字符串键的键名,而与之相关联的则是这些字符串键的新值。

    在一切准备就绪之后,update() 方法会根据 article_data 字典中设置好的键值对,调用 MSET 命令对文章进行更新:

    以下代码展示了这个文章储存程序的使用方法:

    1. >>> from redis import Redis
    2. >>> from article import Article
    3. >>> client = Redis(decode_responses=True)
    4. >>> article = Article(client, 10086) # 指定文章 ID
    5. >>> article.create('message', 'hello world', 'peter') # 创建文章
    6. True
    7. >>> article.get() # 获取文章
    8. {'id': '10086', 'title': 'message', 'content': 'hello world',
    9. 'author': 'peter', 'create_at': '1551199163.4296808'}
    10. >>> article.update(author="john") # 更新文章的作者
    11. True
    12. >>> article.get() # 再次获取文章
    13. 'author': 'john', 'create_at': '1551199163.4296808'}

    表 1-1 展示了上面这段代码创建出的键,以及这些键的值。


    表 1-1 文章数据储存示例

    注解

    键的命名格式

    Article 程序使用了多个字符串键去储存文章信息,并且每个字符串键的名字都是以 article::<id>::<attribute> 格式命名的,这是一种 Redis 使用惯例:Redis 用户通常会为逻辑上相关联的键设置相同的前缀,并通过分隔符来区分键名的各个部分,以此来构建一种键的命名格式。

    比如对于 article::10086::titlearticle::10086::author 这些键来说,article 前缀表明这些键都储存着与文章信息相关的数据,而分隔符 :: 则区分开了键名里面的前缀、ID 以及具体的属性。除了 :: 符号之外,常用的键名分隔符还包括 . 符号,比如 article.10086.title ;或者 -> 符号,比如 article->10086->title ;又或者 | 符号,比如 article|10086|title ;诸如此类。

    分隔符的选择通常只是一个个人喜好的问题,而键名的具体格式也可以根据需要进行构造:比如说,如果你不喜欢 article::<id>::<attribute> 格式,那么也可以考虑使用 article::<attribute>::<id> 格式,诸如此类。唯一需要注意的是,一个程序应该只使用一种键名分隔符,并且持续地使用同一种键名格式,以免造成混乱。

    通过使用相同的格式去命名逻辑上相关联的键,我们可以让程序产生的数据结构变得更容易被理解,并且在有需要的时候,还可以根据特定的键名格式,在数据库里面以模式匹配的方式查找指定的键。