redis实战笔记(3)-第3章 Redis命令
第3章 Redis命令
本章主要内容
字符串命令、 列表命令和集合命令
散列命令和有序集合命令
发布命令与订阅命令
其他命令
在每个不同的数据类型的章节里, 展示的都是该数据类型所独有的、 最具代表性的命令。 首先让我们来看看, 除了GET和SET之外,
Redis的字符串还支持哪些命令。
3.1 字符串
在Redis里面, 字符串可以存储以下3种类型的值。
字节串( byte string) 。
整数。
浮点数。
除了自 增操作和自 减操作之外, Redis还拥有对字节串的其中一部分内容进行读取或者写入的操作(这些操作也可以用于整数或者浮点数,但这种用法并不常见) , 本书在第9章将展示如何使用这些操作来高效地将结构化数据打包( pack) 存储到字符串键里面。
很多键值数据库只能将数据存储为普通的字符串, 并且不提供任何字符串处理操作, 有一些键值数据库允许用户将字节追加到字符串的前面或者后面, 但是却没办法像Redis一样对字符串的子串进行读写。
从很多方面来讲, 即使Redis只支持字符串结构, 并且只支持本节列出的字符串处理命令, Redis也比很多别的数据库要强大得多; 通过使用子串操作和二进制位操作, 配合WATCH命令、 MULTI命令和EXEC命令(本书的3.7.2节将对这3个命令进行初步的介绍, 并在第4章对它们进行更深入的讲解) , 用户甚至可以自 己动手去构建任何他们想要的数据结构。第9章将介绍如何使用字符串去存储一种简单的映射, 这种映射可以在某些情况下节省大量内存。
3.2 列表
Redis的列表允许用户从序列的两端推入或者弹出元素, 获取列表元素, 以及执行各种常见的列表操作。 除此之外,列表还可以用来存储任务信息、 最近浏览过的文章或者常用联系人信息。
对于阻塞弹出命令和弹出并推入命令, 最常见的用例就是消息传递( messaging) 和任务队列( task queue) , 本书将在第6章对这两个主题进行介绍。
练习: 通过列表来降低内存占用
略
3.3 集合
Redis的集合以无序的方式来存储多个各不相同的元素, 用户可以快速地对集合执行添加元素操作、 移除元素操作以及检查一个元素是否存在于集合里
3.4 散列
第1章提到过, Redis的散列可以让用户将多个键值对存储到一个Redis键里面。 从功能上来说, Redis为散列值提供了一些与字符串值相同的特性, 使得散列非常适用于将一些相关的数据存储在一起。 我们可以把这种数据聚集看作是关系数据库中的行, 或者文档数据库中的文档。
第1章介绍的HGET命令和HSET命令分别是HMGET命令和HMSET命令的单参数版本
3.5 有序集合
和散列存储着键与值之间的映射类似, 有序集合也存储着成员与分值之间的映射, 并且提供了分值②处理命令, 以及根据分值大小有序地获取( fetch) 或扫描( scan) 成员和分值的命令
本书曾在第1章使用有序集合实现过基于发表时间排序的文章列表和基于投票数量排序的文章列表
在表3-10展示的命令里面, 有几个是之前没介绍过的新命令。 除了使用逆序来处理有序集合之外, ZREV*命令的工作方式和相对应的非逆序命令的工作方式完全一样
图3-1展示了对两个输入有序集合执行交集运算并得到输出有序集合的过程, 这次交集运算使用的是默认的聚合函数sum, 所以输出有序集合成员的分值都是通过加法计算得出的
图3-2展示了使用聚合函数min执行并集运算的过程, min函数在多个输入有序集合都包含同一个成员的情况下, 会将最小的那个分值设置为这个成员在输出有序集合的分值
图3-3展示了如何使用ZUNIONSTORE命令来将两个有序集合和一个集合组合成一个有序集合
3.6 发布与订阅
一般来说, 发布与订阅(又称pub/sub) 的特点是订阅者( listener) 负责订阅频道( channel) , 发送者( publisher) 负责向频道发送二进制字符串消息( binary string message) 。 每当有消息被发送至给定频道时, 频道的所有订阅者都会收到消息。 我们也可以把频道看作是电台, 其中订阅者可以同时收听多个电台, 而发送者则可以在任何电台发送消息。
虽然Redis的发布与订阅模式非常有用, 但本书只在这一节和8.5节中使用了这个模式, 这样做的原因有以下两个。
第一个原因和Redis系统的稳定性有关。
对于旧版Redis来说, 如果一个客户端订阅了某个或某些频道, 但它读取消息的速度却不够快的话,
那么不断积压的消息就会使得Redis输出缓冲区的体积变得越来越大,
这可能会导致Redis的速度变慢, 甚至直接崩溃。 也可能会导致Redis被
操作系统强制杀死, 甚至导致操作系统本身不可用。 新版的Redis不会
出现这种问题, 因为它会自 动断开不符合client-output-bufferlimit pubsub配置选项要求的订阅客户端(本书第8章将对这个选项做
更详细的介绍) 。
第二个原因和数据传输的可靠性有关。
任何网络系统在执行操作时都可能会遇上断线情况, 而断线产生的连接错误通常会使得网络连接两端中的其中一端进行重新连接。 但是, 如果客户端在执行订阅操作的过程中断线, 那么客户端将丢失在断线期间发送的所有消息, 因此依靠频道来接收消息的用户可能会对Redis提供的PUBLISH命令和SUBSCRIBE命令的语义感到失望。
基于以上两个原因 本书在第6章编写了两个不同的方法来实现可靠的消息传递操作, 这两个方法除了可以处理网络断线之外, 还可以防止Redis因为消息积压而耗费过多内存(这个方法即使对于旧版Redis也是有效的) 。
如果你喜欢简单易用的PUBLISH命令和SUBSCRIBE命令, 并且能够承担可能会丢失一小部分数据的风险, 那么你也可以继续使用Redis提供的发布与订阅特性, 而不是8.5节中提供的实现, 只要记得先把client-output-buffer-limit pubsub选项设置好就行了。
3.7 其他命令
1.首先要介绍的是SORT命令;
2.之后要介绍是用于实现基本事务特性的MULTI命令和EXEC命令, 这两个命令可以让用户将多个命令当作一个命令来执行;
3.最后要介绍的是几个不同的自 动过期命令, 它们可以自 动删除无用数据。
3.7.1 排序
SORT命令
可以根据字符串、 列表、 集合、 有序集合、 散列这5种键里面存储着的数据, 对列表、 集合以及有序集合进行排序。
使用SORT命令提供的选项可以实现以下功能:
1根据降序而不是默认的升序来排序元素;
2.将元素看作是数字来进行排序, 或者将元素看作是二进制字符串来进行排序(比如排序字符串' 110' 和' 12' 的结果就跟排序数字110和12的结果不一样) ;
3.使用被排序元素之外的其他值作为权重来进行排序, 甚至还可以从输入的列表、 集合、 有序集合以外的其他地方进行取值。
![](https://images2017.cnblogs.com/blog/464291/201708/464291-20170822233241418-1151604289.png)
SORT命令不仅可以对列表进行排序, 还可以对集合进行排序, 然后返回一个列表形式的排序结果。 代码清单3-12除了展示如何使用alpha关键字参数对元素进行字符串排序之外, 还展示了如何基于外部数据对元素进行排序, 以及如何获取并返回外部数据。 第7章将介绍如何组合使用集合操作和SORT命令: 当集合结构计算交集、 并集和差集的能力,与SORT命令获取散列存储的外部数据的能力相结合时, SORT命令将变得非常强大。
SORT是Redis中唯一一个可以同时处理3种不同类型的数据(列表、 集合以及有序集合?)的命令
3.7.2 基本的Redis事务
为了对相同或者不同类型的多个键执行操作, Redis有5个命令可以让用户在不被打断( interruption) 的情况下对多个键执行操作, 它们分别是WATCH、 MULTI、 EXEC、 UNWATCH和DISCARD。
当Redis从一个客户端那里接收到MULTI命令时, Redis会将这个客户端之后发送的所有命令都放入到一个队列里面, 直到这个客户端发送EXEC命令为止, 然后Redis就会在不被打断的情况下, 一个接一个地执行存储在队列里面的命令
因为没有使用事务, 所以3个线程都可以在执行自 减操作之前, 对notrans: 计数器执行自 增操作。 虽然代码清单里面通过休眠100毫秒的方式来放大了潜在的问题, 但如果我们确实需要在不受其他命令干扰的情况下, 对计数器执行自 增操作和自 减操作, 那么我们就不得不解决这个潜在的问题
尽管自 增操作和自 减操作之间有一段延迟时间, 但通过使用事务, 各个线程都可以在不被其他线程打断的情况下, 执行各自 队
列里面的命令。 记住, Redis要在接收到EXEC命令之后, 才会执行那些位于MULTI和EXEC之间的入队命令。
使用事务既有利也有弊, 本书的4.4节将对这个问题进行讨论。
练习: 移除竞争条件
练习: 提高性能
额:watch
WATCH 命令用于在事务开始之前监视任意数量的键: 当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败。
以下示例展示了一个执行失败的事务例子:
redis> WATCH name OK redis> MULTI OK redis> SET name peter QUEUED redis> EXEC (nil)
以下执行序列展示了上面的例子是如何失败的:
时间 | 客户端 A | 客户端 B |
---|---|---|
T1 | WATCH name |
|
T2 | MULTI |
|
T3 | SET name peter |
|
T4 | SET name john |
|
T5 | EXEC |
在时间 T4 ,客户端 B 修改了 name
键的值, 当客户端 A 在 T5 执行 EXEC 时,Redis 会发现 name
这个被监视的键已经被修改, 因此客户端 A 的事务不会被执行,而是直接返回失败。
下文就来介绍 WATCH 的实现机制,并且看看事务系统是如何检查某个被监视的键是否被修改,从而保证事务的安全性的。
3.7.3 键的过期时间
虽然过期时间特性对于清理缓存数据非常有用, 不过如果读者翻一下本书的其他章节, 就会发现除了 6.2节、 7.1节和7.2节之外, 本书使用过期时间特性的情况并不多, 这主要和本书使用的结构类型有关。 在本书常用的命令当中, 只有少数几个命令可以原子地为键设置过期时间,并且对于列表、 集合、 散列和有序集合这样的容器( container) 来说,键过期命令只能为整个键设置过期时间, 而没办法为键里面的单个元素设置过期时间(为了解决这个问题, 本书在好几个地方都使用了存储时间戳的有序集合来实现针对单个元素的过期操作)
练习: 使用 EXPIRE命令代替时间戳有序集合
3.8 小结
额:其他重要命令
1.SETEX
SETEX key seconds value 将值 value 关联到 key ,并将 key 的生存时间设为 seconds (以秒为单位)。 如果 key 已经存在, SETEX 命令将覆写旧值。 这个命令类似于以下两个命令: SET key value EXPIRE key seconds # 设置生存时间 不同之处是, SETEX 是一个原子性(atomic)操作,关联值和设置生存时间两个动作会在同一时间内完成,该命令在 Redis 用作缓存时,非常实用。 可用版本:>= 2.0.0 返回值: 设置成功时返回 OK 。 当 seconds 参数不合法时,返回一个错误。 # 在 key 不存在时进行 SETEX redis> SETEX cache_user_id 60 10086 OK redis> GET cache_user_id # 值 "10086" redis> TTL cache_user_id # 剩余生存时间 (integer) 49 # key 已经存在时,SETEX 覆盖旧值 redis> SET cd "timeless" OK redis> SETEX cd 3000 "goodbye my love" OK redis> GET cd "goodbye my love" redis> TTL cd (integer) 2997
2.SETNX
SETNX key value 将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。 可用版本:>= 1.0.0 返回值: 设置成功,返回 1 。 设置失败,返回 0 。 redis> EXISTS job # job 不存在 (integer) 0 redis> SETNX job "programmer" # job 设置成功 (integer) 1 redis> SETNX job "code-farmer" # 尝试覆盖 job ,失败 (integer) 0 redis> GET job # 没有被覆盖 "programmer"
3.用于key的
del,keys
4.列表 LREM
LREM key count value 根据参数 count 的值,移除列表中与参数 value 相等的元素。 count 的值可以是以下几种: count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。 count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。 count = 0 : 移除表中所有与 value 相等的值。 可用版本: >= 1.0.0 时间复杂度: O(N), N 为列表的长度。 返回值: 被移除元素的数量。 因为不存在的 key 被视作空表(empty list),所以当 key 不存在时, LREM 命令总是返回 0 。 # 先创建一个表,内容排列是 # morning hello morning helllo morning redis> LPUSH greet "morning" (integer) 1 redis> LPUSH greet "hello" (integer) 2 redis> LPUSH greet "morning" (integer) 3 redis> LPUSH greet "hello" (integer) 4 redis> LPUSH greet "morning" (integer) 5 redis> LRANGE greet 0 4 # 查看所有元素 1) "morning" 2) "hello" 3) "morning" 4) "hello" 5) "morning" redis> LREM greet 2 morning # 移除从表头到表尾,最先发现的两个 morning (integer) 2 # 两个元素被移除 redis> LLEN greet # 还剩 3 个元素 (integer) 3 redis> LRANGE greet 0 2 1) "hello" 2) "hello" 3) "morning" redis> LREM greet -1 morning # 移除从表尾到表头,第一个 morning (integer) 1 redis> LLEN greet # 剩下两个元素 (integer) 2 redis> LRANGE greet 0 1 1) "hello" 2) "hello" redis> LREM greet 0 hello # 移除表中所有 hello (integer) 2 # 两个 hello 被移除 redis> LLEN greet (integer) 0