在本文中,我們將會講解另一種RabbitMQ消息傳遞模式。
即將同一條消息傳遞給多個接收者。這種模式也稱之爲發佈、訂閱模式
場景描述
在本文中,我們將會實現一個日誌處理系統。
該系統包含兩個部分:
- 第一部分是產生日誌。
- 第二部分是接收日誌並打印日誌。
在運行的過程中,我們會啓動多個接收日誌並打印日誌的服務。
我們希望可以看到每個服務都接收到全部的日誌信息。也就是說,服務1產生的日誌最終會廣播至所有的接收者。
Exchanges
在之前的文章中,我們講解了一個RabbitMQ模型由以下幾個部分組成:
- 消息生產者:產生消息的來源。
- 隊列:存儲尚未處理的消息。
- 消息處理者:接收消息並處理。
實際上,這個模型僅僅是一個簡化版的RabbitMQ模型。
對於真實的RabbitMQ模型而言,消息生產者是不會直接將消息傳入隊列中的。相反,消息生產者會把消息發送給Exchanges(中轉所),而Exchanges(中轉所)在接收到消息後,纔會把消息插入到隊列中。
在Exchanges(中轉所)中,實現的功能包括:
- 該消息是否需要插入某個隊列中
- 該消息僅需要發送至一個隊列還是需要發送至多個隊列
- 該消息是否根據某些Exchanges(中轉所)的類型需要被忽略等等。
Exchanges包含如下幾個類型:direct, topic, headers以及fanout。
在本文中,我們首先來學習fanout類型。
fanout的含義是將每條消息都廣播發送給所有的消息接收者。
例如,可以實現如下:
channel.exchange_declare(exchange='logs',
exchange_type='fanout')
即聲明exchange的類型爲fanout,且名稱爲logs。
在聲明瞭exchange後,我們可以繼續發佈消息:
channel.basic_publish(exchange='logs',
routing_key='',
body=message)
臨時隊列
在之前的文章中,我們需要指定一個特定的隊列名稱,因爲我們需要將一組Worker用於接收某個指定的隊列名稱中的消息。
但是,對於發佈、訂閱模式場景而言,我們需要的是:
- 接收全部的消息,而不是其中的一部分消息。
- 只接收最新產生的消息,而忽略之前傳入的消息。
因此,我們需要完成以下兩個部分的工作:
-
每次在連接到RabbitMQ時,創建一個空的隊列。實現該功能的方式是我們可以創建一個隨機名稱的隊列。
result = channel.queue_declare()
-
在不指定參數時,默認將會產生一個隨機字符串組成的隊列。
-
此外,我們需要在創建隊列時添加一個額外的參數:
result = channel.queue_declare(exclusive=True)
-
exclusive=True
表示當消息接收者斷開連接時,字段刪除該隊列。
將Exchange與消息隊列進行關聯
目前,我們已經創建了一個fanout類型的exchange。同時,在消息接收者中也創建了隊列。
現在,我們需要做的是將exchange和消息隊列關聯起來。
channel.queue_bind(exchange='logs',
queue=result.method.queue)
完整實現
最後,我們來給出消息生產者和消息接收者的完整實現:
消息生產者: 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',
exchange_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',
exchange_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()
實際來看一下效果吧:
我們可以先啓動兩個消息接收者:
receiver1:
python receive_logs.py
receiver2:
python receive_logs.py
然後,我們來發送幾條消息:
python emit_log.py
python emit_log.py