發布/訂閱
ps: 使用pika python客戶端
前面學習了搭建工作隊列,每一個任務只是分發給了一個工作者。但是現在我想實現將一個消息分發給多個消費者。就是設計模式中的"觀察者模式"--發布/訂閱。
為了描述這種模式,構件一個簡單的日志系統。包括兩個程序--第一個程序負發送日志消息,第二個程序負責獲取消息并輸出內容。
在日志系統中,所有運行的接收方程序都會接收消息,我們讓其中一個接收者將日志寫入磁盤,另一個接收者將日志輸出到屏幕上。日志消息是被廣播到所有的接收者。
交換機####
分析前面的教程:
- 發布者(producer)是發布消息的應用程序
- 隊列(queue)用于消息的存儲
- 消費者(consumer)是接受消息的應用程序。
RabbitMQ其實不是直接將消息發送到隊列中去,事實上,發布者是將消息交給excheng(交換機)。交換機一邊從消息發布方接收消息,一邊將消息推送到隊列。交換機必須知道如何處理他所接收的消息,是應該推送到指定的隊列還是多個隊列,或者直接忽視消息。這些規則是有交換機的類型來決定。
RabbitMQ提供幾種類型的交換機可供選擇:
Item | name |
---|---|
直連交換機 | direct exchange |
扇型交換機 | fanout exchange |
主題交換機 | topic exchange |
頭交換機 | headers exchange |
我們使用一下方法來創建一個扇型交換機
channel.exchange_declare(exhcang='logs', type='fanout')
Fanout exchange將所有生產者發送到本交換機上的消息全部像風扇轉動,將所有的消息發給所有的隊列。
匿名交換機
前面我們沒有提到減緩及,但是仍然能夠將消息發送到隊列中。因為我們使用了命名為空字符串的默認交換機。
channel.basic_publish(echange='',
routing_key='hello',
body=message)
exchange參數就是交換機的名字,空字符串表示匿名交換機,消息將發送到指定的routing_key指定的隊列。
我們可以發送一個消息到一個具名的交換機
channel.basic_publish(exchange='logs',
routing='',
body=message)
在扇型交換機中,routing_key是不需要的。
臨時隊列
隊列的名字可以由我們手動創建,但是也可以使用系統給我們創建一個隨機的隊列名字。只需要在創建隊列的函數中加上參數就可以。
result = channel.queue_declare(exclusive=True)
這樣就可以創建一個匿名隊列(形式為amq.gen-*),我們可以通過result.method.queue來獲取這個隨機的隊列名。當消費者斷開連接的時候嗎,這個隊列就會被立即刪除。
綁定(bindings)
我們創建一個扇型交換機和一個隊列,現在需要告訴交換機如發送消息給我們的隊列,交換機和隊列之間的關系叫做綁定(binding)
channel.queue_bind(exchange='logs',
queue=result.method.queue)
現在,logs交換機將會把消息添加到我們的隊列中。
Coding
emit_log.py
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()
我們創建了一個fanout類型的交換機,發送消息時將消息發送到這個交換機上。
receives_logs.py
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()
- 消費者創建了一個fanout類型的交換機,這里重復創建是如果消費者程序先運行,不創建交換機,是不允許將消息發送到一個不存在的交換機的。
- 消費者創建了一個匿名隊列,然后將這個匿名隊列和交換機進行bind。
- 消費者等待從綁定的交換機隊列中獲取消息。
打開兩個終端一個終端將日志保存到日志文件;另一個將日志輸出到屏幕上。
$ python receive_logs.py > logs_from_rabbit.log
這個終端運行的消費者將log信息打印出來
$ python receive_logs.py
發送日志信息:
$ python emit_log.py
使用rabbitmqctl list_bindings
可以查看已經創建的隊列綁定
$ sudo rabbitmqctl list_bindings
Listing bindings ...
...
logs amq.gen-TJWkez28YpImbWdRKMa8sg== []
logs amq.gen-x0kymA4yPzAT6BoC/YP+zw== []
...done.
顯示我們創建的兩個匿名隊列都綁定到了fanout類型交換機logs上面。
待續。。。
參考文章:http://rabbitmq.mr-ping.com/tutorials_with_python/[3]Publish_Subscribe.html