Redis进阶

事物

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)


客户端B执行

127.0.0.1:6379> set name Jerry
OK
127.0.0.1:6379> get name
"Jerry"

时间客户端A客户端B
t1watch name 
t2multi 
t3 set name Jerry
t4set name Sam 
t5exec 

在客户端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需要注意以下几点:

  1. 尽可能减少待排序键中元素的数量(n尽量小)
  2. 使用limit参数只获取需要的数据(使m尽量小)
  3. 如果要排序的数据量较大,尽可能使用参数store参数将结果缓存

任务队列

与任务队列进行交互的实体有两类,一类是“生产者”,一类是“消费者”。生产者会将需要执行的任务放入队列中,而消费者不断从任务队列中读入任务信息并执行

使用任务队列有如下好处:

  1. 松耦合。生产者和消费者无需知道彼此的实现细节,只需约定好任务的描述格式。这使得生产者和消费者可以由不同的团队使用不同的编程语言编写。
  2. 易于扩展消费者可以有多个,可以分布在不同的服务器中,借此可以轻易地降低单台服务器的负载
brpop命令接受两个参数,第一个是键名,第二个是超时时间,单位是秒。当超过此时间还没有获得新元素则会返回nil,超时时间为“0”代表不限制等待时间,如果没有新元素就永远阻塞下去


客户端A
127.0.0.1:6379> brpop queue 0

客户端B
127.0.0.1:6379> lpush queue task
(integer) 1

客户端A

127.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

客户端A

127.0.0.1:6379> blpop queue:1 queue:2 queue:3 0
1) "queue:3"
2) "mybatis"
(11.10s)

客户端B

127.0.0.1:6379> lpush queue:1 hibernate
(integer) 1
127.0.0.1:6379> lpush queue:2 java
(integer) 1

客户端A

127.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个值,第一个值是消息类型,根据消息类型的不同,第二、第三个值的含义也不同。消息类型可能的取值有:

  1. subscribe。表示定有成功的反馈信息。 第二个值表示订阅成功的频道名称,第三值是当前客户端订阅的频道数量
  2. message。这个类型的回复是我们最关心的,它表示接收到的消息。第二个值表示产生消息的频道名称,第三个值是消息的内容
  3. 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

客户端A

127.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类似,可以匹配多个频道,但是将取消订阅所匹配的频道








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值