Redis基础(二)

Redis中的事务(transaction)

是一组命令的集合。事务同命令一样都是Redis 的最小执行单位,一个事务中的命令要么都执行,要么都不执行。

1
2
3
4
5
6
7
8
9
redis> MULTI
OK
redis> SADD "user:1:following" 2
QUEUED
redis> SADD "user:2:followers" 1
QUEUED
redis> EXEC
1) (integer) 1
2) (integer) 1

Redis 的事务还能保证一个事务内的命令依次执行而不被其他命令插入。试想客户端A需要执行几条命令,同时客户端B发送了一条命令,如果不使用事务,则客户端B的命令可能会插入到客户端A的几条命令中执行。

Redis事务错误处理

  • 语法错误
1
2
3
4
5
6
7
8
9
10
redis> MULTI
OK
redis> SET key value
QUEUED
redis> SET key
(error) ERR wrong number of arguments for 'set' command
redis> ERRORCOMMAND key
(error) ERR unknown command 'ERRORCOMMAND'
redis> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

只要有一个命令有语法错误,执行 EXEC 命令后 Redis就会直接返回错误,连语法正确的命令也不会执行。

  • 运行错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis> MULTI
OK
redis> SET key 1
QUEUED
redis> SADD key 2
QUEUED
redis> SET key 3
QUEUED
redis> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) OK
redis> GET key
"3"

运行错误指在命令执行时出现的错误,比如使用散列类型的命令操作集合类型的键,这种错误在实际执行之前 Redis 是无法发现的,所以在事务里这样的命令是会被 Redis接受并执行的。如果事务里的一条命令出现了运行错误,事务里其他的命令依然会继续执行(包括出错命令之后的命令)

Redis的事务没有关系数据库事务提供的回滚(rollback)功能。

watch命令介绍

watch 命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行。监控一直持续到 EXEC 命令(事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控的键值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis> SET key 1
OK
redis> WATCH key
OK
redis> SET key 2
OK
redis> MULTI
OK
redis> SET key 3
QUEUED
redis> EXEC
(nil)
redis> GET key
"2"

过期时间

1
2
3
4
5
expire [key] [seconds]  //指定秒数后过期

ttl [key] //查看一个键的剩余存活时间

persist [key] //将键恢复成永久

除了PERSIST命令之外,使用SET或GETSET命令为键赋值也会同时清除键的过期时间

如果使用 WATCH命令监测了一个拥有过期时间的键,该键时间到期自动删除并不会被WATCH命令认为该键被改变。

  • expire会重新设置键的过期时间
1
2
3
4
5
6
7
8
9
10
redis> SET foo bar
OK
redis> EXPIRE foo 20
(integer) 1
redis> TTL foo
(integer) 15
redis> EXPIRE foo 20
(integer) 1
redis> TTL foo
(integer) 17

如果想要更精确的控制键的过期时间应该使用 pexpire命令,其时间单位是毫秒,对应地可以用PTTL命令以毫秒为单位返回键的剩余时间

实现缓存

1
2
3
4
5
6
7
$rank = GET cache:rank
if not $rank
$rank = 计算排名...
MUlTI
SET cache:rank, $rank
EXPIRE cache:rank, 7200
EXEC

排序

SORT命令可以对列表类型、集合类型和有序集合类型键进行排序,并且可以完成与关系数据库中的连接查询相类似的任务。

1
sort [key]

在对有序集合类型排序时会忽略元素的分数,只针对元素自身的值进行排序

  • 除了可以排列数字外,SORT命令还可以通过ALPHA参数实现按照字典顺序排列非数字元素
1
2
3
4
5
6
7
8
9
10
11
12
redis> LPUSH mylistalpha a c e d B C A
(integer) 7
redis> SORT mylistalpha
(error) ERR One or more scores can't be converted into double
redis> SORT mylistalpha ALPHA
1) "A"
2) "B"
3) "C"
4) "a"
5) "c"
6) "d"
7) "e”
  • 倒序排序
1
sort [key] desc
  • 返回指定范围的排序结果
1
sort [key] limit (start)num (end)num

by参数

如果提供了 BY 参数,SORT 命令将不再依据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个“*”并获取其值,然后依据该值对元素排序。

1
2
3
4
5
redis> SORT tag:ruby:posts BY post:*->time DESC
1) "12"
2) "26"
3) "6"
4) "2"

除了散列类型之外,参考键还可以是字符串类型

当参考键名不包含“*”时(即常量键名,与元素值无关),SORT命令将不会执行排序操作

1
2
3
4
redis> SORT sortbylist BY itemscore:* DESC
1) "2"
2) "1"
3) "3"

get参数

GET参数不影响排序,它的作用是使 SORT命令的返回结果不再是元素自身的值,而是GET参数中指定的键值。

1
2
3
4
5
redis> SORT tag:ruby:posts BY post:*->time DESC GET post:*->title
1) "Windows 8 app designs"
2) "RethinkDB - An open-source distributed database built with love"
3) "Uses for cURL"
4) "The Nature of Ruby"
  • 在一个SORT命令中可以有多个GET参数(而BY参数只能有一个)
1
2
3
4
5
6
7
8
9
redis> SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time
1) "Windows 8 app designs"
2) "1352620100"
3) "RethinkDB - An open-source distributed database built with love"
4) "1352620000"
5) "Uses for cURL"
6) "1352619600"
7) "The Nature of Ruby"
8) "1352619200"

GET #会返回元素本身的值

store参数

默认情况下SORT会直接返回排序结果,如果希望保存排序结果,可以使用store参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//把结果保存到sort.result键中

redis> SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time GET # STORE sort.result
(integer) 12
redis> LRANGE sort.result 0 -1
1) "Windows 8 app designs"
2) "1352620100"
3) "12"
4) "RethinkDB - An open-source distributed database built with love"
5) "1352620000"
6) "26"
7) "Uses for cURL"
8) "1352619600"
9) "6"
10) "The Nature of Ruby"
11) "1352619200"
12) "2"

性能优化

SORT命令的时间复杂度是O(n+mlog(m))

  • 尽可能减少待排序键中元素的数量(使N尽可能小)。
  • 使用LIMIT参数只获取需要的数据(使M尽可能小)。
  • 如果要排序的数据数量较大,尽可能使用STORE参数将结果缓存。

任务队列

为了测试实现对列,我们可以打开两个redis-cli实例,进行演示

  1. 在redis-cli[A]中进行如下操作
1
2
3
redis A> BRPOP queue 0

//键入回车后会处于阻塞状态
  1. 在redis-cli[B]中进行如操作
1
2
redis B> LPUSH queue task
(integer) 1
  1. 在LPUSH命令执行后redis-cli[A]马上就返回了结果
1
2
3
4
5
6
1) "queue"
2) "task"

//同时会发现redis-cli[B]中queue的元素已经被取走:
redis> LLEN queue
(integer) 0

优先级队列

例如打开两个 redis-cli 实例,在实例A中:

1
redis A> BLPOP queue:1 queue:2 queue:3 0

在实例B中:

1
2
redis B> LPUSH queue:2 task
(integer) 1

则实例A中会返回:

1
2
1) "queue:2"
2) "task"

如果多个键都有元素则按照从左到右的顺序取第一个键中的一个元素。我们先在queue:2和queue:3中各加入一个元素:

1
2
3
4
5
6
7
8
redis> LPUSH queue:2 task1
1) (integer) 1
redis> LPUSH queue:3 task2
2) (integer) 1
然后执行BRPOP命令:
redis> BRPOP queue:1 queue:2 queue:3 0
1) "queue:2"
2) "task1"

“发布/订阅”模式

发布/订阅”模式中包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或若干个频道(channel),而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会收到此消息。

  1. 发布者发布消息的命令是 PUBLISH
1
2
3
redis> PUBLISH channel.1 hi
(integer) 0
//向channel.1频道发布内容为“hi”的信息,返回值为0,表示没有订阅者收到消息(因为此时并没有用户订阅该频道)
  1. 订阅频道的命令是 SUBSCRIBE
    “现在新开一个redis-cli 实例 A,用它来订阅 channel.1:
1
2
3
4
5
redis A> SUBSCRIBE channel.1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1

执行 SUBSCRIBE 命令后客户端会进入订阅状态,处于此状态下客户端不能使用除SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE和PUNSUBSCRIBE这4个之外的命令,否则会报错。

  • subscribe。表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个值是当前客户端订阅的频道数量。
  • message。这个类型的回复是我们最关心的,它表示接收到的消息。第二个值表示产生消息的频道名称,第三个值是消息的内容。
  • unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态,之后就可以执行其他非“发布/订阅”模式的命令了。
  1. 这时我们打开另一个redis-cli实例B,并向channel.1发送一条消息:
1
2
3
redis B> PUBLISH channel.1 hi!
(integer) 1
返回值为1表示有一个客户端订阅了channel.1,

此时实例A收到了类型为message的回复:

1
2
3
1) "message"
2) "channel.1"
3) "hi!"