本文主要内容python
- Redis与其余软件的相同之处和不一样之处
- Redis的用法
- 使用Python示例代码与Redis进行简单的互动
- 使用Redis解决实际问题
Redis是一个远程内存数据库,它不只性能强劲,并且还具备复制特性以及为解决问题而生的独一无二的数据模型。Redis提供了5种不一样类型的数 据结构,各式各样的问题均可以很天然地映射到这些数据结构上:Redis的数据结构致力于帮助用户解决问题,而不会像其余数据库那样,要求用户扭曲问题来 适应数据库。除此以外,经过复制、持久化(persistence)和客户端分片(client-side sharding)等特性,用户能够很方便地将Redis扩展成一个可以包含数百GB数据、每秒处理上百万次请求的系统。git
笔者第一次使用Redis是在一家公司里面,这家公司须要对一个保存了6万个客户联系方式的关系数据库进行搜索,搜索能够根据名字、邮件地址、所在 地和电话号码来进行,每次搜索须要花费10~15秒的时间。在花了一周时间学习Redis的基础知识以后,我使用Redis重写了一个新的搜索引擎,而后 又花费了数周时间来仔细测试这个新系统,使它达到生产级别,最终这个新的搜索系统不只能够根据名字、邮件地址、所在地和电话号码等信息来过滤和排序客户联 系方式,而且每次操做均可以在50毫秒以内完成,这比原来的搜索系统足足快了 200 倍。阅读本书可让你学到不少小技巧、小窍门以及使用Redis解决某些常见问题的方法。程序员
本章将介绍Redis的适用范围,以及在不一样环境中使用Redis的方法(好比怎样跟不一样的组件和编程语言进行通讯等);而以后的章节则会展现各式各样的问题,以及使用Redis来解决这些问题的方法。github
如今你已经知道我是怎样开始使用Redis的了,也知道了这本书大概要讲些什么内容了,是时候更详细地介绍一下Redis,并说明为何应该使用Redis了。web
安装Redis和Python 附录A介绍了快速安装Redis和Python的方法。redis
在其余编程语言里面使用Redis 本书只展现了使用Python语言编写的示例代码,使用Ruby、Java和JavaScript(Node.js)编写的示例代码能够在这里找到:https://github.com/josiahcarlson/redis-in-action。使用Spring框架的读者能够经过查看http://www.springsource.org/spring-data/redis来学习如何在Spring框架中使用Redis。算法
前面对于Redis数据库的描述只说出了一部分真相。Redis是一个速度很是快的非关系数据库(non-relational database),它能够存储键(key)与5种不一样类型的值(value)之间的映射(mapping),能够将存储在内存的键值对数据持久化到硬 盘,可使用复制特性来扩展读性能,还可使用客户端分片1来扩展写性能,接下来的几节将分别介绍Redis的这几个特性。spring
若是你熟悉关系数据库,那么你确定写过用来关联两个表的数据的SQL查询。而Redis则属于人们常说的NoSQL数据库或者非关系数据库:Redis不使用表,它的数据库也不会预约义或者强制去要求用户对Redis存储的不一样数据进行关联。数据库
高性能键值缓存服务器memcached也常常被拿来与Redis进行比较:这二者均可用于存储键值映射,彼此的性能也相差无几,可是Redis能 够自动以两种不一样的方式将数据写入硬盘,而且Redis除了能存储普通的字符串键以外,还能够存储其余4种数据结构,而memcached只能存储普通的 字符串键。这些不一样之处使得Redis能够用于解决更为普遍的问题,而且既能够用做主数据库(primary database)使用,又能够做为其余存储系统的辅助数据库(auxiliary database)使用。编程
本书的后续章节会分别介绍将Redis用做主存储(primary storage)和二级存储(secondary storage)时的用法和查询模式。通常来讲,许多用户只会在Redis的性能或者功能是必要的状况下,才会将数据存储到Redis里面:若是程序对性 能的要求不高,又或者由于费用缘由而没办法将大量数据存储到内存里面,那么用户可能会选择使用关系数据库,或者其余非关系数据库。在实际中,读者应该根据 本身的需求来决定是否使用Redis,并考虑是将Redis用做主存储仍是辅助存储,以及如何经过复制、持久化和事务等手段保证数据的完整性。
表1-1展现了一部分在功能上与Redis有重叠的数据库服务器和缓存服务器,从这个表能够看出Redis与这些数据库及软件之间的区别。
表1-1 一些数据库和缓存服务器的特性与功能
名称 |
类型 |
数据存储选项 |
查询类型 |
附加功能 |
---|---|---|---|---|
Redis |
使用内存存储(in-memory)的非关系数据库 |
字符串、列表、集合、散列表、有序集合 |
每种数据类型都有本身的专属命令,另外还有批量操做(bulk operation)和不彻底(partial)的事务支持 |
发布与订阅,主从复制(master/slave replication),持久化,脚本(存储过程,stored procedure) |
memcached |
使用内存存储的键值缓存 |
键值之间的映射 |
建立命令、读取命令、更新命令、删除命令以及其余几个命令 |
为提高性能而设的多线程服务器 |
MySQL |
关系数据库 |
每一个数据库能够包含多个表,每一个表能够包含多个行;能够处理多个表的视图(view);支持空间(spatial)和第三方扩展 |
|
支持ACID性质(须要使用InnoDB),主从复制和主主复制 (master/master replication) |
PostgreSQL |
关系数据库 |
每一个数据库能够包含多个表,每一个表能够包含多个行;能够处理多个表的视图;支持空间和第三方扩展;支持可定制类型 |
|
支持ACID性质,主从复制,由第三方支持的多主复制(multi-master replication) |
MongoDB |
使用硬盘存储(on-disk)的非关系文档存储 |
每一个数据库能够包含多个表,每一个表能够包含多个无schema(schema-less)的BSON文档 |
建立命令、读取命令、更新命令、删除命令、条件查询命令等 |
支持map-reduce操做,主从复制,分片,空间索引(spatial index) |
在使用相似Redis这样的内存数据库时,一个首先要考虑的问题就是“当服务器被关闭时,服务器存储的数据将何去何从呢?”Redis拥有两种不一样 形式的持久化方法,它们均可以用小而紧凑的格式将存储在内存中的数据写入硬盘:第一种持久化方法为时间点转储(point-in-time dump),转储操做既能够在“指定时间段内有指定数量的写操做执行”这一条件被知足时执行,又能够经过调用两条转储到硬盘(dump-to-disk) 命令中的任何一条来执行;第二种持久化方法将全部修改了数据库的命令都写入一个只追加(append-only)文件里面,用户能够根据数据的重要程度, 将只追加写入设置为从不一样步(sync)、每秒同步一次或者每写入一个命令就同步一次。咱们将在第4章中更加深刻地讨论这些持久化选项。
另外,尽管Redis的性能很好,但受限于Redis的内存存储设计,有时候只使用一台Redis服务器可能没有办法处理全部请求。所以,为了扩展 Redis的读性能,并为Redis提供故障转移(failover)支持,Redis实现了主从复制特性:执行复制的从服务器会链接上主服务器,接收主 服务器发送的整个数据库的初始副本(copy);以后主服务器执行的写命令,都会被发送给全部链接着的从服务器去执行,从而实时地更新从服务器的数据集。 由于从服务器包含的数据会不断地进行更新,因此客户端能够向任意一个从服务器发送读请求,以此来避免对主服务器进行集中式的访问。咱们将在第4章中更加深 入地讨论Redis从服务器。
有memcached使用经验的读者可能知道,用户只能用APPEND
命令将数据添加到已有字符串的末尾。memcached的文档中声明,能够用APPEND
命 令来管理元素列表。这很好!用户能够将元素追加到一个字符串的末尾,并将那个字符串看成列表来使用。但随后如何删除这些元素呢?memcached采用的 办法是经过黑名单(blacklist)来隐藏列表里面的元素,从而避免对元素执行读取、更新、写入(包括在一次数据库查询以后执行的memcached 写入)等操做。相反地,Redis的LIST
和SET
容许用户直接添加或者删除元素。
使用Redis而不是memcached来解决问题,不只可让代码变得更简短、更易懂、更易维护,并且还可使代码的运行速度更快(由于用户不须要经过读取数据库来更新数据)。除此以外,在其余许多状况下,Redis的效率和易用性也比关系数据库要好得多。
数据库的一个常见用法是存储长期的报告数据,并将这些报告数据用做固定时间范围内的聚合数据(aggregates)。收集聚合数据的常见作法是: 先将各个行插入一个报告表里面,以后再经过扫描这些行来收集聚合数据,并根据收集到的聚合数据来更新聚合表中已有的那些行。之因此使用插入行的方式来存 储,是由于对于大部分数据库来讲,插入行操做的执行速度很是快(插入行只会在硬盘文件末尾进行写入)。不过,对表里面的行进行更新倒是一个速度至关慢的操 做,由于这种更新除了会引发一次随机读(random read)以外,还可能会引发一次随机写(random write)。而在Redis里面,用户能够直接使用原子的(atomic)INCR
命令及其变种来计算聚合数据,而且由于Redis将数据存储在内存里面2,并且发送给Redis的命令请求并不须要通过典型的查询分析器(parser)或者查询优化器(optimizer)进行处理,因此对Redis存储的数据执行随机写的速度老是很是迅速的。
使用 Redis 而不是关系数据库或者其余硬盘存储数据库,能够避免写入没必要要的临时数据,也免去了对临时数据进行扫描或者删除的麻烦,并最终改善程序的性能。虽然上面列举的都是一些简单的例子,但它们很好地证实了“工具会极大地改变人们解决问题的方式”这一点。
除了第6章提到的任务队列(task queue)以外,本书的大部份内容都致力于实时地解决问题。本书经过展现各类技术并提供可工做的代码来帮助读者消灭瓶颈、简化代码、收集数据、分发 (distribute)数据、构建实用程序(utility),并最终帮助读者更轻松地完成构建软件的任务。只要正确地使用书中介绍的技术,读者的软件 就能够扩展至令那些所谓的“Web扩展技术(web-sacle technology)”相形见绌的地步。
在了解了Redis是什么、它能作什么以及咱们为何要使用它以后,是时候来实际地使用一下它了。接下来的一节将对Redis提供的数据结构进行介绍,说明这些数据结构的做用,并展现操做这些数据结构的其中一部分命令。
正如以前的表1-1所示,Redis能够存储键与5种不一样数据结构类型之间的映射,这5种数据结构类型分别为STRING
(字符串)、LIST
(列表)、SET
(集合)、HASH
(散列)和ZSET
(有序集合)。有一部分Redis命令对于这5种结构都是通用的,如DEL
、TYPE
、RENAME
等;但也有一部分Redis命令只能对特定的一种或者两种结构使用,第3章将对Redis提供的命令进行更深刻的介绍。
大部分程序员应该都不会对Redis的STRING
、LIST
、HASH
这3种结构感到陌生,由于它们和不少编程语言内建的字符串、列表和散列等结构在实现和语义(semantics)方面都很是类似。有些编程语言还有集合数据结构,在实现和语义上相似于Redis的SET
。ZSET
在某种程度上是一种Redis特有的结构,可是当你熟悉了它以后,就会发现它也是一种很是有用的结构。表1-2对比了Redis提供的5种结构,说明了这些结构存储的值,并简单介绍了它们的语义。
表1-2 Redis提供的5种结构
结构类型 |
结构存储的值 |
结构的读写能力 |
---|---|---|
|
能够是字符串、整数或者浮点数 |
对整个字符串或者字符串的其中一部分执行操做;对整数和浮点数执行自增(increment)或者自减(decrement)操做 |
|
一个链表,链表上的每一个节点都包含了一个字符串 |
从链表的两端推入或者弹出元素;根据偏移量对链表进行修剪(trim);读取单个或者多个元素;根据值查找或者移除元素 |
|
包含字符串的无序收集器(unordered collection),而且被包含的每一个字符串都是独一无2、各不相同的 |
添加、获取、移除单个元素;检查一个元素是否存在于集合中;计算交集、并集、差集;从集合里面随机获取元素 |
|
包含键值对的无序散列表 |
添加、获取、移除单个键值对;获取全部键值对 |
|
字符串成员(member)与浮点数分值(score)之间的有序映射,元素的排列顺序由分值的大小决定 |
添加、获取、删除单个元素;根据分值范围(range)或者成员来获取元素 |
命令列表 本节在介绍每一个数据类型的时候,都会在一个表格里面展现一小部分处理这些数据结构的命令,以后的第 3 章会展现一个更详细(但仍不完整)的命令列表,完整的 Redis 命令列表能够在http://redis.io/commands找到。
这一节将介绍如何表示Redis的这5种结构,而且还会介绍Redis命令的使用方法,从而为本书的后续内容打好基础。本书展现的全部示例代码都是 用Python写的,若是读者已经按照附录A里面描述的方法安装好了Redis,那么应该也已经安装好了Python,以及在Python里面使用 Redis所需的客户端库。只要读者在电脑里面安装了Redis、Python和redis-py库,就能够在阅读本书的同时,尝试执行书中展现的示例代 码了。
请安装Redis和Python 在阅读后续内容以前,请读者先按照附录A中介绍的方法安装Redis和Python。若是读者以为附录A描述的安装方法过于复杂,那么这里有一个更简单的方法,但这个方法只能用于Debian系统(或者该系统的衍生系统):从http://redis.io/download下载Redis的压缩包,解压压缩包,执行make && sudo make install
,以后再执行sudo python -m easy_install redis hiredis
(hiredis是可选的,它是一个使用C语言编写的高性能Redis客户端)。
若是读者熟悉过程式编程语言或者面向对象编程语言,那么即便没有使用过Python,应该也能够看懂Python代码。另外一方面,若是读者决定使用其余编程语言来操做Redis,那么就须要本身来将本书的Python代码翻译成正在使用的语言的代码。
使用其余语言编写的示例代码 尽管没有包含在书中,但本书展现的Python示例代码已经被翻译成了Ruby代码、Java代码和JavaScript代码,这些翻译代码能够在https://github.com/josiahcarlson/redis-in-action下载到。跟Python编写的示例代码同样,这些翻译代码也包含相应的注释,方便读者参考。
为了让示例代码尽量地简单,本书会尽可能避免使用Python的高级特性,并使用函数而不是类或者其余东西来执行Redis操做,以此来将焦点放在 使用Redis解决问题上面,而没必要过多地关注Python的语法。本节将使用redis-cli控制台与Redis进行互动。首先,让咱们来了解一下 Redis中最简单的结构:STRING
。
Redis的STRING
和其余编程语言或者其余键值存储提供的字符串很是类似。本书在使用图片表示键和值的时候,一般会将键名(key name)和值的类型放在方框的顶部,并将值放在方框的里面。图1-1以键为hello
、值为world
的STRING
为例,分别标记了方框的各个部分。
图1-1 一个STRING
示例,键为hello
,值为world
STRING
拥有一些和其余键值存储类似的命令,好比GET
(获取值)、SET
(设置值)和DEL
(删除值)。若是读者已经按照附录A中给出的方法安装了Redis,那么能够根据代码清单1-1展现的例子,尝试使用redis-cli执行SET
、GET
和DEL
,表1-3描述了这3个命令的基本用法。
表1-3 字符串命令
命令 |
行为 |
---|---|
|
获取存储在给定键中的值 |
|
设置存储在给定键中的值 |
|
删除存储在给定键中的值(这个命令能够用于全部类型) |
代码清单1-1 SET
、GET
和DEL
的使用示例
使用 redis-cli 为了让读者在一开始就能便捷地与 Redis 进行交互,本章将使用redis-cli这个交互式客户端来介绍Redis命令。
除了可以GET
、SET
和DEL
字符串值以外,Redis还提供了一些能够对字符串的其中一部份内容进行读取和写入的命令,以及一些能对字符串存储的数值执行自增或者自减操做的命令。第3章将对这些命令进行介绍,可是在此以前,咱们还有许多基础知识须要了解,下面来看一下Redis的列表及其功能。
Redis对链表(linked-list)结构的支持使得它在键值存储的世界中独树一帜。一个列表结构能够有序地存储多个字符串,和表示字符串时使用的方法同样,本节使用带有标签的方框来表示列表,并将列表包含的元素放在方框里面。图1-2展现了一个这样的示例。
图1-2 list-key
是一个包含3个元素的列表键,注意列表里面的元素是能够重复的
Redis列表可执行的操做和不少编程语言里面的列表操做很是类似:LPUSH
命令和RPUSH
命令分别用于将元素推入列表的左端(left end)和右端(right end);LPOP
命令和RPOP
命令分别用于从列表的左端和右端弹出元素;LINDEX
命令用于获取列表在给定位置上的一个元素;LRANGE
命令用于获取列表在给定范围上的全部元素。代码清单1-2展现了一些列表命令的使用示例,表1-4简单介绍了示例中用到的各个命令。
表1-4 列表命令
命令 |
行为 |
---|---|
|
将给定值推入列表的右端 |
|
获取列表在给定范围上的全部值 |
|
获取列表在给定位置上的单个元素 |
|
从列表的左端弹出一个值,并返回被弹出的值 |
代码清单1-2 RPUSH
、LRANGE
、LINDEX
和LPOP
的使用示例
即便Redis的列表只支持以上提到的几个命令,它也已经能够用来解决不少问题了,但Redis并无就此止步——除了上面提到的命令之 外,Redis列表还拥有从列表里面移除元素的命令、将元素插入列表中间的命令、将列表修剪至指定长度(至关于从列表的其中一端或者两端移除元素)的命 令,以及其余一些命令。第3章将介绍许多列表命令,可是在此以前,让咱们先来了解一下Redis的集合。
Redis 的集合和列表均可以存储多个字符串,它们之间的不一样在于,列表能够存储多个相同的字符串,而集合则经过使用散列表来保证本身存储的每一个字符串都是各不相同 的(这些散列表只有键,但没有与键相关联的值)。本书表示集合的方法和表示列表的方法基本相同,图1-3展现了一个包含3个元素的示例集合。
图1-3 set-key
是一个包含3个元素的集合键
由于Redis的集合使用无序(unordered)方式存储元素,因此用户不能像使用列表那样,将元素推入集合的某一端,或者从集合的某一端弹出元素。不过用户可使用SADD
命令将元素添加到集合,或者使用SRAM
命令从集合里面移除元素。另外还能够经过SISMEMBER
命令快速地检查一个元素是否已经存在于集合中,或者使用SMEMBERS
命令获取集合包含的全部元素(若是集合包含的元素很是多,那么SMEMBERS
命令的执行速度可能会很慢,因此请谨慎地使用这个命令)。代码清单1-3展现了一些集合命令的使用示例,表1-5简单介绍了代码清单里面用到的各个命令。
代码清单1-3 SADD
、SMEMBERS
、SISMEMBER
和SREM
的使用示例
表1-5 集合命令
命令 |
行为 |
---|---|
|
将给定元素添加到集合 |
|
返回集合包含的全部元素 |
|
检查给定元素是否存在于集合中 |
|
若是给定的元素存在于集合中,那么移除这个元素 |
跟字符串和列表同样,集合除了基本的添加操做和移除操做以外,还支持不少其余操做,好比SINTER
、SUNION
、SDIFF``这
3个命令就能够分别执行常见的交集计算、并集计算和差集计算。第3章将对集合的相关命令进行更详细的介绍,另外第7章还会展现如何使用集合来解决多个问题。不过别心急,由于在Redis提供的5种数据结构中,还有两种咱们还没有了解,让咱们先来看看Redis的散列。
Redis的散列能够存储多个键值对之间的映射。和字符串同样,散列存储的值既能够是字符串又能够是数字值,而且用户一样能够对散列存储的数字值执行自增操做或者自减操做。图1-4展现了一个包含两个键值对的散列。
图1-4 hash-key
是一个包含两个键值对的散列键
散列在不少方面就像是一个微缩版的Redis,很多字符串命令都有相应的散列版本。代码清单1-4展现了怎样对散列执行插入元素、获取元素和移除元素等操做,表1-6简单介绍了代码清单里面用到的各个命令。
代码清单1-4 HSET
、HGET
、HGETALL
和HDEL
的使用示例
表1-6 散列命令
命令 |
行为 |
---|---|
|
在散列里面关联起给定的键值对 |
|
获取指定散列键的值 |
|
获取散列包含的全部键值对 |
|
若是给定键存在于散列里面,那么移除这个键 |
熟悉文档数据库的读者能够将Redis的散列看做是文档数据库里面的文档,而熟悉关系数据库的读者则能够将Redis的散列看做是关系数据库里面的行,由于散列、文档和行这三者都容许用户同时访问或者修改一个或多个域(field)。最后,让咱们来了解一下Redis的5种数据结构中的最后一种:有序集合。
有序集合和散列同样,都用于存储键值对:有序集合的键被称为成员(member),每一个成员都是独一无二的;而有序集合的值则被称为分值(score),分值必须为浮点数。有序集合是Redis里面惟一一个既能够根据成员访问元素(这一点和散列同样),又能够根据分值以及分值的排列顺序来访问元素的结构。图1-5展现了一个包含两个元素的有序集合示例。
图1-5 zset-key
是一个包含两个元素的有序集合键
和Redis的其余结构同样,用户能够对有序集合执行添加、移除和获取等操做,代码清单1-5展现了这些操做的执行示例,表1-7简单介绍了代码清单里面用到的各个命令。
代码清单1-5 ZADD
、ZRANGE
、ZRANGEBYSCORE
和ZREM
的使用示例
表1-7 有序集合命令
命令 |
行为 |
---|---|
|
将一个带有给定分值的成员添加到有序集合里面 |
|
根据元素在有序排列中所处的位置,从有序集合里面获取多个元素 |
|
获取有序集合在给定分值范围内的全部元素 |
|
若是给定成员存在于有序集合,那么移除这个成员 |
如今读者应该已经知道有序集合是什么和它能干什么了,到目前为止,咱们基本了解了Redis提供的5种结构。接下来的一节将展现如何经过结合散列的数据存储能力和有序集合内建的排序能力来解决一个常见的问题。
在对Redis提供的5种结构有了基本的了解以后,如今是时候来学习一下怎样使用这些结构来解决实际问题了。最近几年,愈来愈多的网站开始提供对网 页连接、文章或者问题进行投票的功能,其中包括图1-6展现的reddit以及图1-7展现的StackOverflow。这些网站会根据文章的发布时间 和文章得到的投票数量计算出一个评分,而后按照这个评分来决定如何排序和展现文章。本节将展现如何使用Redis来构建一个简单的文章投票网站的后端。
图1-6 Reddit是一个能够对文章进行投票的网站
图1-7 StackOverflow是一个能够对问题进行投票的网站
要构建一个文章投票网站,咱们首先要作的就是为了这个网站设置一些数值和限制条件:若是一篇文章得到了至少200张支持票(up vote),那么网站就认为这篇文章是一篇有趣的文章;假如这个网站天天发布1000篇文章,而其中的50篇符合网站对有趣文章的要求,那么网站要作的就 是把这50篇文章放到文章列表前100位至少一天;另外,这个网站暂时不提供投反对票(down vote)的功能。
为了产生一个可以随着时间流逝而不断减小的评分,程序须要根据文章的发布时间和当前时间来计算文章的评分,具体的计算方法为:将文章获得的支持票数量乘以一个常数,而后加上文章的发布时间,得出的结果就是文章的评分。
咱们使用从UTC时区1970年1月1日到如今为止通过的秒数来计算文章的评分,这个值一般被称为Unix时间。之因此选择使用 Unix时间,是由于在全部可以运行Redis的平台上面,使用编程语言获取这个值都是一件很是简单的事情。另外,计算评分时与支持票数量相乘的常量为 432,这个常量是经过将一天的秒数(86 400)除以文章展现一天所需的支持票数量(200)得出的:文章每得到一张支持票,程序就须要将文章的评分增长432分。
构建文章投票网站除了须要计算文章评分以外,还须要使用Redis结构存储网站上的各类信息。对于网站里的每篇文章,程序都使用一个散列来存储文章 的标题、指向文章的网址、发布文章的用户、文章的发布时间、文章获得的投票数量等信息,图1-8展现了一个使用散列来存储文章信息的例子。
图1-8 一个使用散列存储文章信息的例子
使用冒号做为分隔符 本书使用冒号(:
)来分隔名字的不一样部分:好比图 1-8 里面的键名article:92617
就使用了冒号来分隔单词article
和文章的ID号92617
,以此来构建命名空间(namespace)。使用:
做为分隔符只是个人我的喜爱,不过大部分Redis用户也都是这么作的,另外还有一些常见的分隔符,如句号(.
)、斜线(/
),有些人甚至还会使用管道符号(|
)。不管使用哪一个符号来作分隔符,都要保持分隔符的一致性。同时,请读者注意观察和学习本书使用冒号建立嵌套命名空间的方法。
咱们的文章投票网站将使用两个有序集合来有序地存储文章:第一个有序集合的成员为文章 ID,分值为文章的发布时间;第二个有序集合的成员一样为文章 ID,而分值则为文章的评分。经过这两个有序集合,网站既能够根据文章发布的前后顺序来展现文章,又能够根据文章评分的高低来展现文章,图1-9展现了这 两个有序集合的一个示例。
图1-9 两个有序集合分别记录了根据发布时间排序的文章和根据评分排序的文章
为了防止用户对同一篇文章进行屡次投票,网站须要为每篇文章记录一个已投票用户名单。为此,程序将为每篇文章建立一个集合,并使用这个集合来存储全部已投票用户的ID,图1-10展现了一个这样的集合示例。
图1-10 为100408号文章投过票的一部分用户
为了尽可能节约内存,咱们规定当一篇文章发布期满一周以后,用户将不能再对它进行投票,文章的评分将被固定下来,而记录文章已投票用户名单的集合也会被删除。
在实现投票功能以前,让咱们来看看图 1-11:这幅图展现了当115423号用户给100408号文章投票的时候,数据结构发生的变化。
图1-11 当115423号用户给100408号文章投票的时候,数据结构发生的变化
既然咱们已经知道了网站计算文章评分的方法,也知道了网站存储数据所需的数据结构,那么如今是时候实际地实现这个投票功能了!当用户尝试对一篇文章进行投票时,程序须要使用ZSCORE
命令检查记录文章发布时间的有序集合,判断文章的发布时间是否未超过一周。若是文章仍然处于能够投票的时间范围以内,那么程序将使用SADD
命令,尝试将用户添加到记录文章已投票用户名单的集合里面。若是添加操做执行成功的话,那么说明用户是第一次对这篇文章进行投票,程序将使用ZINCRBY
命令为文章的评分增长432分(ZINCRBY``命令
用于对有序集合成员的分值执行自增操做),并使用HINCRBY
命令对散列记录的文章投票数量进行更新(HINCRBY``命令
用于对散列存储的值执行自增操做),代码清单1-6展现了投票功能的实现代码。
代码清单1-6 article_vote()
函数
Redis事务 从技术上来说,要正确地实现投票功能,咱们须要将代码清单1-6里面的SADD
、ZINCRBY
和HINCRBY
这3个命令放到一个事务里面执行,不过由于本书要等到第4章才介绍Redis事务,因此咱们暂时忽略这个问题。
这个投票功能仍是很不错的,对吧?那么发布文章的功能要怎么实现呢?
发布一篇新文章首先须要建立一个新的文章ID,这项工做能够经过对一个计数器(counter)执行INCR
命令来完成。接着程序须要使用SADD
将文章发布者的ID添加到记录文章已投票用户名单的集合里面,并使用EXPIRE
命令为这个集合设置一个过时时间,让Redis在文章发布期满一周以后自动删除这个集合。以后,程序会使用HMSET
命令来存储文章的相关信息,并执行两个ZADD
命令,将文章的初始评分(initial score)和发布时间分别添加到两个相应的有序集合里面。代码清单1-7展现了发布新文章功能的实现代码。
代码清单1-7 post_article()
函数
好了,咱们已经陆续实现了文章投票功能和文章发布功能,接下来要考虑的就是如何取出评分最高的文章以及如何取出最新发布的文章了。为了实现这两个功能,程序须要先使用ZREVRANGE
命令取出多个文章ID,而后再对每一个文章ID执行一次HGETALL
命令来取出文章的详细信息,这个方法既能够用于取出评分最高的文章,又能够用于取出最新发布的文章。这里特别要注意的一点是,由于有序集合会根据成员的分值从小到大地排列元素,因此使用ZREVRANGE
命令,以“分值从大到小”的排列顺序取出文章ID才是正确的作法,代码清单1-8展现了文章获取功能的实现函数。
代码清单1-8 get_articles()
函数
Python的默认值参数和关键字参数 代码清单1-8中的get_articles()
函数为order
参数设置了默认值score:
。 Python语言的初学者可能会对“默认值参数”以及“根据名字(而不是位置)来传入参数”的一些细节感到陌生。若是读者在理解函数定义或者参数传递方面 有困难,能够考虑去看看《Python语言教程》,教程里面对这两个方面进行了很好的介绍,点击如下短连接就能够直接访问教程的相关章节:http://mng.bz/KM5x。
虽然咱们构建的网站如今已经能够展现最新发布的文章和评分最高的文章了,但它还不具有目前不少投票网站都支持的群组(group)功能:这个功能可 以让用户只看见与特定话题有关的文章,好比与“可爱的动物”有关的文章、与“政治”有关的文章、与“Java编程”有关的文章或者介绍“Redis用法” 的文章等等。接下来的一节将向咱们展现为文章投票网站添加群组功能的方法。
群组功能由两个部分组成,一个部分负责记录文章属于哪一个群组,另外一个部分负责取出群组里面的文章。为了记录各个群组都保存了哪些文章,网站须要为每 个群组建立一个集合,并将全部同属一个群组的文章ID都记录到那个集合里面。代码清单1-9展现了将文章添加到群组里面的方法,以及从群组里面移除文章的 方法。
代码清单1-9 add_remove_groups()
函数
初看上去,可能会有读者以为使用集合来记录群组文章并无多大用处。到目前为止,读者只看到了集合结构检查某个元素是否存在的能力,但实际上Redis不只能够对多个集合执行操做,甚至在一些状况下,还能够在集合和有序集合之间执行操做。
为了可以根据评分对群组文章进行排序和分页(paging),网站须要将同一个群组里面的全部文章都按照评分有序地存储到一个有序集合里面。Redis的ZINTERSTORE
命令能够接受多个集合和多个有序集合做为输入,找出全部同时存在于集合和有序集合的成员,并以几种不一样的方式来组合(combine)这些成员的分值(全部集合成员的分值都会被视为是1)。对于咱们的文章投票网站来讲,程序须要使用ZINTERSTORE
命令选出相同成员中最大的那个分值来做为交集成员的分值:取决于所使用的排序选项,这些分值既能够是文章的评分,也能够是文章的发布时间。
图 1-12 展现了对一个包含少许文章的群组集合和一个包含大量文章及评分的有序集合执行ZINTERSTORE
命令的过程,注意观察那些同时出如今集合和有序集合里面的文章是怎样被添加到结果有序集合里面的。
图1-12 对集合groups:programming
和有序集合score:
进行交集计算得出了新的有序集合score:programming
,它包含了全部同时存在于集合groups:programming
和有序集合score:
的成员。由于集合groups:programming
的全部成员的分值都被视为是1
,而有序集合score:
的全部成员的分值都大于1
,而且此次交集计算挑选的分值为相同成员中的最大分值,因此有序集合score:programming
的成员的分值其实是由有序集合score:
的成员的分值来决定的
经过对存储群组文章的集合和存储文章评分的有序集合执行ZINTERSTORE
命令,程序能够获得按照文章评分排序的群组文章;而经过对存储群组文章的集合和存储文章发布时间的有序集合执行ZINTERSTORE
命令,程序则能够获得按照文章发布时间排序的群组文章。若是群组包含的文章很是多,那么执行ZINTERSTORE
命令就会比较花时间,为了尽可能减小Redis的工做量,程序会将这个命令的计算结果缓存60秒。另外,咱们还重用了已有的get_articles()
函数来分页并获取群组文章,代码清单1-10展现了网站从群组里面获取一整页文章的方法。
代码清单1-10 get_group_articles()
函数
有些网站只容许用户将文章放在一个或者两个群组里面(其中一个是“全部文章”群组,另外一个是最适合文章的群组)。在这种状况下,最好直接将文章所在的群组记录到存储文章信息的散列里面,并在article_vote()
函数的末尾增长一个ZINCRBY
命 令调用,用于更新文章在群组中的评分。可是在这个示例里面,咱们构建的文章投票网站容许一篇文章同时属于多个群组(好比一篇文章能够同时属于“编程”和 “算法”两个群组),因此对于一篇同时属于多个群组的文章来讲,更新文章的评分意味着程序须要对文章所属的所有群组执行自增操做。在这种状况下,若是一篇 文章同时属于不少个群组,那么更新文章评分这一操做可能会变得至关耗时,所以,咱们在get_group_articles()
函数里面对ZINTERSTORE
命令的执行结果进行了缓存处理,以此来尽可能减小ZINTERSTORE
命令的执行次数。开发者在灵活性或限制条件之间的取舍将改变程序存储和更新数据的方式,这一点对于任何数据库都是适用的,Redis也不例外。
练习:实现投反对票的功能
咱们的示例目前只实现了投支持票的功能,可是在不少实际的网站里面,反对票也能给用户提供有用的反馈信息。所以,请读者能想办法在
article_vote()
函数和post_article()
函数里面添加投反对票的功能。除此以外,读者还能够尝试为用户提供对调投票的功能:好比将支持票转换成反对票,或者将反对票转换成支持票。提示:若是读者在实现对调投票功能时出现了困难,能够参考一下第3章介绍的SMOVE
命令。
好的,如今咱们已经成功地构建起了一个展现最受欢迎文章的网站后端,这个网站能够获取文章、发布文章、对文章进行投票甚至还能够对文章进行分组。如 果你以为前面展现的内容很差理解,或者弄不懂这些示例,又或者没办法运行本书提供的源代码,那么请阅读下一节来了解如何获取帮助。
当你遇到与Redis有关的问题时,不要惧怕求助于别人,由于其余人可能也遇到过相似的问题。首先,你能够根据错误信息在搜索引擎里面进行查找,看是否有所发现。
若是搜索一无所得,又或者你遇到的问题与本书的示例代码有关,那么你能够到Manning出版社提供的论坛里面发问(http://www.manning-sandbox.com/forum.jspa?forumID=809),我和其余熟悉本书的人将为你提供帮助。
若是你遇到的问题与Redis自己有关,又或者你正在解决的问题在这本书里面没有出现过,那么你能够到Redis的邮件列表里面发问(https://groups.google.com/d/forum/redis-db/),一样地,我和其余熟悉Redis的人将为你提供帮助。
最后,若是你遇到的问题与某个函数库或者某种编程语言有关,那么比起在Redis邮件列表里面发帖提问,更好的方法是直接到你正在使用的那个函数库或者那种编程语言的邮件列表或论坛里面寻求帮助。
本章对Redis进行了初步的介绍,说明了Redis与其余数据库的相同之处和不一样之处,以及一些读者可能会使用Redis的理由。在阅读本书的后 续章节以前,请记住本书的目标并非构建一个完整的应用或者工具,而是展现各式各样的问题,并给出使用Redis来解决这些问题的办法。
本章但愿向读者传达这样一个概念:Redis是一个能够用来解决问题的工具,它既拥有其余数据库不具有的数据结构,又拥有内存存储(这使得 Redis的速度很是快)、远程(这使得Redis能够与多个客户端和服务器进行链接)、持久化(这使得服务器能够在重启以后仍然保持重启以前的数据)和 可扩展(经过主从复制和分片)等多个特性,这使得用户能够以熟悉的方式为各类不一样的问题构建解决方案。
在阅读本书的后续章节时,请读者注意本身解决问题的方式发生了什么变化:你也许会惊讶地发现,本身思考数据问题的方式已经从原来的“怎样将个人想法塞进数据库的表和行里面”,变成了“使用哪一种Redis数据结构来解决这个问题比较好呢?”。