前面我們創建了工作隊列,工作隊列中每個任務只分發給一個worker。現在我們要把一個消息分發給多個消費者,這種模式就是發佈訂閱。爲解釋這種模式,我們將構建一個簡單的日誌系統,它包括兩個程序——一個提交日誌,另一個接收並打印。日誌系統中每個接收程序的拷貝都會收到消息,這樣可以一個將日誌寫入磁盤,另一個輸出到屏幕。本質上講,發佈的日誌消息被廣播給了所有的消費者。
交換機
前面的章節我們說消息發送到隊列或是從隊列接收消息。現在是時候介紹一下Rabbit中的消息模型了。生產者發送消息,隊列存儲消息,消費者接收消息。RabbitMQ中消息模型的核心是,生產者不能直接將消息發送給隊列。實際上,大多數情況生產者甚至根本不知道把消息發送到哪個隊列。生產者只能將消息發送到交換機。交換機很簡單,一方面它從生產者接收消息,另一方面把消息推送到隊列。交換機必須知道如何處理收到的消息。是該推送到一個特定的隊列?還是推送到多個隊列?還是丟棄?交換機類型定義了處理的規則。幾種交換機類型:direct、topic、headers、fanout。現在先只看fanout。創建一個名爲logs的該類型的交換機:
channel.exchange_declare(exchange='logs',type='fanout')
fanout類型的交換機會廣播所有收到的消息給所有它知道的隊列。這正是我們的日誌系統需要的。
列出服務裏的交換機可以使用rabbitmqctl:
sudo rabbitmqctl list_exchanges
匿名交換機,前面的章節我們使用了默認交換機,由空字符串標識:
channel.basic_publish(exchange='',routing_key='hello', body=message)
參數exchange就是交換機的名字,空字符串表示默認或匿名交換機,消息被路由到由routing_key指定名字的隊列,如果它存在的話。
現在,我們可以將消息發佈到命名的交換機了:
channel.basic_publish(exchange='logs', routing_key='',body=message)
臨時隊列
之前我們使用的隊列都是有特定名字的。給隊列命名很關鍵,我們需要把workers指定到相同的隊列。當我們想在生產者和消費者間共享隊列時,給隊列命名很重要。
但是我們的日誌系統不是這種情況。我們想收到所有的消息,而不是一個子集。我們只關心當前的消息而不是舊的。我們需要這樣做:
首先,當我們連接到Rabbit我們需要一個新的空隊列。我們可以使用隨機的名字最好讓服務爲我們選一個名字來創建隊列,只要我們不提供queue參數給queue_declare即可:
result = channel.queue_declare()
result.method.queue就包含一個隨機的隊列名。
其次,我們斷開連接,隊列應該被刪除,exclusive參數可以做到:
result =channel.queue_declare(exclusive=True)
綁定
我們需要告訴交換機將消息發送到隊列,交換機和隊列之間的關係稱爲綁定。
channel.queue_bind(exchange='logs',queue=result.method.queue)
列出綁定,可以使用sudo rabbitmqctl list_bindings
提交log:
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()
創建連接後,聲明交換機,這步很必要,因爲發佈到一個不存在的交換機是被禁止的。
如果還沒有隊列綁定到交換機,消息將被丟棄。接收log:
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()