第三种应用场景是发布/订阅模式
这种场景的特点是,一个发布者发布消息,多个订阅者根据自己的需求订阅相同或不同的内容。
这时,就需要exchange出马了。在之前的两个例子中,因为对binding没有要求,所以使用默认的exchange足以完成任务。
先看P端代码:
#!/usr/bin/env python
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs',
type='fanout')
message = ' '.join(sys.argv[1:]) or "info: Hello World!"
channel.basic_publish(exchange='logs',
routing_key='',
body=message)
print " [x] Sent %r" % (message,)
connection.close()
与前面的例子相比,主要区别是声明了一个exchange,命名为'logs',并且类型设定为fanout.之前的文章已经讲过fanout类型的行为模式,这里不再累述。
channel.exchange_declare(exchange='logs',
type='fanout')
这里可以体现出,即使是对于订阅模式,P(发布者)也无需关心有多少队列等在后面,它只需要指定一个合适的exchange(快递员),快递公司会负责找到对此感兴趣的C(订阅者)。
C端代码:
#!/usr/bin/env python
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='logs',
type='fanout')
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
channel.queue_bind(exchange='logs',
queue=queue_name)
print ' [*] Waiting for logs. To exit press CTRL+C'
def callback(ch, method, properties, body):
print " [x] %r" % (body,)
channel.basic_consume(callback,
queue=queue_name,
no_ack=True)
channel.start_consuming()
C端代码的变化体现在对于队列的操作上,首先声明了一个未命名的队列。在queue_declare函数中,如果未指明队列名称,RabbitMQ就会自动赋值一个唯一的名字,可以通过result.method.queue获取这个名字。作为唯一使用的选项,exclusive=Ture指定了在Customer停止使用队列后自动删除。
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
channel.queue_bind(exchange='logs',
queue=queue_name)
之后将队列与exchange绑定,就可以接收所有exchange上接收到的消息了。
对于订阅来说,所有人订阅所有的消息只是一种场景,更多情况下,每个订阅者都有自己特定的需求。
这个时候需要用到的就是direct exchange,对于不同的队列分别定制它的绑定(binding),也就是binding key.
例如:
或者
从这两张图上可以看出,绑定既可以是同一个队列绑定多个key,也可以是多个队列绑定同一个key。
依旧回来看下P端的代码先
#!/usr/bin/env python
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='direct_logs',
type='direct')
severity = sys.argv[1] if len(sys.argv) > 1 else 'info'
message = ' '.join(sys.argv[2:]) or 'Hello World!'
channel.basic_publish(exchange='direct_logs',
routing_key=severity,
body=message)
print " [x] Sent %r:%r" % (severity, message)
connection.close()
除了exchange type改为direct,唯一的差别就是设置了routing_key,给每条发出的消息设置一个routing_key,
C端的代码则变得越来越长
#!/usr/bin/env python
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='direct_logs',
type='direct')
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
severities = sys.argv[1:]
if not severities:
print >> sys.stderr, "Usage: %s [info] [warning] [error]" % \
(sys.argv[0],)
sys.exit(1)
for severity in severities:
channel.queue_bind(exchange='direct_logs',
queue=queue_name,
routing_key=severity)
print ' [*] Waiting for logs. To exit press CTRL+C'
def callback(ch, method, properties, body):
print " [x] %r:%r" % (method.routing_key, body,)
channel.basic_consume(callback,
queue=queue_name,
no_ack=True)
channel.start_consuming()
for severity in severities:
channel.queue_bind(exchange='direct_logs',
queue=queue_name,
routing_key=severity)
也就是在绑定队列的时候一起指名routing_key,这就可以和P端发出时的routing_key作比对,看是否是当前队列订阅的了。