在一个私人博客网站中,粉丝关注博主需要添加自己的邮箱,以便接收邮件确认信息和通知信息;博主每发布一篇新文章时,需要向他的粉丝们同步发送邮件通知;
诸如,当页面需要进行如发送邮件、复杂数据运算等耗时较长的操作是会阻塞页面的渲染。为了避免用户等待太久,应该使用独立的线程来完成这类操作。不过一些编程语言或框架不易实现多线程,这时可以通过其他进程来实现。设想有一个进程能够完成发邮件的功能,那么在页面中只需要想办法通知这个进程向指定的地址发送邮件就可以了。
这个通知的过程可以借助任务队列来实现。任务队列,就是“传递任务的队列”。与任务队列进行交互的实体有两类,一类是生产者,另一类是消费者。生产者会将需要处理的任务加入任务队列中,而消费者则不断地从任务队列中读入任务信息并执行。
生产者和消费者无需知道彼此的实现细节,只需要约定好任务的描述格式。这使得生产者和消费者可以由不同的团队使用不同的编程语言编写。
消费者可以由多个,而且可以分布在不同的服务器中,可以轻易地降低单台服务器的负载。
通过LPUSH和RPOP
或者RPUSH和LPOP
命令实现任务队列。生产者将任务使用LPUSH命令加入到某个键中,另一边让消费者不断地使用RPOP命令从该键中取出任务即可。
在使用RPOP命令时,当键中没有数据时,需要每隔1秒循环取数据,这样太频繁了。可以使用BRPOP命令,当键中没有数据时,会一直阻塞,直到有数据才取出。
BRPOP命令接收两个参数,第一个是键名,第二个是超时时间,单位是秒。当超过了此时间仍然没有获得新元素的话就会返回nil。当超时时间是0时,表示不限制等待的时间,即如果没有新元素加入列表就会永远阻塞下去。
当获得一个元素后 BRPOP 命令返回两个值,分别是键名和元素值。
除了 BRPOP 命令外,Redis还提供了 BLPOP 命令,从队列左边取。
当一个进程用到的一个队列中,有很多的任务在队列中等待执行,这时来了几个紧急任务也加入到了这个队列,但是它们的执行只能按顺序执行,这就导致紧急任务无法第一时间得到执行。解决这一问题,可以把紧急任务放入另一个队列,当这个队列中有任务时,优先执行这个队列中任务,没有任务时还继续执行之前的队列。
同时监听多个队列,可以使用 BRPOP 命令,BRPOP 命令可以接收多个键,其完整的命令格式为:
BRPOP key [key ...]
如 BRPOP queue:1 queue:2 0
。意义是同时检测多个键,如果所有键都没有元素则阻塞,如果其中一个键有元素则会从该键中弹出元素。
如果多个键都有元素,则按照从左到右的顺序取第一个键中的一个元素。
借此特性可以实现区分优先级的任务队列。
“发布/订阅”模式(publish/subscribe)同样可以实现进程间的消息传递,其原理:“发布/订阅”模式中包含两种角色,分别是发布者和订阅者。订阅者可以订阅一个或若干个频道(channel),而发布者可以向指定的频道发送消息,所有订阅此频道的订阅者都会受到此消息。
发布者发布消息的命令时 PUBLISH,格式:
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 ...]
现在新开一个客户端A订阅 channel.1
127.0.0.1:6379> SUBSCRIBE channel.1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1
执行 SUBSCRIBE 命令后客户端会进入订阅状态,处于此状态下客户端不能使用除 SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE和PUNSUBSCRIBE这个4个属于“发布/订阅”模式的命令之外的命令,否则会报错。
进入订阅状态后客户端可能收到3种类型的回复。每种类型的回复都包含3个值,第一个值是消息的类型,根据消息类型的不同,第二、三个值得含义也不同。
消息类型取值:
(1)subscribe。表示订阅成功的反馈消息。第二个值是订阅成功的频道名称。第三个值是当前客户端订阅的频道数量。
(2)message。这个类型的回复是我们最关心的,它表示接收到的消息。第二个值表示产生消息的频道名称,第三个值是消息的内容。
(3)unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态,之后就可以执行其他非“发布/订阅”模式的命令了。
示例:
上面客户端A已经订阅频道channel.1了,新开一个客户端B向频道channel.1发送一条消息:
客户端B
127.0.0.1:6379> PUBLISH channel.1 hello
(integer) 1
返回值为1,表示有一个客户端订阅了 channel.1。客户端A收到类型为message的消息:
客户端A
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel.1"
3) (integer) 1
1) "message"
2) "channel.1"
3) "hello"
除了可以使用 SUBSCRIBE 命令订阅指定名称的频道外,还可以使用 PSUBSCRIBE 命令订阅指定的规则。规则支持glob风格通配符格式。
示例:
客户端C订阅频道
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.。这是在客户端B发布消息:
127.0.0.1:6379> PUBLISH channel.1 xxoo
(integer) 2
返回结果为2是因为客户端A和C都订阅了 channel.1。客户端C接收到回复是:
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) "xxoo"
pmessage表示这条消息是通过PSUBSCRIBE 命令订阅频道的,channel.?表示订阅时使用的通配符,channel.1表示实际收到消息的频道命令,xxoo则是消息内容。
author:su1573
鄙人记录生活点滴,学习并分享,请多指教!!!
如需交流,请联系 [email protected],鄙人看到会及时回复