整理自:
- 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消息流:
- 生產者把消息發佈到交換機上
- 交換機收到消息,負責把消息路由
- 一個隊列和交換機的綁定被建立,交換機把消息路由到隊列中
- 隊列中的消息被消費者處理
交換機、連接(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
#!/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
示例代碼:
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()