rabbitmq中文教程python版 - 遠端過程呼叫

weixin_34321977發表於2018-03-31

原始碼:https://github.com/ltoddy/rabbitmq-tutorial

遠端過程呼叫(RPC)

(using the Pika Python client)

本章節教程重點介紹的內容

在第二篇教程中,我們學習瞭如何使用工作佇列在多個工作人員之間分配耗時的任務。

但是如果我們需要在遠端計算機上執行某個功能並等待結果呢?那麼,這是一個不同的事情。
這種模式通常稱為遠端過程呼叫(RPC)。

在本教程中,我們將使用RabbitMQ構建一個RPC系統:一個客戶端和一個可擴充套件的RPC伺服器。
由於我們沒有任何值得分發的耗時任務,我們將建立一個返回斐波那契數字的虛擬RPC服務。

客戶端介面

為了說明如何使用RPC服務,我們將建立一個簡單的客戶端類。它將公開一個名為call的方法 ,
它傳送一個RPC請求並阻塞,直到收到答案:

fibonacci_rpc = FibonacciRpcClient()
result = fibonacci_rpc.call(4)
print("fib(4) is %r" % result)
*有關RPC的說明*

雖然RPC是計算中很常見的模式,但它經常被吹毛求疵。當程式設計師不知道函式呼叫是本地的還是
慢速的RPC時會出現這些問題。像這樣的混亂導致不可預知的問題,並增加了除錯的不必要的複雜性,
而不是我們想要的簡化軟體。

銘記這一點,請考慮以下建議:

  * 確保顯而易見哪個函式呼叫是本地的,哪個是遠端的。
  * 記錄您的系統。清楚元件之間的依賴關係。
  * 處理錯誤情況。當RPC伺服器長時間關閉時,客戶端應該如何反應?

有疑問時避免RPC。如果可以的話,你應該使用非同步管道 - 而不是類似於RPC的阻塞,
其結果被非同步推送到下一個計算階段。

回撥佇列

一般來說,通過RabbitMQ來執行RPC是很容易的。客戶端傳送請求訊息,伺服器回覆響應訊息。
為了接收響應,客戶端需要傳送一個“回撥”佇列地址和請求。讓我們試試看:

result = channel.queue_declare(exclusive=True)
callback_queue = result.method.queue

channel.basic_publish(exchange='',
                      routing_key='rpc_queue',
                      properties=pika.BasicProperties(
                            reply_to = callback_queue,
                            ),
                      body=request)
訊息屬性

AMQP 0-9-1協議預定義了一組包含14個屬性的訊息。大多數屬性很少使用,但以下情況除外:

delivery_mode:將訊息標記為持久(值為2)或瞬態(任何其他值)。你可能會記得第二篇教程中的這個屬性。
content_type:用於描述編碼的MIME型別。例如,對於經常使用的JSON編碼,將此屬性設定為application/json是一種很好的做法。
reply_to:通常用於命名回撥佇列。
correlation_id:用於將RPC響應與請求關聯起來。

相關ID

在上面介紹的方法中,我們建議為每個RPC請求建立一個回撥佇列。這是非常低效的,
但幸運的是有一個更好的方法 - 讓我們為每個客戶端建立一個回撥佇列。

這引發了一個新問題,在該佇列中收到回覆後,不清楚回覆屬於哪個請求。那是什麼時候使用correlation_id屬性。
我們將把它設定為每個請求的唯一值。稍後,當我們在回撥佇列中收到訊息時,我們將檢視此屬性,
並基於此屬性,我們將能夠將響應與請求進行匹配。如果我們看到未知的correlation_id值,
我們可以放心地丟棄該訊息 - 它不屬於我們的請求。

您可能會問,為什麼我們應該忽略回撥佇列中的未知訊息,而不是丟擲錯誤?
這是由於伺服器端可能出現競爭狀況。雖然不太可能,但在傳送給我們答案之後,但在傳送請求的確認訊息之前,
RPC伺服器可能會死亡。如果發生這種情況,重新啟動的RPC伺服器將再次處理該請求。
這就是為什麼在客戶端,我們必須優雅地處理重複的響應,理想情況下RPC應該是等冪的。

總結

3308539-75d24807a3f513b9.png
image

我們的RPC會像這樣工作:

  • 當客戶端啟動時,它建立一個匿名獨佔回撥佇列。
  • 對於RPC請求,客戶端將傳送具有兩個屬性的訊息:reply_to,該訊息設定為回撥佇列和correlation_id,該值設定為每個請求的唯一值。
  • 該請求被髮送到rpc_queue佇列。
  • RPC worker(又名:伺服器)正在等待該佇列上的請求。當出現請求時,它執行該作業,並使用reply_to欄位中的佇列將結果傳送回客戶端。
  • 客戶端在回撥佇列中等待資料。當出現訊息時,它會檢查correlation_id屬性。如果它匹配來自請求的值,則返回對應用程式的響應。

把它放在一起

rpc_server.py的程式碼:

#!/usr/bin/env python
import pika

connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))

channel = connection.channel()

channel.queue_declare(queue='rpc_queue')


def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)


def on_request(ch, method, props, body):
    n = int(body)

    print(" [.] fib(%s)" % n)
    response = fib(n)

    ch.basic_publish(exchange='',
                     routing_key=props.reply_to,
                     properties=pika.BasicProperties(
                         correlation_id=props.correlation_id),
                     body=str(response))
    ch.basic_ack(delivery_tag=method.delivery_tag)


channel.basic_qos(prefetch_size=1)
channel.basic_consume(on_request, queue='rpc_queue')

print(" [x] Awaiting RPC requests")
channel.start_consuming()

伺服器程式碼非常簡單:

  • (4)像往常一樣,我們首先建立連線並宣告佇列。
  • (11)我們宣告我們的斐波那契函式。它只假定有效的正整數輸入。(不要指望這個版本適用於大數字,它可能是最慢的遞迴實現)。
  • (20)我們宣告瞭basic_consume的回撥,它是RPC伺服器的核心。它在收到請求時執行。它完成工作並將響應發回。
  • (34)我們可能想執行多個伺服器程式。為了在多臺伺服器上平均分配負載,我們需要設定prefetch_count設定。

rpc_client.py的程式碼:

#!/usr/bin/env python
import pika
import uuid


class FibonacciRpcClient:
    def __init__(self):
        self.connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))

        self.channel = self.connection.channel()

        result = self.channel.queue_declare(exclusive=True)
        self.callback_queue = result.method.queue

        self.channel.basic_consume(self.on_response, no_ack=True)

    def on_response(self, ch, method, props, body):
        if self.corr_id == props.corrrelation_id:
            self.response = body

    def call(self, n):
        self.response = None
        self.corr_id = str(uuid.uuid4())
        self.channel.basic_publish(exchange='',
                                   routing_key='rpc_queue',
                                   properties=pika.BasicProperties(
                                       reply_to=self.callback_queue,
                                       correlation_id=self.corr_id),
                                   body=str(n))
        while self.response is None:
            self.connection.process_data_events()

        return int(self.response)


fibonacci_rpc = FibonacciRpcClient()

print(" [x] Requesting fib(30)")
response = fibonacci_rpc.call(30)
print(" [.] Got %r" % response)

客戶端程式碼稍有涉及:

  • (8)我們建立連線,通道併為回覆宣告獨佔的“回撥”佇列。
  • (17)我們訂閱'回撥'佇列,以便我們可以接收RPC響應。
  • (19)對每個響應執行的'on_response'回撥函式做了一個非常簡單的工作,對於每個響應訊息它檢查correlation_id是否是我們正在尋找的。如果是這樣,它將儲存self.response中的響應並打破消費迴圈。
  • (23)接下來,我們定義我們的主要呼叫方法 - 它執行實際的RPC請求。
  • (25)在這個方法中,首先我們生成一個唯一的correlation_id數並儲存 - 'on_response'回撥函式將使用這個值來捕獲適當的響應。
  • (29)接下來,我們釋出具有兩個屬性的請求訊息:reply_tocorrelation_id
  • (32)在這一點上,我們可以坐下來等待,直到適當的迴應到達。
  • (41)最後,我們將回復返回給使用者。

相關文章