RabbitMQ 簡介

RabbitMQ是幹什麼的呢?

     解釋RabbitMQ,就不得不提到AMQP(Advanced Message Queuing Protocol)協議。AMQP協議是一種基於網絡的消息傳輸協議,它能夠在應用或組織之間提供可靠的消息傳輸。RabbitMQ是該AMQP協議的一種實現,利用它,可以將消息安全可靠的從發送方傳輸到接收方。簡單的說,就是消息發送方利用RabbitMQ將信息安全的傳遞給接收方。


     可靠的消息傳輸爲什麼一定要用RabbitMQ呢?直接用TCP,HTTP不OK?

     在回答這個問題時,我比較模糊。應該說這個應用的範圍不同吧,TCP協議支持在IP之間進行消息傳輸,而RabbitMQ是根據關鍵字進行消息的分配和傳輸。TCP可以將消息從192.168.1.2傳輸到192.168.1.3。但是它不能將消息根據關鍵字進行傳輸吧,比如,給定一個關鍵字’key‘,你知道要將消息傳輸到哪嗎?呵呵,RabbitMQ知道。


     怎麼根據關鍵字發送消息呢?

     這個嘛!理解比較簡單,但介紹起來有點長。要理解這個發送機制,首先要對RabbitMQ的幾個定義搞清楚:

1 Server,要利用RabbitMQ進行消息傳輸,那麼就得有一個運行的RabbitMQ服務,我們可以稱爲Server

2 Producer,既然是傳輸消息,那得有一個消息的發送者,這裏我們成爲生產者(Producer)

3 Consumer,消息的接收者,這裏稱爲消息的消費這(Consumer)

4 Exchange,在生產者將消息發出後,消息往哪走呢?這個得由Exchange來決定,可以將它看作一個交換機,它 根據消息自帶的特徵信息(這裏指的是routing_key),進行發送。發給誰呢?不是接收者,是下面的Queue。

5 Queue,一個Exchange可以對應多個Queue,每個Queue都會在定義時,聲明自己要接收消息的特徵信息(routing_key)。Exchange根據Queue和消息的routing_key的匹配情況進行發送。消息到達Queue後,Consumer就可以將消息從Queue取出了。

       一個Server可以聲明n多Exchange,一個Exchange可以對應n多Queue。Exchange和Queue都是存在Server內部的。簡化點,可以理解爲一個Producer與一個Exchange綁定,Producer將消息交給Exchange,Exchange負責發送消息。一個Consumer與一個Queue綁定,當Queue中有消息時,直接取就行了,管它怎麼來的。而消息在Exchange在Queue之間的怎麼傳遞,是由RabbitMQ負責的。我們只需要將消息交給Exchange,然後在Queue中取就行了。(當然還要多點步驟:聲明消息和Queue的特徵信息,將Queue與Exchange進行關聯)

     用一個來自官網的圖片說明下:

RabbitMQ

P: Producer, X: Exchange, amq: Queue, C: Consumer

       在消息的傳輸機制上理解,較爲簡單。但在源碼級別上,進行使用要爲複雜。這個我個人要歸功於RabbitMQ客戶端的幾種封裝,讓我是分不清東南西北。下面是官網上列舉的六個應用實例,有易到難,比較容易理解。

1 直接發送模式

不聲明Exchange,即採用默認的Exchange。默認的Exchange可以根據routing_key將消息直接發送給特定Queue。

注意:

1) 這種匹配是消息的routing_key與Queue的名子直接進行匹配,而不是與Queue的routing_key。

2) 消息並不能直接發送給queue,這裏是經過一個名爲''的Exchange進行發送的。

RabbitMQ

Producer.py

#!/usr/bin/env python 
import pika 
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) 
channel = connection.channel() 
#這裏要對queue進行聲明,已確定Queue存在,如果不存在則創建名爲‘hello'的queue。
#否則消息發送時,queue不存在,消息會被直接丟棄
channel.queue_declare(queue='hello') 
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!') 
print " [x] Sent 'Hello World!'" 
connection.close()

Consumer.py

#!/usr/bin/env python 
import pika 
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) 
channel = connection.channel() 
channel.queue_declare(queue='hello') 
print ' [*] Waiting for messages. To exit press CTRL+C' 
def callback(ch, method, properties, body): 
    print " [x] Received %r" % (body,) 
channel.basic_consume(callback, queue='hello', no_ack=True) 
channel.start_consuming()

2 工作列隊模式

該模型適用於分發資源密集型的任務。假設一下你需要進行10次計算圓周率的操作,每次計算到小數點後n位,每次耗時1一個小時(這只是一個假設,一般沒有比要進行10重複操作的。現在就當作你真的有這個必要)。如果只用一臺機器計算,需要十個小時。但如果我們將這十個任務分發給十臺進行計算,那麼只需一個小時。下面的模型就是適用於這種分發任務的。

RabbitMQ

採用這種模式,列隊消息以輪詢(round_robin)的方式將消息平均的發給所有與Queue關聯的Consumer,一般情況下,每個Consumer都平均的分攤任務。

注意:

1) 在目前的情況下,消息一旦被Consumer取出,就立即從列隊中消除。這樣當woker執行到任務中途失敗時,該任務的信息也丟失了,不能重新開始。

2) RabbitMQ提供一種消息認證機制(message acknowledgments),只有當Consumer返回一個ack時,它纔會將消息從列隊中刪除。如果當Consumer斷開連接時,依然沒有收到ack,那麼它就會重新分發給消息。

3) RabbitMQ不允許以新的屬性來重新定義Queue,所以這裏我們需要給Queue換個新名子

Producer.py

#!/usr/bin/env python 
import pika 
import sys 
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) 
channel = connection.channel() 
#Queue聲明持久化
channel.queue_declare(queue='task_queue', durable=True) 
message = ' '.join(sys.argv[1:]) or "Hello World!" 
channel.basic_publish(exchange='', routing_key='task_queue', body=message, 
                                    properties=pika.BasicProperties( delivery_mode = 2, 消息聲明持久化 )) 
print " [x] Sent %r" % (message,) 
connection.close()

Consumer.py

#!/usr/bin/env python 
import pika 
import time 
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) 
channel = connection.channel() 
#Queue聲明持久化
channel.queue_declare(queue='task_queue', durable=True) 
print ' [*] Waiting for messages. To exit press CTRL+C' 
def callback(ch, method, properties, body): 
    print " [x] Received %r" % (body,) 
    time.sleep( body.count('.') ) 
    print " [x] Done" 
    #返回消息認證
    ch.basic_ack(delivery_tag = method.delivery_tag) 
channel.basic_qos(prefetch_count=1) 
channel.basic_consume(callback, queue='task_queue') 
channel.start_consuming()


3 廣播(fanout)模型

前面兩種模型,每條消息只能被一個Consumer獲取。原因在於:使用默認的Exchange,它只能將每條消息發給一個或零個Queue中。這裏我們將使用一種類型爲fanout的Exchange,它可以將消息發送給每一個於它關聯的Queue,這樣每個Consumer都可以獲取相同的消息。

RabbitMQ

Producer.py

#!/usr/bin/env python 
import pika 
import sys 
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) 
channel = connection.channel() 
#定義一個類型爲fanout的Exchange
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()

Consumer.py

 
  
    
   
  
#由於此時Exchange將消息發給所有的Queue,所以Queue無需命名,
#此處沒有給queue指定名稱,MQ會給它產生一個隨機名子。

4 direct模型

到目前爲止,Consumer只能被動的隨機接收一部分消息,或者接收全部,不能自主選擇接收哪一部分消息。該消息路由模型,可以使得Consumer指定它想要接收到消息。

RabbitMQ

RabbitMQ

將Exchange聲明爲direct類型:

channel.exchange_declare(exchange='direct_logs', type='direct')

將Queue與Exchange綁定,注意此處queue與Exchange綁定時,指定了一個參數routing_key。如上面兩圖所示,一個queue可以以多個routing_key與Exchange進行綁定,多個不同的Queue可以以相同routing_key與同一個Exchange進行綁定。遺留一個問題,一個Queue可不可以與多個Exchange進行綁定呢?

channel.queue_bind(exchange=exchange_name, queue=queue_name, routing_key='black')

發送消息:

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

在發送消息時,也會指定一個routing_key。當Exchange在決定將消息發給哪幾個Queue時,它會將該routing_key與Queue綁定時指定的routing_key進行匹配,相同的Queue則可接收消息。

Producer.py

#!/usr/bin/env python 
import pika 
import sys 
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) 
channel = connection.channel() 
channel.exchange_declare(exchange='direct_logs', type='direct') 
severity = sys.argv[1if len(sys.argv) > 1 else 'info' 
message = ' '.join(sys.argv[2:]) or 'Hello World!' 
channel.basic_publish(exchange='direct_logs', routing_key=severity, body=message) 
print " [x] Sent %r:%r" % (severity, message) 
connection.close()

Consumer.py

#!/usr/bin/env python 
import pika 
import sys 
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) 
channel = connection.channel() 
channel.exchange_declare(exchange='direct_logs', type='direct') 
result = channel.queue_declare(exclusive=True) 
queue_name = result.method.queue 
severities = sys.argv[1:] 
if not severities: 
    print >> sys.stderr, "Usage: %s [info] [warning] [error]" % \ (sys.argv[0],) 
    sys.exit(1) 
for severity in severities: 
    channel.queue_bind(exchange='direct_logs', queue=queue_name, routing_key=severity) 
print ' [*] Waiting for logs. To exit press CTRL+C' 

def
 callback(ch, method, properties, body): 
    print " [x] %r:%r" % (method.routing_key, body,)  

channel
.basic_consume(callback, queue=queue_name, no_ack=True)
channel.start_consuming()

5 Topic模型

direct模型是不是已經很好用、很靈活了?不,它的靈活度還不夠。看它在routing_key進行匹配時,只能將兩個完全相同的routing_key進行匹配,這個不夠好,要是能用正則表達式進行匹配,那就完美了。

Topic模型,雖然沒有實現用正則表達式進行匹配,但是它進步了一小步。實現了對任意的單詞進行匹配:

  • * 

    (星號) 可以匹配任意一個單詞

  • # 

    (警號) 可以匹配任意零個或多個單詞

RabbitMQ

Producer.py

#!/usr/bin/env python 
import pika 
import sys 
connection = pika.BlockingConnection(pika.ConnectionParameters( host='localhost')) 
channel = connection.channel() 
channel.exchange_declare(exchange='topic_logs', type='topic') 
routing_key = sys.argv[1] if len(sys.argv) > 1 else 'anonymous.info' 
message = ' '.join(sys.argv[2:]) or 'Hello World!' 
channel.basic_publish(exchange='topic_logs', routing_key=routing_key, body=message) 
print " [x] Sent %r:%r" % (routing_key, message) 
connection.close()

Consumer.py

 
  
  
    
   
  
   
   
   
   
      
 
    
   
  
     
     [object Object]

6 遠程調用模型(RPC)

這個是對RabbitMQ消息傳輸的實際應用,在Openstack中,各個組件間的調用就是通過RabbitMQ實現的,這樣是我爲什麼學習RabbitMQ的原因了。

RabbitMQ

個人猜想,Client端執行遠程調用(RPC CALL),通過一個Queue將函數名、傳參、已經用於傳輸返回結果的臨時聲明的Queue(這個Queue在聲明時,無需指定名子,由RabbitMQ自動分配,這樣還可以避免命名衝突),Server端接收到消息後,調用相應的函數進行處理,並將結果通過默認Exchange傳給臨時Queue,這樣就完成了一個遠程調用。

但是這個猜想有問題:

 每進行一次RPC CALL就要聲明一個臨時Queue。這個有點浪費。有多浪費我也不知道,沒測試過。

我們可以爲每個Client指定一個用於返回結果的Queue,這樣就不用每次聲明瞭。每次RPC CALL時,綁定一個correlation_id,這樣使得返回的結果不會混亂。

注意:

在返回結果的Queue中可能存在髒數據,比如,Server在將結果傳輸到Queue後,還沒來得及返回消息確認就掛了。那麼先前發的調用消息就不會消除,在Server下次啓動時會再次執行,並再次返回結果。這就有了髒數據。所以,面對Queue裏的髒數據,我們只需忽略就行了。

Server.py

 
  
    
   
 
  
    
  
    
  
 
    [object Object]

Client.py

#!/usr/bin/env python 
import pika 
import uuid 
class FibonacciRpcClient(object): 
    def __init__(self): 
        self.connection = pika.BlockingConnection(pika.ConnectionParameters(host='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, queue=self.callback_queue) 
    def on_response(self, ch, method, props, body): 
         if self.corr_id == props.correlation_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,)

原文:http://blog.sina.com.cn/s/blog_85998e380101amp6.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章