事物
Redis中的事物是一组命令的集合。事物同命令一样都是Redis的执行最小单元,一个事物的命令要么都执行,要么都不执行。
事物的原理是先将属于一个事物的命令发送给Redis,然后Redis依次执行这些命令
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd java "spring" "hibernate" "mybatis"
QUEUED
127.0.0.1:6379> sadd php "ThinkPHP" "Yii"
QUEUED
127.0.0.1:6379> exec
1) (integer) 3
2) (integer) 2
127.0.0.1:6379> smembers java
1) "mybatis"
2) "spring"
3) "hibernate"
127.0.0.1:6379> smembers php
1) "Yii"
2) "ThinkPHP"
Redis保证一个事物中的所有命令要么都执行,要么都不执行。如果发送exec命令前客户线程断了,则Redis会清空事物队列
开启事物后,如果发生语法错误,提交exec后,Redis就会直接返回错误,连正确的命令也不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key 1
QUEUED
127.0.0.1:6379> set key
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379> sadd key 2
QUEUED
127.0.0.1:6379> set key 3
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get key
(nil)
如果错误在开启事物时并没有发现,这样的命令是会被Redis执行的
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key 1
QUEUED
127.0.0.1:6379> sadd key 2
QUEUED
127.0.0.1:6379> set key 3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
127.0.0.1:6379> get key
"3"
Redis不支持关系数据库的回滚(rollback)功能,因此在事物上保持简洁和快速
watch命令在事务开始之前监视任意数量的键, 当执行exec提交事物时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败
客户端执行A
127.0.0.1:6379> get name
"Tom"
127.0.0.1:6379> watch name
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name Sam
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> set name Jerry
OK
127.0.0.1:6379> get name
"Jerry"
时间 | 客户端A | 客户端B |
t1 | watch name | |
t2 | multi | |
t3 | set name Jerry | |
t4 | set name Sam | |
t5 | exec |
在客户端A调用exec提交事物之前,客户端已经对name的值做了修改,所以客户端A最后执行事物失败
生存时间
expire key seconds可以设置一个键的生命周期,单位是秒,使用ttl可以返回键的剩余生存时间,当返回-2代表键的生存时间已过或键从未存在
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> expire foo 100
(integer) 1
127.0.0.1:6379> ttl foo
(integer) 95
127.0.0.1:6379> ttl foo
(integer) 62
127.0.0.1:6379> ttl foo
(integer) -2
127.0.0.1:6379> get foo
(nil)
persist可以使有生命周期的键变为永久,除了persist,set和getset同样可以清除键的生命周期
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> expire foo 300
(integer) 1
127.0.0.1:6379> ttl foo
(integer) 288
127.0.0.1:6379> ttl foo
(integer) 265
127.0.0.1:6379> persist foo
(integer) 1
127.0.0.1:6379> ttl foo
(integer) -1
127.0.0.1:6379> get foo
"bar"
expireat命令使用Unix时间作为第二个参数表示键的生存时间(秒)
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> expireat foo 1478989320
(integer) 1
127.0.0.1:6379> ttl foo
(integer) 35
pexpireat与expireat类似,但第二个参数表示键的生存时间(毫秒)
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> pexpireat foo 1478989620000
(integer) 1
127.0.0.1:6379> ttl foo
(integer) 36
排序
sort命令可以对列表类型、集合类型和有序集合类型进行排序
列表排序
127.0.0.1:6379> lpush mylist 5 8 1 2 9 4 3 6 7
(integer) 9
127.0.0.1:6379> sort mylist
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"
集合排序,然而使用sort对有序集合进行排序会忽略元素的分数,只对元素自身的值进行排序
127.0.0.1:6379> zadd myset 12 11 10 13 15 14 16 19
(integer) 4
127.0.0.1:6379> zrange myset 0 -1
1) "13"
2) "11"
3) "14"
4) "19"
127.0.0.1:6379> sort myset
1) "11"
2) "13"
3) "14"
4) "19"
sort命令加上alpha还可以对字母进行排序,但仅限对字母,列表不能有数字
127.0.0.1:6379> lpush mylistalpha a c e d B C A
(integer) 7
127.0.0.1:6379> sort mylistalpha alpha
1) "a"
2) "A"
3) "B"
4) "c"
5) "C"
6) "d"
7) "e"
sort命令加上desc可以降序排列,limit可以截取第一个和第二个
127.0.0.1:6379> sort mylist desc
1) "9"
2) "8"
3) "7"
4) "6"
5) "5"
6) "4"
7) "3"
8) "2"
9) "1"
127.0.0.1:6379> sort mylist desc limit 1 2
1) "8"
2) "7"
by参数
很多情况下列表、集合或有序集合存储的元素代表的是对象的id,单纯对这些id做排序有时意义并不大。更多时候是希望根据id对应的对象的某个属性进行排序。为了满足这一要求,可以在sort命令使用by参数
by参数的语法“by参考键”。其中参考键可以是字符串类型键或者是散列类型键的某个字段(键名->字段名)。如果提供了by参数,sort命令不再根据自身的值进行排序,而是对每个元素会用元素的值替换参考键中的第一个“*”,并获取其值,然后依据该值对元素进行排序
127.0.0.1:6379> lpush sortbylist 1 2 3 (integer) 3 127.0.0.1:6379> set itemscore:1 50 OK 127.0.0.1:6379> set itemscore:2 100 OK 127.0.0.1:6379> set itemscore:3 -10 OK 127.0.0.1:6379> sort sortbylist by itemscore:* desc 1) "2" 2) "1" 3) "3" 127.0.0.1:6379> lpush sortbylist 4 5 (integer) 5 127.0.0.1:6379> set itemscore:4 50 OK 127.0.0.1:6379> set itemscore:5 20 OK 127.0.0.1:6379> sort sortbylist by itemscore:* desc 1) "2" 2) "4" 3) "1" 4) "5" 5) "3"
127.0.0.1:6379> hset itemhash:1 num 20 (integer) 1 127.0.0.1:6379> hset itemhash:2 num -10 (integer) 1 127.0.0.1:6379> hset itemhash:3 num 9 (integer) 1 127.0.0.1:6379> hset itemhash:4 num 15 (integer) 1 127.0.0.1:6379> hset itemhash:5 num 0
127.0.0.1:6379> sort sortbylist by itemhash:*->num desc 1) "4" 2) "1" 3) "3" 4) "5" 5) "2"
get参数
get参数不影响排序,它的作用是使得sort命令返回的不再是元素自身的值,而是get参数中指定的键值
127.0.0.1:6379> hset itemhash:1 name John (integer) 1 127.0.0.1:6379> hset itemhash:2 name Tom (integer) 1 127.0.0.1:6379> hset itemhash:3 name Amy (integer) 1 127.0.0.1:6379> hset itemhash:4 name Sam (integer) 1 127.0.0.1:6379> hset itemhash:5 name Rose (integer) 1 127.0.0.1:6379> sort sortbylist by itemhash:*->num desc get itemhash:*->name 1) "John" 2) "Sam" 3) "Amy" 4) "Rose" 5) "Tom"
可以使用多个get获取不同的字段,如果要返回本身的id,可以使用get #
127.0.0.1:6379> lpush sortbylist 6 (integer) 6 127.0.0.1:6379> sort sortbylist by itemhash:*->num desc get itemhash:*->name get itemhash:*->age get # 1) "John" 2) "15" 3) "1" 4) "Sam" 5) "16" 6) "4" 7) "Amy" 8) "19" 9) "3" 10) (nil) 11) (nil) 12) "6" 13) "Rose" 14) "20" 15) "5" 16) "Tom" 17) "25" 18) "2"
sort会直接返回结果,可以使用store存储结果,其结果会存储在列表中
127.0.0.1:6379> sort sortbylist by itemhash:*->num desc get itemhash:*->name get itemhash:*->age get # store sort.result (integer) 18 127.0.0.1:6379> type sort.result list 127.0.0.1:6379> lrange sort.result 0 -1 1) "John" 2) "15" 3) "1" 4) "Sam" 5) "16" 6) "4" 7) "Amy" 8) "19" 9) "3" 10) "" 11) "" 12) "6" 13) "Rose" 14) "20" 15) "5" 16) "Tom" 17) "25" 18) "2"
sort命令是Redis最强大最复杂的命令之一,如果使用不好很容易称为性能瓶颈。sort命令的时间复杂度为O(n+mlogm),其中n表示要排序的列表、集合或者有序集合中的元素个数,m表示要返回的元素个数。n较大的时候sort命令的性能较低,并且sort命令会建立一个长度为n的容器来存储待排序的元素,使用sort需要注意以下几点:
- 尽可能减少待排序键中元素的数量(n尽量小)
- 使用limit参数只获取需要的数据(使m尽量小)
- 如果要排序的数据量较大,尽可能使用参数store参数将结果缓存
任务队列
与任务队列进行交互的实体有两类,一类是“生产者”,一类是“消费者”。生产者会将需要执行的任务放入队列中,而消费者不断从任务队列中读入任务信息并执行
使用任务队列有如下好处:
- 松耦合。生产者和消费者无需知道彼此的实现细节,只需约定好任务的描述格式。这使得生产者和消费者可以由不同的团队使用不同的编程语言编写。
- 易于扩展消费者可以有多个,可以分布在不同的服务器中,借此可以轻易地降低单台服务器的负载
brpop命令接受两个参数,第一个是键名,第二个是超时时间,单位是秒。当超过此时间还没有获得新元素则会返回nil,超时时间为“0”代表不限制等待时间,如果没有新元素就永远阻塞下去
客户端A127.0.0.1:6379> brpop queue 0
客户端B127.0.0.1:6379> lpush queue task (integer) 1
客户端A127.0.0.1:6379> brpop queue 0 1) "queue" 2) "task" (11.85s)
127.0.0.1:6379> lrange queue 0 -1 (empty list or set)
除了brpop之外,Redis还提供了blpop,两者的区别是:brpop是从队列的右边取元素,而blpop与之相反。
客户端A
127.0.0.1:6379> blpop queue:1 queue:2 queue:3 0
客户端B
127.0.0.1:6379> lpush queue:3 mybatis (integer) 1
客户端A127.0.0.1:6379> blpop queue:1 queue:2 queue:3 0 1) "queue:3" 2) "mybatis" (11.10s)
客户端B127.0.0.1:6379> lpush queue:1 hibernate (integer) 1 127.0.0.1:6379> lpush queue:2 java (integer) 1
客户端A127.0.0.1:6379> blpop queue:1 queue:2 queue:3 0 1) "queue:1" 2) "hibernate" 127.0.0.1:6379> blpop queue:1 queue:2 queue:3 0 1) "queue:2" 2) "java"
发布/订阅模式除了实现任务队列外,Redis还提供了一组命令让开发者实现“发布/订阅”模式。“发布/订阅”模式同样可以实现进程间的消息传递,其原理是这样的:
“发布/订阅”模式包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或若干个频道,发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会收到此消息
客户端A订阅channe1.1频道
127.0.0.1:6379> subscribe channe1.1 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channe1.1" 3) (integer) 1
发布者发布消息的命令是publish,用法是publish channel message
客户端B往频道发送hi,因为有一个客户端订阅了channe1.1,所以返回值为1
127.0.0.1:6379> publish channe1.1 hi (integer) 1
客户端A查看到客户端B发送的消息
127.0.0.1:6379> subscribe channe1.1 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channe1.1" 3) (integer) 1 1) "message" 2) "channe1.1" 3) "hi"
执行subscribe命令后客户端会进入订阅状态,处于此状态下的客户端不能使用除subscribe/unsubscribe/psubscribe/punsubscribe 这4个属于“发布/订阅”模式的命令之外的命令,否则会报错进入订阅状态后客户端可能收到三种类型的回复。每种类型的回复都包含3个值,第一个值是消息类型,根据消息类型的不同,第二、第三个值的含义也不同。消息类型可能的取值有:
- subscribe。表示定有成功的反馈信息。 第二个值表示订阅成功的频道名称,第三值是当前客户端订阅的频道数量
- message。这个类型的回复是我们最关心的,它表示接收到的消息。第二个值表示产生消息的频道名称,第三个值是消息的内容
- unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态
使用psubscribe命令订阅指定的规则
客户端A
127.0.0.1:6379> psubscribe channel.? Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "channel.?" 3) (integer) 1
客户端B
127.0.0.1:6379> publish channel.1 java (integer) 1 127.0.0.1:6379> publish channel.2 python (integer) 1
客户端A127.0.0.1:6379> psubscribe channel.? Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "channel.?" 3) (integer) 1 1) "pmessage" 2) "channel.?" 3) "channel.1" 4) "java" 1) "pmessage" 2) "channel.?" 3) "channel.2" 4) "python"
punsubscribe与psubscribe类似,可以匹配多个频道,但是将取消订阅所匹配的频道