RabbitMQ的發佈、訂閱模式(廣播)

在本文中,我們將會講解另一種RabbitMQ消息傳遞模式。

即將同一條消息傳遞給多個接收者。這種模式也稱之爲發佈、訂閱模式

場景描述

在本文中,我們將會實現一個日誌處理系統。
該系統包含兩個部分:

  1. 第一部分是產生日誌。
  2. 第二部分是接收日誌並打印日誌。

在運行的過程中,我們會啓動多個接收日誌並打印日誌的服務。
我們希望可以看到每個服務都接收到全部的日誌信息。也就是說,服務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用於接收某個指定的隊列名稱中的消息。
但是,對於發佈、訂閱模式場景而言,我們需要的是:

  1. 接收全部的消息,而不是其中的一部分消息。
  2. 只接收最新產生的消息,而忽略之前傳入的消息。

因此,我們需要完成以下兩個部分的工作:

  1. 每次在連接到RabbitMQ時,創建一個空的隊列。實現該功能的方式是我們可以創建一個隨機名稱的隊列。

result = channel.queue_declare()
  1. 在不指定參數時,默認將會產生一個隨機字符串組成的隊列。

  2. 此外,我們需要在創建隊列時添加一個額外的參數:

result = channel.queue_declare(exclusive=True)
  1. 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
 
發佈了142 篇原創文章 · 獲贊 20 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章