RabbitMQ中的Exchange Types

整理自:

  • https://www.rabbitmq.com
  • https://www.cloudamqp.com/blog/2015-09-03-part4-rabbitmq-for-beginners-exchanges-routing-keys-bindings.html
  • 《RabbitMQ实战》

在RabbitMQ中, 生产者把消息发送到一个交换机(exchange)上,而不是直接发布到队列上。交换机是一个消息路由代理,它负责把消息路由到不同的队列中去(通过header 属性,bindings,routing key)。

绑定(binding)是队列和交换机的一个链接。

路由键(routing key)是一个消息属性,交换机在决定把这条消息路由到队列中时,也许会根据这个key来做判断。(这取决于交换机的类型)

标准的RabbitMQ消息流:

  1. 生产者把消息发布到交换机上
  2. 交换机收到消息,负责把消息路由  
  3. 一个队列和交换机的绑定被建立,交换机把消息路由到队列中
  4. 队列中的消息被消费者处理


交换机、连接(connections)和队列在创建时可以被一些参数来配置,比如durable, temporary, 和 auto delete。Durable的交换机在服务器重启时会存活下来,直到被明确的删除。Temporary的交换器在RabbitMQ关闭的时候就没了。Auto Deleted的交换机在最后一个被绑定的对象解绑时就被删去了。

在RabbitMQ中,有四种不同的交换机。


一、Direct Exchange


Direct交换机根据消息的路由键把消息传递给队列。路由键是生产者加到消息头部的一个消息属性。路由键可以被视作交换机用来决定怎么路由这个消息的一个地址。消息会进入到binding key完全符合消息的路由键的队列中。

在你想要用一个简单的字符串标识符来区分被发布到同一个交换机上的消息时, direct交换机就有用了。想象队列A(下图中的create_pdf_queue)被binding key pdf_create绑定在direct交换机 (pdf_events) ,当一个路由键为pdf_create的消息到达这个direct交换机时,交换机把它路由到binding_key = routing_key的队列中去,在这里,也就是队列A。

(也可以多个队列用同一个routing key来绑定,这样的话消息会被发送到所有的这些队列中)


例子:

路由键为pdf_log的消息被发送到了交换机pdf_events,消息被路由到pdf_log_queue因为路由键pdf_log符合binding key pdf_log。

如果消息的路由键不符合任何的binding key,那么这个消息将被丢弃。

默认交换机

默认交换机是一个预先定义的direct交换机,没有名字,经常被空字符串“”来引用。 当你使用默认交换机时,你的消息会被路由到名字和消息的路由键相等的队列中。每个队列都自动绑定到默认交换机,binding key就是队列的名字。

默认交换机的代码示例:

producer.py:

#!/usr/bin/env python
import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

channel.queue_declare(queue='task_queue', durable=True)

message = ' '.join(sys.argv[1:]) or "Hello World!"
channel.basic_publish(exchange='',
                      routing_key='task_queue',
                      body=message,
                      properties=pika.BasicProperties(
                         delivery_mode = 2, # make message persistent
                      ))
print(" [x] Sent %r" % message)
connection.close()


worker.py:

#!/usr/bin/env python
import pika
import time

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

channel.queue_declare(queue='task_queue', durable=True)
print(' [*] Waiting for messages. To exit press CTRL+C')

def callback(ch, method, properties, body):
    print(" [x] Received %r" % body)
    time.sleep(body.count(b'.'))
    print(" [x] Done")
    ch.basic_ack(delivery_tag = method.delivery_tag)

channel.basic_qos(prefetch_count=1)
channel.basic_consume(callback,
                      queue='task_queue')

channel.start_consuming()


二、Topic Exchange


Topic交换机根据路由键和routing pattern之间的通配符匹配来把消息路由到一个或多个队列中。routing pattern是queue binding所指定的(类似于上文的binding key)。
路由键必须是可以由“.”分割的一个字符串,比如agreements.us和 agreements.eu.stockholm。routing pattern可能包含一个“*”来匹配路由键的某个位置。比如,"agreements.*.*.b.*"这个routing pattern将只会匹配第一个单词为agreements以及第四个单词为b的路由键。一个“#”匹配0个或多个单词,比如"agreements.eu.berlin.#"这个routing pattern匹配任意以agreements.eu.berlin开头的路由键。

消费者建立一个队列,并用给定的routing pattern与交换机绑定。所有路由键符合routing pattern的消息都会被路由到这个队列中。

示例:
路由键为agreements.eu.berlin的消息被发送到了交换机agreements。 消息被路由到队列berlin_agreements因为routing pattern agreements.eu.berlin.# 与路由键匹配。消息也被路由到了队列all_agreements,因为路由键匹配routing pattern agreements.# 。



示例代码:

producer.py:

#!/usr/bin/env python
import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='topic_logs',
                         type='topic')

routing_key = sys.argv[1] if len(sys.argv) > 2 else 'anonymous.info'
message = ' '.join(sys.argv[2:]) or 'Hello World!'
channel.basic_publish(exchange='topic_logs',
                      routing_key=routing_key,
                      body=message)
print(" [x] Sent %r:%r" % (routing_key, message))
connection.close()

consumer.py:

#!/usr/bin/env python
import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='topic_logs',
                         type='topic')

result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue

binding_keys = sys.argv[1:]
if not binding_keys:
    sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
    sys.exit(1)

for binding_key in binding_keys:
    channel.queue_bind(exchange='topic_logs',
                       queue=queue_name,
                       routing_key=binding_key)

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()



三、Fanout Exchange


Fanout交换机把收到的消息复制和路由到所有绑定它的队列上,不管routing key/pattern。提供的那些Key都会被忽视。
Fanout在一个消息需要倍发送到一个或多个队列中,用不同的方法去处理时,是有用的。


示例代码:

emit_log.py:

#!/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()

receive_logs.py:

#!/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()


四、Headers Exchange


Headers交换机基于包括headers和可选值的参数来做路由。Headers交换机和Topic交换机非常像,但是它是基于header的值而不是路由键来做路由的。一条消息被认为是匹配的,如果它header的值与绑定中指定的值相同的话。

一个叫做”x-match“的特殊的参数说明是否所有的header都必须匹配或者只需要有一个, 它有两种值, 默认为"all",表示所有的header键值对都必须匹配;而"any"表示至少一个header键值对需要匹配。Headers可以被int或者string组成。


  • 交换机与队列A的绑定的参数: format = pdf, type = report
  • 交换机与队列B的绑定的参数: format = pdf, type = log
  • 交换机与队列C的绑定的参数: format = zip, type = report

如果没有任何匹配的队列,消息会被丢弃。RabbitMQ提供了一个AMQP的拓展,"Dead Letter Exchange", 可以捕获到那些没有被成功传达的消息。

示例:

emitter.py:

#!/usr/bin/env python
import pika

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='testing',
                         type='headers')

try:
    while True:
        fields = {}
        data = input('>')
        if '=' in data:
            key, val = data.split('=')
            fields[key] = val
            continue
        channel.basic_publish(exchange = 'testing',
                              routing_key = '',
                              body = data,
                              properties = \
                                  pika.BasicProperties(headers = fields))
        print (' [x] Send {0} with headers: {1}'.format(data, fields))
except KeyboardInterrupt:
    print ('Bye')
finally:
    connection.close()

receiver.py:

#!/usr/bin/env python
import pika
import sys

connection = pika.BlockingConnection(pika.ConnectionParameters(
        host='localhost'))
channel = connection.channel()

channel.exchange_declare(exchange='testing',
                         type='headers')

result = channel.queue_declare(exclusive=True)
if not result:
    print ('Queue didnt declare properly!')
    sys.exit(1)
queue_name = result.method.queue


channel.queue_bind(exchange='testing',
                   queue = queue_name,
                   routing_key = '',
                   arguments = {'ham': 'good', 'x-match':'any'})

def callback(ch, method, properties, body):
    print ("{headers}:{body}".format(headers = properties.headers,
                                    body = body))

channel.basic_consume(callback,
                      queue = queue_name,
                      no_ack=True)

try:
    channel.start_consuming()
except KeyboardInterrupt:
    print ('Bye')
finally:
    connection.close()


发布了123 篇原创文章 · 获赞 334 · 访问量 52万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章