Redis基础之消息通知

来源:互联网 发布:晋城网络电视台导视5 编辑:程序博客网 时间:2024/05/29 12:48

无论是软件还是网站,业务逻辑往往是复杂的,有些模块往往需要经过复杂的运算查询等耗时较长的操作,为了避免与之关联的模块等待太久,应该使用独立的线程来完成这类操作。不过一些编程语言或者框架不易实现多线程,这时很容易想到通过其他进程来实现。进程间实现了异步,而进程间的通信可以实现消息通知的方式。

通知的过程可以借助任务队列来实现。任务队列顾名思义就是“传递任务的队列”。与任务队列进行交互的实体有两类,一类是生产者(producer),一类是消费者(consumer)。生产者会将需要处理的任务放入任务队列中,而消费者则不断地从任务队列中读入任务信息并执行。使用任务队列有如下好处:

  • 松耦合。生产者和消费者无需知道彼此的实现细节,只需要约定好任务的描述格式。这使得生产者和消费者可以由不同的编程语言实现。
  • 易于扩展消费者可以有多个,而且可以分布在不同的服务器中。借此可以轻易地降低单台服务器的负载。

1. 使用Redis实现任务队列

说道任务队列自然就能想到Redis的列表类型,使用LPUSH和RPOP命令实现队列的概念。如果要实现任务队列,只需要让生产者将任务用LPUSH命令加入到某个键中,另一边让消费者不断地使用RPOP命令从该键中取出任务即可。其伪代码为:
loop    $task = RPOP queue    if $task        execut($task)    else        wait 2 second
到此便实现了一个简单的Redis任务队列。不过有一点不好的地方:当任务队列为空时,消费者还要每2秒钟调用RPOP来查看是否有新的任务。如果可以实现一旦有新的任务加入到队列就通知消费者就好了。其实,借助BRPOP命令可以实现这样的需求。
BRPOP可RPOP命令相似,唯一的区别是当列表中没有元素时BRPOP命令会一直阻塞住连接,直到有新的元素加入。那么,新的伪代码为:
loop    $task = BRPOP queue 0    execut($task[1]) 
BRPOP接收两个参数,第一个是键名,第二个是超时时间,单位为秒。当超过了此时间仍然没有获得新元素的话,则返回nil。若时间设置为0,则表示无限等待。当获得一个元素后BRPOP命令返回两个值,分别是键名和元素值。
为了测试BRPOP命令,我么打开两个终端,并分别运行redis-cli实例,在实例A中执行:
127.0.0.1:6379> BRPOP queue 0
键入回车后,实例A会处于阻塞状态,这时,在实例B中向queue中加入一个元素:
127.0.0.1:6379> LPUSH queue task(integer) 1
在LPUSH命令执行完后,实例A会立马返回结果:
127.0.0.1:6379> BRPOP queue 01) "queue"2) "task"(42.65s)
同时,检查queue会发现,元素已经被取走了:
127.0.0.1:6379> LLEN queue(integer) 0
除了BRPOP命令之外,Redis还提供了BLPOP,和BRPOP的区别在于从队列中取元素时,BLPOP会从左边取。

2. 优先队列

当任务队列中的任务数量非常多时,消费者可能已是处理不过来,而有的任务需要紧急处理,有的任务又不那么紧急,这时便需要优先处理紧急任务。为了实现这一目的,我们需要实现一个优先级队列。BRPOP命令可以同时接受多个建,其完整的命令格式为:
BLPOP key [key ...] timeout
如:BLPOP queue:1 queue:2 0,表示同时检测多个键,如果所有的键都没有元素则阻塞,如果其中有一个键有元素则会从该键中弹出元素。例如:打开两个redis-cli实例,在实例A中执行:
127.0.0.1:6379> BLPOP queue:1 queue:2 queue:3 0
在实例B中执行:
LPUSH queue:2 task
则在实例A中会返回:
127.0.0.1:6379> BLPOP queue:1 queue:2 queue:3 01) "queue:2"2) "task"
如果多个键都有元素,则按照从左到右的顺序取第一个键中的一个元素。现在queue:2和queue:3中各加入一个元素,然后执行BRPOP命令:
127.0.0.1:6379> LPUSH queue:2 task1(integer) 1127.0.0.1:6379> LPUSH queue:3 task2(integer) 1127.0.0.1:6379> BLPOP queue:1 queue:2 queue:3 01) "queue:2"2) "task1"
借此特征可以实现分区优先级的任务。将不同优先级的放入不同的队列中,查询时,将队列从左到右按照优先级从高到低排列即可。


3. "发布/订阅"模式

除了实现任务队列,Redis还提供了一组命令可以让开发者实现“发布/订阅(publish/subscribe)”模式。
“发布/订阅”模式中包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或若干这频道(channel),而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会收到消息。发布者发布消息的命令式PUSHLISH,用法是:

PUBLISH channel message

如向channel.1说一声"hi":
127.0.0.1:6379> PUBLISH channel.1 hi(integer) 0
这样消息就发出去了。PUBLISH命令的返回值表示接收到这条消息的订阅者数量。因为此时没有客户端订阅channel.1,所以返回0.发送出去的消息不会被持久化,也就是说,当有客户端订阅channel.1后只能收到后续发布到该频道的消息,之前发送的就收不到了。
订阅频道的命令式SUBSCRIBE,可以同时订阅多个频道,用法是:

SUBSCRIBE channel [channel ...]

打开一个新的redis-cli实例,用它来订阅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时,客户端会退出订阅模式,之后就可以执行其他非“发布/订阅”模式的命令了。
当上例中的实例A进入订阅模式后,收到了subscribe类型的回复,这时打开另一个redis-cli实例B,并向channel.1发送一条消息:
127.0.0.1:6379> PUBLISH channel.1 hello(integer) 1
返回值1表示有一个客户端订阅了channel.1,此时实例A收到了类型为message的回复:
1) "message"2) "channel.1"3) "hello"
使用UNSUBSCRIBE命令可以取消订阅指定的频道,用法是:

UNSUBSCRIBE [channel [channel ...] ]

如果不指定频道,则会取消所有订阅的频道。(由于redis-cli的限制,无法在其中测试UNSUBSCRIBE命令)

4. 按照规则订阅

除了使用SUBSCRIBE命令订阅指定名称的频道外,还可以使用PSUBSCRIBE命令订阅指定的规则。规则支持glob风格的通配符格式,新打开一个redis-cli实例C,并执行PSUBSCRIBE命令:
127.0.0.1:6379> PSUBSCRIBE channel.?*Reading messages... (press Ctrl-C to quit)1) "psubscribe"2) "channel.?*"3) (integer) 1
规则channel.?*可以匹配channel.1和channel.10,但不会匹配channel。这时,在实例A中发送消息:
127.0.0.1:6379> PUBLISH channel.1 hello!(integer) 2
结果返回2,因为有两个客户端都订阅了channel.1频道。实例C接收到的回复是:
1) "pmessage"2) "channel.?*"3) "channel.1"4) "hello!"
第一个值表示这条消息通过PSUBSCRIBE命令订阅频道收到的,第二个值表示使用的通配符,第三个值表示实际收到消息频道,第四个值表示收到的消息内容。
提示: 使用PSUBSCRIBE可以重复订阅一个频道,如某客户端执行了PSUBSCRIBE channel.? channel.?*.这时向channel.2发布消息后客户端会收到两个消息,而同时PUBLISH命令返回的也是2而不是1.同样,如果某一客户端执行了SUBSCRIBE channel.10和PSUBSCRIBE channel.?*的话,向channel.10发送命令的客户端也会收到两条消息(但是是两种类型,message和pmessage),同时PUBLISH命令会返回2.
PUNSUBSCRIBE命令可以退订指定的规则,用法是:PUNSUBSCRIBE [pattern [pattern ...]],如果没有参数则退订所有规则。
注意: 使用PUNSUBSCRIBE命令只能退订通过PSUBSCRIBE命令订阅的规则,不会影响直接通过SUBSCRIBE命令订阅的频道;同样,UNSUBSCRIBE命令也不会影响通过PSUBSCRIBE命令订阅的频道。另外容易出错的一点事PUNSUBSCRIBE命令退订某个规则时不会讲其中的通配符展开,而是进行严格的字符串匹配,所以PUNSUBSCRIBE*无法退订channel.*规则,而是必须使用PUNSUBSCRIBE channel.*才能退订。

















0 0
原创粉丝点击