2-3 Tutorials with python Publish Subscribe


發佈/訂閱

(使用pika 0.9.5 Python客戶端)

在上篇教程中,我們搭建了一個工作隊列,每個任務只分發給一個工作者(worker)。在本篇教程中,我們要做的跟之前完全不一樣 —— 分發一個消息給多個消費者(consumers)。這種模式被稱爲“發佈/訂閱”。

爲了描述這種模式,我們將會構建一個簡單的日誌系統。它包括兩個程序——第一個程序負責發送日誌消息,第二個程序負責獲取消息並輸出內容。

在我們的這個日誌系統中,所有正在運行的接收方程序都會接受消息。我們用其中一個接收者(receiver)把日誌寫入硬盤中,另外一個接受者(receiver)把日誌輸出到屏幕上。

最終,日誌消息被廣播給所有的接受者(receivers)。

交換機(Exchanges)

前面的教程中,我們發送消息到隊列並從中取出消息。現在是時候介紹RabbitMQ中完整的消息模型了。

讓我們簡單的概括一下之前的教程:

  • 發佈者(producer)是發佈消息的應用程序。
  • 隊列(queue)用於消息存儲的緩衝。
  • 消費者(consumer)是接收消息的應用程序。

RabbitMQ消息模型的核心理念是:發佈者(producer)不會直接發送任何消息給隊列。事實上,發佈者(producer)甚至不知道消息是否已經被投遞到隊列。

發佈者(producer)只需要把消息發送給一個交換機(exchange)。交換機非常簡單,它一邊從發佈者方接收消息,一邊把消息推送到隊列。交換機必須知道如何處理它接收到的消息,是應該推送到指定的隊列還是是多個隊列,或者是直接忽略消息。這些規則是通過交換機類型(exchange type)來定義的。

有幾個可供選擇的交換機類型:直連交換機(direct), 主題交換機(topic), (頭交換機)headers和 扇型交換機(fanout)。我們在這裏主要說明最後一個 —— 扇型交換機(fanout)。先創建一個fanout類型的交換機,命名爲logs:

channel.exchange_declare(exchange='logs',
                         type='fanout')

扇型交換機(fanout)很簡單,你可能從名字上就能猜測出來,它把消息發送給它所知道的所有隊列。這正是我們的日誌系統所需要的。

交換器列表

rabbitmqctl能夠列出服務器上所有的交換器:

$ sudo rabbitmqctl list_exchanges
Listing exchanges ...
logs      fanout
amq.direct      direct
amq.topic       topic
amq.fanout      fanout
amq.headers     headers
...done.

這個列表中有一些叫做amq.*的交換器。這些都是默認創建的,不過這時候你還不需要使用他們。

匿名的交換器

前面的教程中我們對交換機一無所知,但仍然能夠發送消息到隊列中。因爲我們使用了命名爲空字符串("")默認的交換機。

回想我們之前是如何發佈一則消息:

channel.basic_publish(exchange='',
                      routing_key='hello',
                      body=message)

exchange參數就是交換機的名稱。空字符串代表默認或者匿名交換機:消息將會根據指定的routing_key分發到指定的隊列。

現在,我們就可以發送消息到一個具名交換機了:

channel.basic_publish(exchange='logs',
                      routing_key='',
                      body=message)

臨時隊列

你還記得之前我們使用的隊列名嗎( hello和task_queue)?給一個隊列命名是很重要的——我們需要把工作者(workers)指向正確的隊列。如果你打算在發佈者(producers)和消費者(consumers)之間共享同隊列的話,給隊列命名是十分重要的。

但是這並不適用於我們的日誌系統。我們打算接收所有的日誌消息,而不僅僅是一小部分。我們關心的是最新的消息而不是舊的。爲了解決這個問題,我們需要做兩件事情。

首先,當我們連接上RabbitMQ的時候,我們需要一個全新的、空的隊列。我們可以手動創建一個隨機的隊列名,或者讓服務器爲我們選擇一個隨機的隊列名(推薦)。我們只需要在調用queue_declare方法的時候,不提供queue參數就可以了:

result = channel.queue_declare()

這時候我們可以通過result.method.queue獲得已經生成的隨機隊列名。它可能是這樣子的:amq.gen-U0srCoW8TsaXjNh73pnVAw==。

第二步,當與消費者(consumer)斷開連接的時候,這個隊列應當被立即刪除。exclusive標識符即可達到此目的。

result = channel.queue_declare(exclusive=True)

綁定(Bindings)

我們已經創建了一個扇型交換機(fanout)和一個隊列。現在我們需要告訴交換機如何發送消息給我們的隊列。交換器和隊列之間的聯繫我們稱之爲綁定(binding)。

channel.queue_bind(exchange='logs',
                   queue=result.method.queue)

現在,logs交換機將會把消息添加到我們的隊列中。

綁定(binding)列表

你可以使用rabbitmqctl list_bindings 列出所有現存的綁定。

代碼整合

發佈日誌消息的程序看起來和之前的沒有太大區別。最重要的改變就是我們把消息發送給logs交換機而不是匿名交換機。在發送的時候我們需要提供routing_key參數,但是它的值會被扇型交換機(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()

(emit_log.py 源文件)

正如你看到的那樣,在連接成功之後,我們聲明瞭一個交換器,這一個是很重要的,因爲不允許發佈消息到不存在的交換器。

如果沒有綁定隊列到交換器,消息將會丟失。但這個沒有所謂,如果沒有消費者監聽,那麼消息就會被忽略。

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

(receive_logs.py source)

這樣我們就完成了。如果你想把日誌保存到文件中,只需要打開控制檯輸入:

$ python receive_logs.py > logs_from_rabbit.log

如果你想在屏幕中查看日誌,那麼打開一個新的終端然後運行:

$ python receive_logs.py

當然還要發送日誌:

$ python emit_log.py

使用rabbitmqctl list_bindings你可確認已經創建的隊列綁定。你可以看到運行中的兩個receive_logs.py程序:

$ sudo rabbitmqctl list_bindings
Listing bindings ...
 ...
logs    amq.gen-TJWkez28YpImbWdRKMa8sg==                []
logs    amq.gen-x0kymA4yPzAT6BoC/YP+zw==                []
...done.

顯示結果很直觀:logs交換器把數據發送給兩個系統命名的隊列。這就是我們所期望的。

如何監聽消息的子集呢?讓我們移步教程4

發佈了21 篇原創文章 · 獲贊 7 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章