Redis使用手册(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-29 17:50:45

点击下载

作者:黄健宏

出版社:机械工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

Redis使用手册

Redis使用手册试读:

前言

时光荏苒,距离我的第一本书《Redis设计与实现》出版已经过去了整整五年。在这五年间,Redis从一个不为人熟知、只有少量应用的崭新数据库,逐渐变成了内存数据库领域的事实标准。

五年前,当人们提到Redis的时候,语气通常都充满了怀疑:“Redis我还是第一次听说,它好用吗?”“Redis比起Memcached有什么优势?”“用Redis存储数据安全吗,不会丢数据吧?”然而时至今日,经过大量的实践应用,Redis简洁高效、安全稳定的特性已经深入人心。无论是国内还是国外,从五百强公司到小型初创公司都在使用Redis,很多云服务提供商还以Redis为基础构建了相应的缓存服务、消息队列服务以及内存存储服务,当你使用这些服务时,实际上就是在使用Redis。

Redis除了变得越来越受欢迎之外,另一个变化就是更新速度越来越快,功能也变得越来越多、越来越强大,比如说,Redis的数据结构数量已经从过去的五种增加到了九种,RDB-AOF混合持久化模式的引入使得用户不必再陷入“鱼和熊掌不可兼得”的难题中,而集群功能和模块机制的引入则让Redis在性能和功能上拥有了近乎无限的扩展能力。

综上所述,可以说现在的Redis跟五年前比起来已经完全不一样了,而如何向读者讲述新版Redis方方面面的变化,则是每一本Redis书都必须回答的问题。本书以服务Redis初学者和使用者为目标,介绍了Redis日常使用中最常用到的部分,并以“命令描述+代码示例”的模式详细列举了各个Redis命令的用法和用例。我相信无论是刚开始学习Redis的读者,还是每天都要使用Redis的读者,在阅读本书的时候都会有所收获。

虽然在写作本书的过程中已经思虑再三并且几易其稿,但书中难免还是会有错误或者遗漏的地方。如果读者朋友在阅读的过程中发现任何错误,或有任何疑问、建议,都可以通过邮箱huangz1990@gmail.com或者huangz.me中列出的联系方式来联系我。由于技术研究和写作工作较为繁重,本人可能无法每封邮件都予以回复,但只要有来信我就一定会阅读,决不食言。

最后,感谢吴怡编辑在写作过程中给我的帮助和指导,感谢赵亮宇编辑为本书出版所做的努力,还要感谢我的家人和朋友,如果没有他们的关怀和支持,本书不可能顺利完成。黄健宏2019年8月于清远第1章 引言

欢迎来到本书的第1章。在这一章,我们首先会了解到一些关于Redis的基本信息,比如它提供了什么功能,它能做什么,它的优点是什么,有哪些公司使用它等等。

之后我们会快速地了解本书各个章节的具体编排,并完成一些学习Redis的前期准备工作,比如安装Redis服务器等。在一切准备就绪之后,我们就会开始学习如何执行Redis命令,以及如何通过配置选项对Redis服务器进行配置。

在本章的最后,我们还会看到获取本书示例代码的方法,并知悉本书使用的Redis版本以及本书配套的读者服务网站。1.1 Redis简介

Redis是一个主要由Salvatore Sanfilippo(Antirez)开发的开源内存数据结构存储器,经常用作数据库、缓存以及消息代理等。

Redis因其丰富的数据结构、极快的速度、齐全的功能而为人所知,它是目前内存数据库方面的事实标准,在互联网上有非常广泛的应用,微博、Twitter、GitHub、Stack Overflow、知乎等国内外公司都大量地使用了Redis。

Redis之所以广受开发者欢迎,跟它自身拥有强大的功能以及简洁的设计不无关系。

Redis最重要的特点有以下几种(参见图1-1):

·结构丰富

Redis为用户提供了字符串、散列、列表、集合、有序集合、HyperLogLog、位图、流、地理坐标等一系列丰富的数据结构,每种数据结构都适用于解决特定的问题。在有需要的时候,用户还可以通过事务、Lua脚本、模块等特性,扩展已有数据结构的功能,甚至从零实现自己专属的数据结构。通过这些数据结构和特性,Redis能够确保用户可以使用适合的工具去解决问题。

·功能完备

在上述数据结构的基础上,Redis提供了很多非常实用的附加功能,比如自动过期、流水线、事务、数据持久化等,这些功能能够帮助用户将Redis应用在更多不同的场景中,或者为用户带来便利。更重要的是,Redis不仅可以单机使用,还可以多机使用:通过Redis自带的复制、Sentinel和集群功能,用户可以将自己的数据库扩展至任意大小。无论你运营的是一个小型的个人网站,还是一个为上千万消费者服务的热门站点,都可以在Redis中找到你想要的功能,并将其部署到你的服务器中。

·速度飞快

Redis是一款内存数据库,它将所有数据存储在内存中。因为计算机访问内存的速度要远远高于访问硬盘的速度,所以与基于硬盘设计的传统数据库相比,Redis在数据的存取速度方面具有天然的优势。但Redis并没有因此放弃在效率方面的追求,相反,Redis的开发者在实现各项数据结构和特性的时候都经过了大量考量,在底层选用了很多非常高效的数据结构和算法,以此来确保每个操作都可以在尽可能短的时间内完成,并且尽可能地节省内存。

·用户友好“虽然Redis提供了很多很棒的数据结构和特性,但如果它们使用起来非常困难的话,那么这一切就没有意义。”如果你对此有所担心的话,那么现在可以打消你的顾虑了!Redis API遵循的是UNIX“一次只做一件事,并把它做好”的设计哲学。Redis的API虽然丰富,但它们大部分都非常简短,并且只需接受几个参数就可以完成用户指定的操作。更棒的是,Redis在官方网站(redis.io)上为每个API以及相关特性都提供了详尽的文档,并且客户端本身也可以在线查询这些文档。当你遇到文档无法解决的问题时,还可以在Redis项目的GitHub页面(github.com/antirez/redis)、Google Group(groups.google.com/forum/#!forum/redis-db)甚至作者的Twitter(twitter.com/antirez)上提问。

·支持广泛

正如之前所说,Redis已经在互联网公司得到广泛应用,许多开发者为不同的编程语言开发了相应的客户端(redis.io/clients),大多数编程语言的使用者都可以轻而易举地找到所需的客户端,然后直接开始使用Redis。此外,包括亚马逊、谷歌、RedisLabs、阿里云和腾讯云在内的多个云服务提供商都提供了基于Redis或兼容Redis的服务,如果你不打算自己搭建Redis服务器,那么上述提供商可能是不错的选择。图1-1 Redis特色一览1.2 内容编排

本书第1章为引言,接下来的第2~20章正文分成了“数据结构与应用”“附加功能”和“多机功能”三个部分。

在“数据结构与应用”部分,介绍了Redis核心的9种数据结构,列举了操作这些数据结构的众多命令及其详细信息,并在其中穿插介绍了多个使用Redis命令构建应用程序的示例。通过这些示例,读者可以进一步加深对命令的认识,并学会如何在实际中使用这些命令,达到学以致用的目的。“附加功能”部分介绍了Redis在数据结构的基础上为用户提供的额外功能,包括管理数据结构的数据库管理功能和自动过期功能,将数据结构持久化至硬盘从而避免数据丢失的持久化功能,提高多条命令执行效率的流水线功能,保证命令安全性的事务和Lua脚本功能,以及扩展服务器特性的模块功能等。这些功能在为用户提供方便的同时,也进一步扩大了Redis的适用范围,读者可以通过阅读这一部分来学会如何将Redis应用在更多场景中。“多机功能”部分介绍了Redis的3项多机功能,分别是复制、Sentinel和集群。其中复制用于创建多个Redis服务器的副本,并借此提升整个Redis系统的读性能以及容灾能力。Sentinel在复制的基础上,为Redis系统提供了自动的故障转移功能,从而使整个系统可以更健壮地运行。最后,通过使用Redis集群,用户可以在线扩展Redis系统的读写能力。读者可以通过阅读这一部分来获得扩展Redis读写性能的相关知识,并根据自己的情况为Redis系统选择合适的扩展方式。1.3 目标读者

本书面向所有Redis初学者和Redis使用者,是学习和日常使用Redis必不可少的参考书。

对于Redis初学者来说,本书的章节经过妥善的编排,按照从简单到复杂的顺序详细罗列了Redis的各项特性,因此Redis初学者只需要按顺序阅读,就可以循序渐进地学习到具体的Redis知识,而穿插其中的应用示例则让读者有机会亲自实践书中介绍的命令,真正做到学以致用。

对于Redis使用者来说,本书包含了大量对Redis新版特性的介绍,读者可以通过本书了解到最新的Redis知识。除此之外,对于日常的命令文档查找和应用示例查找,本书也做了优化,读者可以通过目录和附录快速定位命令和示例,便于日常查阅。1.4 预备工作

本书包含大量Redis命令操作实例和Python代码应用示例,执行和测试这些示例需要用到Redis服务器及其附带的redis-cli客户端、Python编程环境和redis-py客户端,如果你尚未安装这些软件,那么请查阅附录A和附录B并按照指引进行安装。

在正确安装Redis服务器之后,可以通过执行以下命令启动Redis服务器:$ redis-server28393:C 02 Jul 2019 23:49:25.952 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo28393:C 02 Jul 2019 23:49:25.952 # Redis version=999.999.999, bits=64, commit=0cabe0cf, modified=1, pid=28393, just started28393:C 02 Jul 2019 23:49:25.952 # Warning: no config file specified, using the default config. In order to specify a config file use /Users/huangz/code/redis/src/redis-server /path/to/redis.conf28393:M 02 Jul 2019 23:49:25.953 * Increased maximum number of open files to 10032 (it was originally set to 256). _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 999.999.999 (0cabe0cf/1) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 28393 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-'28393:M 02 Jul 2019 23:49:25.954 # Server initialized28393:M 02 Jul 2019 23:49:25.954 * Ready to accept connection

并通过以下命令启动redis-cli客户端:$ redis-cli127.0.0.1:6379>

以及通过以下命令启动Python解释器并载入redis-py库:$ python3Python 3.7.3 (default, Mar 27 2019, 09:23:15)[Clang 10.0.1 (clang-1001.0.46.3)] on darwinType "help", "copyright", "credits" or "license" for more information.>>> from redis import Redis>>>

上述准备工作圆满完成之后,我们就可以开始学习Redis命令的基本知识了。1.5 执行命令

Redis服务器通过接收客户端发送的命令请求来执行指定的命令,并在命令执行完毕之后通过响应将命令的执行结果返回给客户端,如图1-2所示,结果中的内容称为命令回复。图1-2 命令请求与响应

Redis为每种数据结构和功能特性都提供了相应的命令,掌握如何使用这些命令是学习Redis的重中之重。幸运的是,大部分Redis命令都非常简单,只需要给出少量参数就可以完成非常强大的操作。

Redis的所有命令都由一个命令名后跟任意多个参数以及可选项组成:COMMAND [arg1 arg2 arg3 ...] [[OPTION1 value1] [OPTION2 value2] [...]]

在本书中,命令和可选项的名字通常以大写字母形式出现,命令参数和可选项的值则以小写字母形式出现。比如上例中的COMMAND就是命令的名字,OPTION1和OPTION2是可选项的名字,arg1、arg2和arg3是命令的参数,value1和value2是可选项的值。

命令描述中的方括号“[]”仅用于包围命令中可选的参数和选项,在执行命令的时候并不需要给出这些方括号。命令描述中的“...”用于表示命令接受任意数量的参数或可选项。

关于Redis命令格式的描述已经足够多了,现在让我们来看一个实际的例子。Redis的PING命令接受一条可选的消息作为参数,这个命令通常用于测试客户端和服务器之间的连接是否正常:PING [message]

如果用户以无参数形式执行这个命令,那么服务器在连接正常的情况下,将向客户端返回PONG作为回复:127.0.0.1:6379> PINGPONG

但是,如果用户给定了可选的消息,那么服务器将原封不动地向客户端返回该消息:127.0.0.1:6379> PING "hello world""hello world"

另外,如果服务器与客户端的连接不正常,那么客户端将返回一个错误:-- 客户端未能连接服务器,返回一个连接错误127.0.0.1:6379> PINGCould not connect to Redis at 127.0.0.1:6379: Connection refused

我们为这个命令调用添加了一条注释,用于说明客户端遇到的问题。在本书中,redis-cli客户端的命令执行示例都使用“--”作为注释前缀,这些注释仅用于对被执行的命令做进一步的说明,并不是被执行命令的一部分。图1-3中给出了在redis-cli中执行Redis命令的示意图。图1-3 在redis-cli中执行Redis命令1.6 配置服务器

在阅读本书的过程中,有时候我们还需要使用配置选项对Redis服务器进行配置,这可以通过两种方法来完成。

第一种方法是在启动Redis服务器的时候给定配置选项作为参数,格式为:$ redis-server --OPTION1 [value1 value2 ...] --OPTION2 [value1 value2 ...] [...]

例如,Redis服务器默认使用6379作为端口号,但如果你想使用10086而不是6379作为端口号,那么可以在启动Redis服务器时通过设定port可选项来指定想要的端口号:$ redis-server --port 10086

第二种方法是在启动Redis服务器的时候为其提供配置文件,并将想要修改的配置选项写在配置文件中:$ redis-server /path/to/your/file

例如,为了将Redis服务器的端口号改为12345,我们可以在当前文件夹中创建配置文件myredis.conf,并在文件中包含以下内容:port 12345

然后在启动Redis服务器时向其提供该配置文件:$ redis-server myredis.conf1.7 示例代码

正如前面提到的那样,本书提供了大量Python代码示例,这些示例的源码可以通过访问以下页面获取:github.com/huangz1990/RedisGuide-code。

本书在展示代码示例的同时,会在示例标题的旁边给出源代码的具体访问路径。比如对于代码清单1-1中展示的连接检查脚本check_connection.py来说,该文件就位于/introduction文件夹中:

代码清单1-1 检查连接的脚本:/introduction/check_connection.pyfrom redis import Redisclient = Redis()# ping()方法在连接正常时将返回Trueif client.ping() is True: print("connecting")else: print("disconnected")1.8 版本说明

本书基于Redis 5.0版本撰写,这是创作本书期间Redis的最新版本。

为了方便使用旧版Redis的读者,本书在介绍每个命令和特性的时候都指出了它们具体可用的版本,读者通过查阅这一信息就可以知道特定的命令和特性在自己的版本中是否可用。

得益于Redis极好的向后兼容性,即使读者将来使用的是Redis 6.0、7.0甚至更新的版本,本书中的绝大部分知识将仍是有效的。1.9 读者服务网站

本书配套了读者服务网站RedisGuide.com,其中列举了本书的介绍信息、购买链接、目录、试读章节、示例代码和勘误等内容,有兴趣的读者朋友可以浏览一下。1.10 启程

一切准备就绪,是时候开始我们的Redis旅程了。在接下来的一章,我们将开始学习Redis最基本的数据结构——字符串。第一部分 数据结构与应用

第2章 字符串

第3章 散列

第4章 列表

第5章 集合

第6章 有序集合

第7章 HyperLogLog

第8章 位图

第9章 地理坐标

第10章 流第2章 字符串

字符串(string)键是Redis最基本的键值对类型,这种类型的键值对会在数据库中把单独的一个键和单独的一个值关联起来,被关联的键和值既可以是普通的文字数据,也可以是图片、视频、音频、压缩文件等更为复杂的二进制数据。

图2-1展示了数据库视角下的4个字符串键,其中:

·与键"message"相关联的值是"hello world"。

·与键"number"相关联的值是"10086"。

·与键"homepage"相关联的值是"redis.io"。

·与键"redis-logo.jpg"相关联的值是二进制数据"\xff\xd8\xff\xe0\x00\x10JFIF\x00..."。图2-1 数据库中的字符串键示例

Redis为字符串键提供了一系列操作命令,通过使用这些命令,用户可以:

·为字符串键设置值。

·获取字符串键的值。

·在获取旧值的同时为字符串键设置新值。

·同时为多个字符串键设置值,或者同时获取多个字符串键的值。

·获取字符串值的长度。

·获取字符串值指定索引范围内的内容,或者对字符串值指定索引范围内的内容进行修改。

·将一些内容追加到字符串值的末尾。

·对字符串键存储的整数值或者浮点数值执行加法操作或减法操作。

接下来将对以上提到的字符串键命令进行介绍,并演示如何使用这些命令去解决各种实际问题。2.1 SET:为字符串键设置值

创建字符串键最常用的方法就是使用SET命令,这个命令可以为一个字符串键设置相应的值。在最基本的情况下,用户只需要向SET命令提供一个键和一个值就可以了:SET key value

与之前提到过的一样,这里的键和值既可以是文字也可以是二进制数据。

SET命令在成功创建字符串键之后将返回OK作为结果。比如通过执行以下命令,我们可以创建出一个字符串键,它的键为"number",值为"10086":redis> SET number "10086"OK

再比如,通过执行以下命令,我们可以创建出一个键为"book",值为"The Design and Implementation of Redis"的字符串键:redis> SET book "The Design and Implementation of Redis"OK图2-2 执行SET命令之前数据库的状态

图2-2和图2-3分别展示了数据库在以上两条SET命令执行之前以及执行之后的状态。图2-3 执行SET命令之后数据库的状态数据库键的存放方式

为了方便阅读,本书会将数据库中新出现的键放置到已有键的下方。比如在上面展示的数据库图2-3中,我们就将新添加的"number"键和"book"键放置到了已有键的下方。

在实际中,Redis数据库是以无序的方式存放数据库键的,一个新加入的键可能会出现在数据库的任何位置上,因此我们在使用Redis的过程中不应该对键在数据库中的摆放位置做任何假设,以免造成错误。2.1.1 改变覆盖规则

在默认情况下,对一个已经设置了值的字符串键执行SET命令将导致键的旧值被新值覆盖。举个例子,如果我们连续执行以下两条SET命令,那么第一条SET命令设置的值将被第二条SET命令设置的值所覆盖:redis> SET song_title "Get Wild"OKredis> SET song_title "Running to Horizon"OK

在第二条SET命令执行完毕之后,song_title键的值将从原来的"Get Wild"变为"Running to Horizon"。

从Redis 2.6.12版本开始,用户可以通过向SET命令提供可选的NX选项或者XX选项来指示SET命令是否要覆盖一个已经存在的值:SET key value [NX|XX]

如果用户在执行SET命令时给定了NX选项,那么SET命令只会在键没有值的情况下执行设置操作,并返回OK表示设置成功;如果键已经存在,那么SET命令将放弃执行设置操作,并返回空值nil表示设置失败。

以下代码展示了带有NX选项的SET命令的行为:redis> SET password "123456" NXOK -- 对尚未有值的password键进行设置,成功redis> SET password "999999" NX(nil) -- password键已经有了值,设置失败

因为第二条SET命令没有改变password键的值,所以password键的值仍然是刚开始时设置的"123456"。

如果用户在执行SET命令时给定了XX选项,那么SET命令只会在键已经有值的情况下执行设置操作,并返回OK表示设置成功;如果给定的键并没有值,那么SET命令将放弃执行设置操作,并返回空值表示设置失败。

举个例子,如果我们对一个没有值的键mongodb-homepage执行以下SET命令,那么命令将因为XX选项的作用而放弃执行设置操作:redis> SET mongodb-homepage "mongodb.com" XX(nil)

相反,如果我们对一个已经有值的键执行带有XX选项的SET命令,那么命令将使用新值去覆盖已有的旧值:redis> SET mysql-homepage "mysql.org"OK -- 为键mysql-homepage设置一个值redis> SET mysql-homepage "mysql.com" XXOK -- 对键的值进行更新

在第二条SET命令执行之后,mysql-homepage键的值将从原来的"mysql.org"更新为"mysql.com"。2.1.2 其他信息

复杂度:O(1)。

版本要求:不带任何可选项的SET命令从Redis 1.0.0版本开始可用;带有NX、XX等可选项的SET命令从Redis 2.6.12版本开始可用。2.2 GET:获取字符串键的值

用户可以使用GET命令从数据库中获取指定字符串键的值:GET key

GET命令接受一个字符串键作为参数,然后返回与该键相关联的值。图2-4 使用GET命令获取数据库键的值

比如对于图2-4所示的数据库来说,我们可以通过执行以下GET命令来取得各个字符串键相关联的值:redis> GET message"hello world"redis> GET number"10086"redis> GET homepage"redis.io"

另外,如果用户给定的字符串键在数据库中并没有与之相关联的值,那么GET命令将返回一个空值:redis> GET date(nil)

上面这个GET命令的执行结果表示数据库中并不存在date键,也没有与之相关联的值。

因为Redis的数据库要求所有键必须拥有与之相关联的值,所以如果一个键有值,那么我们就说这个键存在于数据库;相反,如果一个键没有值,那么我们就说这个键不存在于数据库。比如对于上面展示的几个键来说,date键就不存在于数据库,而message键、number键和homepage键则存在于数据库。其他信息

复杂度:O(1)。

版本要求:GET命令从Redis 1.0.0开始可用。2.3 GETSET:获取旧值并设置新值

GETSET命令就像GET命令和SET命令的组合版本,GETSET首先获取字符串键目前已有的值,接着为键设置新值,最后把之前获取到的旧值返回给用户:GETSET key new_value

以下代码展示了如何使用GETSET命令去获取number键的旧值并为它设置新值:redis> GET number -- number键现在的值为"10086""10086"redis> GETSET number "12345""10086" -- 返回旧值redis> GET number -- number键的值已被更新为"12345""12345"

如果被设置的键并不存在于数据库,那么GETSET命令将返回空值作为键的旧值:redis> GET counter(nil) -- 键不存在redis> GETSET counter 50(nil) -- 返回空值作为旧值redis> GET counter"50"其他信息

复杂度:O(1)。

版本要求:GETSET命令从Redis 1.0.0开始可用。示例:缓存

对数据进行缓存是Redis最常见的用法之一,因为缓存操作是指把数据存储在内存而不是硬盘上,而访问内存远比访问硬盘的速度要快得多,所以用户可以通过把需要快速访问的数据存储在Redis中来提升应用程序的速度。

代码清单2-1展示了一个使用Redis实现的缓存程序代码,这个程序使用SET命令将需要缓存的数据存储到指定的字符串键中,并使用GET命令来从指定的字符串键中获取被缓存的数据。

代码清单2-1 使用字符串键实现的缓存程序:/string/cache.pyclass Cache: def __init__(self, client): self.client = client def set(self, key, value): """ 把需要被缓存的数据存储到键key里面,如果键key已经有值,那么使用新值去覆盖旧值 """ self.client.set(key, value) def get(self, key): """ 获取存储在键key里面的缓存数据,如果数据不存在,那么返回None """ return self.client.get(key) def update(self, key, new_value): """ 对键key存储的缓存数据进行更新,并返回键key在被更新之前存储的缓存数据。 如果键key之前并没有存储数据,那么返回None """ return self.client.getset(key, new_value)

除了用于设置缓存的set()方法以及用于获取缓存的get()方法之外,缓存程序还提供了由GETSET命令实现的update()方法,这个方法可以让用户在对缓存进行设置的同时,获得之前被缓存的旧值。用户可以根据自己的需要决定是使用set()方法还是update()方法对缓存进行设置。

以下代码展示了如何使用这个程序来缓存一个HTML页面,并在需要时获取它:>>> from redis import Redis>>> from cache import Cache>>> client = Redis(decode_responses=True) # 使用文本编码方式打开客户端>>> cache = Cache(client)>>> cache.set("greeting-page", "

hello world

")>>> cache.get("greeting-page")'

hello world

'>>> cache.update("greeting-page", "

good morning

")'

hello world

'>>> cache.get("greeting-page")'

good morning

'

因为Redis的字符串键不仅可以存储文本数据,还可以存储二进制数据,所以这个缓存程序不仅可以用来缓存网页等文本数据,还可以用来缓存图片和视频等二进制数据。比如,如果你正在运营一个图片网站,那么你同样可以使用这个缓存程序来缓存网站上的热门图片,从而提高用户访问这些热门图片的速度。

作为例子,以下代码展示了将Redis的Logo图片缓存到键redis-logo.jpg中的方法:>>> from redis import Redis>>> from cache import Cache>>> client = Redis # 使用二进制编码方式打开客户端>>> cache = Cache(client)>>> image = open("redis-logo.jpg", "rb") # 以二进制只读方式打开图片文件>>> data = image.read() # 读取文件内容>>> image.close() # 关闭文件>>> cache.set("redis-logo.jpg", data) # 将内存缓存到键redis-logo.jpg中>>> cache.get("redis-logo.jpg")[:20] # 读取二进制数据的前20个字节b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00'提示 在测试以上两段代码的时候,请务必以正确的编码方式打开客户端(第一段代码采用文本方式,第二段代码采用二进制方式),否则测试代码将会出现编码错误。示例:锁

锁是一种同步机制,用于保证一项资源在任何时候只能被一个进程使用,如果有其他进程想要使用相同的资源,那么就必须等待,直到正在使用资源的进程放弃使用权为止。

一个锁的实现通常会有获取(acquire)和释放(release)这两种操作:

·获取操作用于取得资源的独占使用权。在任何时候,最多只能有一个进程取得锁,我们把成功取得锁的这个进程称为锁的持有者。在锁已经被持有的情况下,所有尝试再次获取锁的操作都会失败。

·释放操作用于放弃资源的独占使用权,一般由锁的持有者调用。在锁被释放之后,其他进程就可以再次尝试获取这个锁了。

代码清单2-2展示了一个使用字符串键实现的锁程序,这个程序会根据给定的字符串键是否有值来判断锁是否已经被获取,而针对锁的获取操作和释放操作则是分别通过设置字符串键和删除字符串键来完成的。

代码清单2-2 使用字符串键实现的锁程序:/string/lock.pyVALUE_OF_LOCK = "locking"class Lock: def __init__(self, client, key): self.client = client self.key = key def acquire(self): """ 尝试获取锁。成功时返回True,失败时返回False """ result = self.client.set(self.key, VALUE_OF_LOCK, nx=True) return result is True def release(self): """ 尝试释放锁。成功时返回True,失败时返回False """ return self.client.delete(self.key) == 1

获取操作acquire()方法是通过执行带有NX选项的SET命令来实现的:result = self.client.set(self.key, VALUE_OF_LOCK, nx=True)

NX选项的值确保了代表锁的字符串键只会在没有值的情况下被设置:

·如果给定的字符串键没有值,那么说明锁尚未被获取,SET命令将执行设置操作,并将result变量的值设置为True。

·如果给定的字符串键已经有值了,那么说明锁已经被获取,SET命令将放弃执行设置操作,并将result变量的值设置为None。

acquire()方法最后会通过检查result变量的值是否为True来判断自己是否成功取得了锁。

释放操作release()方法使用了之前没有介绍过的DEL命令,这个命令接受一个或多个数据库键作为参数,尝试删除这些键以及与之相关联的值,并返回被成功删除的键的数量作为结果:DEL key [key ...]

因为Redis的DEL命令和Python的del关键字重名,所以在redis-py客户端中,执行DEL命令实际上是通过调用delete()方法来完成的:self.client.delete(self.key) == 1

release()方法通过检查delete()方法的返回值是否为1来判断删除操作是否执行成功:如果用户尝试对一个尚未被获取的锁执行release()方法,那么方法将返回false,表示没有锁被释放。

在使用DEL命令删除代表锁的字符串键之后,字符串键将重新回到没有值的状态,这时用户就可以再次调用acquire()方法去获取锁了。

以下代码演示了这个锁的使用方法:>>> from redis import Redis>>> from lock import Lock>>> client = Redis(decode_responses=True)>>> lock = Lock(client, 'test-lock')>>> lock.acquire() # 成功获取锁True>>> lock.acquire() # 锁已被获取,无法再次获取False>>> lock.release() # 释放锁True>>> lock.acquire() # 锁释放之后可以再次被获取True

虽然代码清单2-2中展示的锁实现了基本的获取和释放功能,但它并不完美:

·因为这个锁的释放操作无法验证进程的身份,所以无论执行释放操作的进程是否为锁的持有者,锁都会被释放。如果锁被持有者以外的其他进程释放,那么系统中可能会同时出现多个锁,导致锁的唯一性被破坏。

·这个锁的获取操作不能设置最大加锁时间,因而无法让锁在超过给定的时限之后自动释放。因此,如果持有锁的进程因为故障或者编程错误而没有在退出之前主动释放锁,那么锁就会一直处于已被获取的状态,导致其他进程永远无法取得锁。

本书后续将继续改进这个锁的实现,使得它可以解决这两个问题。2.4 MSET:一次为多个字符串键设置值

除了SET命令和GETSET命令之外,Redis还提供了MSET命令用于对字符串键进行设置。与SET命令和GETSET命令只能设置单个字符串键不同,MSET命令可以一次为多个字符串键设置值:MSET key value [key value ...]

以下代码展示了如何使用一条MSET命令去设置message、number和homepage这3个键:redis> MSET message "hello world" number "10086" homepage "redis.io"OKredis> GET message"hello world"redis> GET number"10086"redis> GET homepage"redis.io"

与SET命令一样,MSET命令也会在执行设置操作之后返回OK表示设置成功。此外,如果给定的字符串键已经有相关联的值,那么MSET命令也会直接使用新值去覆盖已有的旧值。

比如以下代码就展示了如何使用MSET命令去覆盖上一个MSET命令为message键和number键设置的值:redis> MSET message "good morning!" number "12345"OKredis> GET message"good morning!"redis> GET number"12345"

MSET命令除了可以让用户更为方便地执行多个设置操作之外,还能有效地提高程序的效率:执行多条SET命令需要客户端和服务器之间进行多次网络通信,并因此耗费大量的时间;而使用一条MSET命令去代替多条SET命令只需要一次网络通信,从而有效地减少程序执行多个设置操作时的时间。其他信息

复杂度:O(N),其中N为用户给定的字符串键数量。

版本要求:MSET命令从Redis 1.0.1开始可用。2.5 MGET:一次获取多个字符串键的值

MGET命令就是一个多键版本的GET命令,MGET接受一个或多个字符串键作为参数,并返回这些字符串键的值:MGET key [key ...]

MGET命令返回一个列表作为结果,这个列表按照用户执行命令时给定键的顺序排列各个键的值。比如,列表的第一个元素就是第一个给定键的值,第二个元素是第二个给定键的值,以此类推。

作为例子,以下代码展示了如何使用一条MGET命令去获取message、number和homepage这3个键的值:redis> MGET message number homepage1) "hello world" -- message键的值2) "10086" -- number键的值3) "redis.io" -- homepage键的值

与GET命令一样,MGET命令在碰到不存在的键时也会返回空值:redis> MGET not-exists-key1) (nil)

与MSET命令类似,MGET命令也可以将执行多个获取操作所需的网络通信次数从原来的N次降低至只需一次,从而有效地提高程序的运行效率。其他信息

复杂度:O(N),其中N为用户给定的字符串键数量。

版本要求:MGET命令从Redis 1.0.0开始可用。2.6 MSETNX:只在键不存在的情况下,一次为多个字符串键设置值

MSETNX命令与MSET命令一样,都可以对多个字符串键进行设置:MSETNX key value [key value ...]

MSETNX与MSET的主要区别在于,MSETNX只会在所有给定键都不存在的情况下对键进行设置,而不会像MSET那样直接覆盖键已有的值:如果在给定键当中,即使有一个键已经有值了,那么MSETNX命令也会放弃对所有给定键的设置操作。MSETNX命令在成功执行设置操作时返回1,在放弃执行设置操作时则返回0。

在以下代码中,因为键k4已经存在,所以MSETNX将放弃对键k1、k2、k3和k4进行设置操作:redis> MGET k1 k2 k3 k41) (nil) -- 键k1、 k2和k3都不存在2) (nil)3) (nil)4) "hello world" -- 键k4已存在redis> MSETNX k1 "one" k2 "two" k3 "three" k4 "four"(integer) 0 -- 因为键k4已存在,所以MSETNX未能执行设置操作redis> MGET k1 k2 k3 k4 -- 各个键的值没有变化1) (nil)2) (nil)3) (nil)4) "hello world"

如果只对不存在的键k1、k2和k3进行设置,那么MSETNX可以正常地完成设置操作:redis> MSETNX k1 "one" k2 "two" k3 "three"(integer) 1 -- 所有给定键都不存在,成功执行设置操作redis> MGET k1 k2 k3 k41) "one" -- 刚刚使用MSETNX设置的3个值2) "two"3) "three"4) "hello world" -- 之前已经存在的键k4的值没有改变其他信息

复杂度:O(N),其中N为用户给定的字符串键数量。

版本要求:MSETNX命令从Redis 1.0.1开始可用。示例:存储文章信息

在构建应用程序的时候,我们经常会需要批量地设置和获取多项信息。以博客程序为例:

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

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

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

代码清单2-3 文章存储程序:/string/article.pyfrom time import time # time()函数用于获取当前UNIX时间戳class Article: def __init__(self, client, article_id): self.client = client self.id = str(article_id) self.title_key = "article::" + self.id + "::title" self.content_key = "article::" + self.id + "::content" self.author_key = "article::" + self.id + "::author" self.create_at_key = "article::" + self.id + "::create_at" def create(self, title, content, author): """ 创建一篇新的文章,创建成功时返回True,因为文章已存在而导致创建失败时返回False """ article_data = { self.title_key: title, self.content_key: content, self.author_key: author, self.create_at_key: time() } return self.client.msetnx(article_data) def get(self): """ 返回ID对应的文章信息 """ result = self.client.mget(self.title_key, self.content_key, self.author_key, self.create_at_key) return {"id": self.id, "title": result[0], "content": result[1], "author": result[2], "create_at": result[3]} def update(self, title=None, content=None, author=None): """ 对文章的各项信息进行更新,更新成功时返回True,失败时返回False """ article_data = {} if title is not None: article_data[self.title_key] = title if content is not None: article_data[self.content_key] = content if author is not None: article_data[self.author_key] = author return self.client.mset(article_data)

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

接着程序会使用这个字符串格式的文章ID,构建出用于存储文章各项信息的字符串键的键名:self.title_key = "article::" + self.id + "::title"self.content_key = "article::" + self.id + "::content"self.author_key = "article::" + self.id + "::author"self.create_at_key = "article::" + self.id + "::create_at"

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

当用户想要根据给定的文章ID创建具体的文章时,就需要调用create()方法,并传入文章的标题、内容以及作者信息作为参数。create()方法会把以上信息以及当前的UNIX时间戳放入一个Python字典里面:article_data = { self.title_key: title, self.content_key: content, self.author_key: author, self.create_at_key: time()}

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

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

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

·如果给定的字符串键尚未有值,那么create()方法将根据用户给定的信息创建文章,并在成功之后返回True。

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

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

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载