本章将介绍一些没有在第1章和第2章出现过的Redis命令, 学习这些命令有助于读者在已 有示例的基础上构建更为复杂的程序 , 并学会如何更好地去解决自己遇到的问题。本章将使用客户端与Redis服务器进行简单的互动, 并以此来介绍命令的用法, 如果读者想要看一些更为具体 的代码示例, 那么可以阅读第2章。.
3.1 字符串
本书在第1章和第2章曾经说过,Redis的字符串就是一个由字节组成的序列,它们和很多编程语言里面的字符串没有什么明显的不同,跟C或者C++风格的字符数组也相去不远。在 Redis里面,字符串可以存储以下3种类型的值。
• 字节串(byte string)。
• 整数。
• 浮点数。
用户可以通过给定一个任意的数值,对存储着整数或者浮点数的字符串执行自增(increment)或者自减(decrement)操作,在有需要的时候, Redis还会将整数转换成浮点数。整数的取值范围和系统的长整数(long integer)的取值范围相同(在32位系统上,整数就是32位有符号整数,在64位系统上,整数就是64位有符号整数),而浮点数的取值范围和精度则与IEEE 754标准的双精度浮点数(double)相同。Redis 明确地区分字节串、整数和浮点数的做法是一种优势,比起只能够存储字节串的做法,Redis的做法在数据表现方面具有更大的灵活性。
本节将对Redis里面最简单的结构 介绍基本的数值自增和自减操作,以及二进制位(bit)和子串(substring)处理命令, 读者可能会惊讶地发现,Redis 里面最简单的结构居然也有如此强大的作用。
当用户将一个值存储到Redis字符串里面的时候,如果这个值可以被解释(inte1-pret)为十进制整数或者浮点数,那么Redis会察觉到这一点,并允许用户对这个字符串执行各种INCR*和DECR*操作。如果用户对一个不存在的键或者一个保存了空串的键执行自增或者自减操作,那么Redis在执行操作时会将这个键的值当作是0来处理。如果用户尝试对一个值无法被解释为整数或者浮点数的字符串键执行自增或者自减操作,那么Redis将向用户返回一个错误。
除了自增操作和自减操作之外, Redis 还拥有对字节串的其中一部分内容进行读取或者写入 的操作(这些操作也可以用于整数或者浮点数, 但这种用法并不常见), 本书在第 9 章将展示如何使用这些操作来高效地将结构化数据打包 (pack) 存储到字符串键里面。 表 3-2 展示了用来处理字符串子串和二进制位的命令。
在使用SETRANGE或者SETB工T命令对字符串进行写入的时候,如果字符串当前的长度不 能满足写入的要求,那么Redis会自动地使用空字节(null)来将字符串扩展至所需的长度,然 后才执行写入或者更新操作。在使用GETRANGE读取字符串的时候,超出字符串末尾的数据会 被视为是空串,而在使用GETB工T读取二进制位串的时候,超出字符串末尾的二进制位会被视为 是0。代码清单3-2展示了一些字符串处理命令的使用示例。
3.2列表
Redis的列表允许用户从序列的两端推入或者弹出元素,获取列表元素等。除此之外,列表还可以用来存储任务信息、最近浏览过得文章或者常用联系人信息。
本节将对列表这个由多个字符串值组成的有序序列结构进行介绍,并展示一些最常用的列表处理信息。
有几个列表命令可以将元素从一个列表移动到另一个列表,或者阻塞 (block)执行命令的客户端直到有其他客户端给列表添加元素为止,这些命令在第1章都没有介绍过,表3-4列出了这些阻塞弹出命令和元素移动命令。
列表的一个主要优点在于它可以包含多个字符串值,这使得用户可以将数据集中在同一个地 方。Redis的集合也提供了与列表类似的特性,但集合只能保存各不相同的元素。接下来的一节 中就让我们来看看不能保存相同元素的集合都能做些什么。
3.3 集合
Redis的集合以无序的方式来存储多个各不相同的元素,用户可以快速地对集合执行添加 元素操作、移除元素操作以及检查一个元素是否存在于集合里。
通过使用上面展示的命令,我们可以将各不相同的多个元素添加到集合里面,比如第1章就使用集合记录了文章已投票用户名单,以及文章属于哪个群组。但集合真正厉害的地方在于组合和关联多个集合。
3.4 散列
从功能上来说, Redis 为散列值提供了一些与字符串值相同的特性, 使得散列非常适用于将一些相关的数据存储在一起。我们可以把这种数据聚集看作是关系数据库中的行, 或者文档数据库中的文档。
本节将对最常用的散列命令进行介绍:其中包括添加和删除键值对的命令、获取所有键值对的命令, 以及对键值对的值进行自增或者自减操作的命令。阅读这一节可以让读者学习到如何将数据存储到散列里面,以及这样做的好处是什么。
像HMGET和HMSET这种批量处理多个键的命令即可以给用户带来方便,又可以通过减少命令的调用次数以及客户端与Redis之间的通信往返次数来提升Redis性能。
正如前面所说,在对散列进行处理的时候,如果键值对的值的体积非常庞大,那么用户可以先使用 HKEYS获取散列的所有键 , 然后通过只获取必要的值来减少需要传输的数据址。除此之外,用户还可以像使用SISMEMBER检查一个元素是否存在于集合里面一样, 使用HEXISTS检查一个键是否存在于散列里面。另外第1章也用到了本节刚刚回顾过的 HINCRBY来记录文章被投票的次 数。
3.5 有序集合
和散列存储着键与值之间的映射类似,有序集合也存储着成员与分值之间的映射,并且提供了分值的处理命令,以及根据分值大小有序地获取或扫描成员和分值的命令。
有序集合的并集运算和交集运算在刚开始接触时可能会比较难懂,所以本节将使用图片来展示交集运算和并集运算的执行过程。图3-1展示了对两个输入有序集合执行交集运算并得到输出 有序集合的过程,这次交集运算使用的是默认的聚合函数sum,所以输出有序集合成员的分值都 是通过加法计算得出的。
并集运算和交集运算不同,只要某个成员存在于至少一个输入有序集合里面,那么这个成员 就会被包含在输出有序集合里面。图3-2展示了使用聚合函数min执行并集运算的过程,min 函数在多个输入有序集合都包含同一个成员的情况下,会将最小的那个分值设置为这个成员在输 出有序集合的分值。
3.6 发布与订阅
一般来说,发布与订阅(又称 pub/sub)的特点是订阅者(listener)负责订阅频道(chrumel), 发送者(publisher)负责向频道发送二进制字符串消息(binary string message)。 每当有消息被发送至给定频道时,频道的所有订阅者都会收 到消息。我们也可以把频道看作是电台,其中订阅者可以同时收听多个电台,而发送者则可以在任何电台发送消息 。
虽然Redis的发布与订阅模式非常有用,但本书只在这一节和8.5节中使用了这个模式, 这样做的原因有以下两个。
第一个原因和Redis系统的稳定性有关。 对于旧版Redis来说,如果一个客户端订阅了某个或某些频道,但它读取消息的速度却不够快的话, 那么不断积压的消息就会使得Redis输出缓冲区的体积变得越来越大, 这可能会导致Redis的速度变慢, 甚至直接崩溃。 也可能会导致Redis被操作系统强制杀死, 甚至导致操作系统本身不可用。新版的Redis不会出现这种问题,因为它会自动断开不符合client-output-buffer-巨mit pubsub配置选项要求的订阅客户端(本书第8章将对这个选项做更详细的介绍)。
第二个原因和数据传输的可靠性有关。任何网络系统在执行操作时都可能会遇上断线情况,而断线产生的连接错误通常会使得网络连接两端中的其中一端进行重新连接。
3.7 其他命令
3.7.1 排序
Redis的排序操作和其他编程语言的排序操作一样,都可以根据某种比较规则对一系列元素进行有序的排列。负责执行排序操作的 SORT 命令可以根据字符串、 列表、 集合、 有序集合、 散列这 5 种键里面存储着的数据, 对列表、集合以及有序集合进行排序。如果读者之前曾经使用过关系数据库的话, 那么可以将 SORT 命令看作是 SQL 语言里的 order by 子句。表 3-12 展示了 SORT 命令的定义。
SORT 命令不仅可以对列表进行排序, 还可以对集合进行排序,然后返回一个列表形式的排序结果。
3.7.2 基本的Redis事物
Redis有5个命令可以用于在不被打断的情况下对多个键执行操作,它们分别是WATCH.MULTI.EXEC和UNWATCH等多个命令的事务是什么样子的。
什么是Redis的基本事务
Redis的基本事务需要用到MULTI命令和EXEC命令,这种事务可以让一个客户端在不被其他客户端打断的情况下执行多个命令。当一个事务执行完毕之后,Redis才会处理其他客户端的处理。
要在Redis里面执行事务,我们首先需要执行MULTI命令,然后输入那些我们想要的事务里面执行的命令,最后再执行EXEC命令。当Redis从一个客户端那里接收到MULTI命令时,REID会将这个客户端之后发送的所有命令都放入到一个队列里面,直到这个客户端发送EXEC命令为止。
3.7.3 键的过期时间
在使用Redis存储数据的时候, 有些数据可能在某个时间点之后就不再有用了, 用户可以使 用DEL命令显式地删除这些无用数据,也可以通过Redis的过期时间(expiration)特性来让一个 键在给定的时限(timeout)之后自动被删除。当我们说一个键 ”带有生存时间(time to live)"或者一个键 ”会在特定时间之后过期(expire)"时 , 我们指的是Redis会在这个键的过期时间到达 时自动删除该键。
虽然过期时间特性对于清理缓存数据非常有用,不过如果读者翻一下本书的其他章节, 就会 发现除了6.2节、 7.1节和7.2节之外 , 本书使用过期时间特性的情况并不多 , 这主要和本书使用 的结构类型有关。在本书常用的命令当中, 只有少数几个命令可以原子地为键设置过期时间,并且对于列表 、 集合 、 散列和有序集合这样的容器(container)来说, 键过期命令只能为整个键设置过期时间,而没办法为键里面的单个元素设翌过期时间(为了解决这个问题,本书在好几个地方都使用了存储时间戳的有序集合来实现针对单个元素的过期操作)。