Redis(2)

事务

redis中的事务(transaction)和传统关系型数据库中的事务一样,是一组命令的集合。事务同命令一样都是redis最小的执行单位,一个事务中的命令要么都执行,要么都不执行。

1
2
3
4
5
6
MULTI

SADD "user:1:following" 2
SADO "user:2:following" 1

EXEC

在执行到EXEC之前,前面的命令并不会马上执行,而是添加到等待执行的事务队列中。如果在EXEC之前事务中断,则事务的所有命令都不会被执行,一旦发送了EXEC命令,所有命令都一定会被执行。

另外redis事务还保证一个事务内的命令依次执行而不被其他命令插入,也就是在多个客户端的情况下,一旦某个客户端开始执行某个事务,到结束之前,其他客户端的命令都不会被执行。

错误处理

  • 语法错误
    事务所有命令都不会被执行。

  • 运行错误。比如使用散列类型的命令操作集合类型的键。这种错误在事务中会被接受并继续执行,也就是即使某条命令出现允许错误,其后面的命令还是会被执行,整个事务会被完成。

需要注意的是,redis不支持rollback。

WATCH命令

很多情况下我们需要保证在我们获得某个键值后,该键值不能被其他客户端修改,直到当前客户端执行事务完成,这时我们可以使用WATCH。WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会被执行(与事务的最后一条性质相违背?没有!!)。监控一直持续到EXEC命令(监控可以在MULTI之前进行,从那时就开始监控,事务中的命令是在EXEC之后才执行的,所以在MULTI命令后可以修改WATCH监控,也就是WATCH作用是,从WATCH开始,到MULTI中间,如果被watch的键被修改,那么这个事务不会被执行)。

1
2
3
4
5
6
7
8
SET key 1
WATCH key
SET key 2
MULTI
SET key 3
EXEC

GET key # 结果是2

执行EXEC命令后会取消对所有键的监控,也可以 用UNWATCH命令来取消监控。

生存时间

在redis可以使用EXPIRE命令设置一个键的生存时间,到时间后redis会自动删除。

1
2
3
4
5
6
EXPIRE key seconds # 返回1表示设置成功,0表示失败
EXPIRE session:29e3d 900

TTL key # 还有多少时间会被删除(秒),-1表示键不存在或没有设置时间

PERSIST key# 取消生存时间设置(恢复为永久保存)

除了PRESIST外,使用SET或GETSET命令赋值也同时回清除键的生存时间(恢复为永久)。其他命令只对键值进行操作的命令(INCR,LPUSH,HSET,ZREM)都不会影响键的生存时间。

1
PEXPIRE key 1000 # 毫秒位单位

实现缓存

为了提高性能和负载能力,我们通常需要将一些访问频率较高或者计算密集的数据结果缓存起来,并希望这些数据能根据具体访问情况自动更新或过期(性能和有效的内存利用)。由于内存有限,我们不可能无限的将所有数据缓存在内存中,需要按照一定的规则淘汰不需要的缓存键,通过配置文件的maxmemory参数我们可以设置redis最大可用内存大小,超出这个限制时redis回根据maxmemory-policy参数指定的策略删除一些键以空出空间。

rule explaination
volatile-lru 使用LRU算法删除一个键(只对设置了生存时间的键)
allkeys-lru 使用LRU算法删除一个键
volatile-random 随见删除一个键(只对设置了生存时间的键)
allkeys-lru 随机删除一个键
valatile-ttl 删除生存时间最近的一个键
noeviction 不删除键,返回错误

排序

  • 有序集合
  • SORT命令

SORT可以对集合,列表和有序集合类型进行排序(在对有序集合排序时会忽略元素的分数,只针对元素自身的值进行排序,如数字是根据数字大小,还可以通过ALPHA参数实现按照字典顺序排列非数字元素),使用DESC可以实现将元素从大到小排序。

1
2
3
4
SORT tag:ruby:posts
SORT myzset

SORT mylistalpha ALPHA DESC

另外SORT还支持LIMIT offset count的用法
SORT tag:ruby:posts DESC LIMIT 1 2

  • BY参数

很多时候列表,集合等中存储的元素值代表的是对象ID,单纯对这些ID排序意义不大,我们需要根据ID对于的对象的某个属性进行排序,这时我们可以使用BY参数。BY 参考键,其中参考键可以是字符串类型键或者是散列类型的某个字段
(表示为键名->字段名)。如果提供了BY参数,SORT命令不在根据元素自身的值进行排序,而是对每个元素使用元素的值替换参考键中的第一个*并获取其值,用以进行排序。

1
SORT tag:ruby:posts BY post:*->time DESC

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

1
SORT list my itemscore:* DESC

当某个元素的参考键不存在时,会默认参考键值为0.

  • GET参数

GET参数不影响排序,它的作用是使SORT命令的返回结果不再是元素自身的值,而是GET参数中指定的键值。GET参数的规则和BY参数一样,也支持字符串类型和散列类型的键,并同样使用*作为占位符。另外在一个SORT 命令中可以有多个GET参数(而BY参数只能有一个)。

SORT tag:ruby:posts BY post:*->time DESC GET post:*title GET post:*->time GET #

有N个GET参数,每个元素返回的结果就有N行。使用#作为参数可以返回键值本身。

  • STORE参数

可以将排序结果使用STORE保存,返回结果为保存结果个数。

SORT tag:ruby:posts BY post:*->time DESC GET post:*title GET post:*->time GET # STORE stor.result

  • 性能优化

消息通知

任务队列

两类实体:生产者和消费者,生产者会将需要处理的任务放入任务队列中,而消费者不断地从任务队列中读入任务信息并执行。使用任务队列可以实现松耦合,使得生产者和消费者都可伸缩,并且在访问量急剧增加的情况下可以根据系统资源情况削峰,降低负载。

使用redis实现任务队列

最基本使用redis的列表类型,生产者使用LPUSH加入任务,消费者使用RPOP取出任务并执行(while不断循环,可设置间隔时间)。

上面的一个确定是消费者需要不断调用RPOP查看是否存在新任务,带了额外的负载压力。我们可以通过BRPOP实现一旦有新任务就通知消费者,消费者在调用BRPOP之后,如果没有任务则会阻塞,知道有新任务加入。

1
BRPOP queue 0 # 0表示一直阻塞,其他表示只会阻塞一定时间

优先级队列

有时一个消费者可能能处理相同类型的不同任务,但优先级不同。如VIP客户的订单处理和普通客户的订单处理,可以将不同类型的客户的订单存在不同队列中。BRPOP命令可以同时接收多个键,并处理第一个存在任务的队列的任务。

『发布/订阅』模式

除了任务队列以外,redis还提供了一组命令可以让开发者实现”发布/订阅”模式。”发布/订阅”模式同样可以实现进程间的消息传递:订阅者可以订阅一个或若干个频道(channel),而发布者可以向指定频道发送消息,所有订阅此频道都会收到该消息。

发布者发布消息:

1
PUBLIC channel.1 hi

订阅频道的命令是SUBSCRIBE,可以同时订阅多个频道。

1
SUBSCRIBE channel.1

进入订阅状态后客户端可能会收到三种类型的回复。每种类型的回复都包含三个值。第一个值是消息的类型,根据消息类型的不同,第二、第三个值的含义也不同。

三种类型:

  • 第一个值为”subsribe”。表示订阅成功的反馈信息,第二个值是订阅成功的频道名称,第三个值是当前客户端订阅的频道数量。
  • 第一个值为”message”。一般这种类型是我们关心的,表示接受到的消息。第二个值表示频道名称,第三个值表示消息的内容。
  • unsubscribe,表示成功取消订阅频道。

使用UNSUBSCRIBE命令可以取消订阅指定的频道,如果不指定参数则会取消所有订阅频道。

1
UNSUBSCRIBE channel1 channel2 ...

按照规则订阅

PSUBSCRIBE支持通配符匹配:

1
2
3
4
5
6
PSUBSCRIBE channel.?*

1) pmessage
2) channnel.?*
3) channel.1
4) "hi!"

PUNSUBSCRIBE可以退订指定的规则。

管道