AMQP大致內容就是,將消息和隊列綁定起來,規定讓進入到交換機中的具有某個路由鍵的消息進入到指定隊列中去。
RabbitMQ就是,可以自然的在網絡上應用程序之間傳送數據的一類程序。且支持持久化。
http://blog.ftofficer.com/2010/03/translation-rabbitmq-python-rabbits-and-warrens/
1.MQ作用:
現成的、通用的消息隊列(MQ)服務器——無論是用什么語言寫出的,不需要復雜的裝配的,可以自然的在網絡上的應用程序之間傳送數據的一類程序。
2.MQ經歷及RabbitMQ的特點:
過去的4年里,人們寫了有好多好多的開源的MQ服務器啊。
有三個專門設計用來做及其靈活的消息隊列的程序值得關注:
Apache ActiveMQ 曝光率最高,不過看起來它有些問題,可能會造成丟消息。不可接受,下一個。
ZeroMQ 和 RabbitMQ 都支持一個開源的消息協議,稱為為AMQP。AMQP的一個優點是它是一個靈活和開放的協議,AMQP,即Advanced Message Queuing Protocol,一個提供統一消息服務的應用層標準高級消息隊列協議,是應用層協議的一個開放標準,為面向消息的中間件設計。以便和另外兩個商業化的Message Queue (IBM和Tibco)競爭,很好。不過ZeroMQ不支持消息持久化和崩潰恢復,不太好。剩下的只有RabbitMQ了。如果你不在意消息持久化和崩潰恢復,試試ZeroMQ吧,延遲很低,而且支持靈活的拓撲。
RabbitMQ支持持久化。是的,如果RabbitMQ死掉了,消息并不會丟失,當隊列重啟,一切都會回來。
Rabbitmq是一個MQ系統,也就是消息中間件,它實現了AMQP 0.8規范,簡單來說就是一個TCP的廣播服務器。AMQP協議,你可以類比JMS,不過JMS僅僅是java領域內的API規范,而AMQP比JMS更進一步,它有自己的wire-level protocol,有一套可編程的協議,中立于語言。
3.RabbitMQ兔子消息隊列上使用AMQP協議,工作方式解讀:
這里是花了一周時間閱讀關于AMQP和關于它如何在RabbitMQ上工作的文檔之后的一個總結,還有,怎么在Python當中使用。
3.1AMQP當中有四個概念非常重要:虛擬主機(virtual host),交換機(exchange),隊列(queue)和綁定(binding)。一個虛擬主機持有一組交換機、隊列和綁定。為什么需要多個虛擬主機呢?很簡單,RabbitMQ當中,用戶只能在虛擬主機的粒度進行權限控制。因此,如果需要禁止A組訪問B組的交換機/隊列/綁定,必須為A和B分別創建一個虛擬主機。每一個RabbitMQ服務器都有一個默認的虛擬主機“/”。
3.2交換機,隊列,還有綁定的關系
交換機可以理解成具有路由表的路由程序,僅此而已。每個消息都有一個稱為路由鍵(routing key)的屬性,就是一個簡單的字符串。交換機當中有一系列的綁定(binding),即路由規則(routes),例如,指明具有路由鍵 “X” 的消息要到名為timbuku的隊列當中去。
一個綁定就是一個基于路由鍵將交換機和隊列連接起來的路由規則:
路由規則,即綁定(binding)。一個綁定就是一個類似這樣的規則:將交換機“desert(沙漠)”當中具有路由鍵“阿里巴巴”的消息送到隊列“hideout(山洞)”里面去。換句話說,一個綁定就是一個基于路由鍵將交換機和隊列連接起來的路由規則。
交換機不過就是一個由綁定構成的路由表:
,一個綁定就是一個基于路由鍵將交換機和隊列連接起來的路由規則。例如,具有路由鍵“audit”的消息需要被送到兩個隊列,“log-forever”和“alert-the-big-dude”。要做到這個,就需要創建兩個綁定,每個都連接一個交換機和一個隊列,兩者都是由“audit”路由鍵觸發。在這種情況下,交換機會復制一份消息并且把它們分別發送到兩個隊列當中。交換機不過就是一個由綁定構成的路由表。
交換機有多種類型。他們都是做路由的,不過接受不同類型的綁定。為什么不創建一種交換機來處理所有類型的路由規則呢?因為每種規則用來做匹配分子的CPU開銷是不同的。常見有以下三種類型交換機:
Fanout Exchange– 不處理路由鍵。你只需要簡單的將隊列綁定到交換機上。一個發送到交換機的消息都會被轉發到與該交換機綁定的所有隊列上。很像子網廣播,每臺子網內的主機都獲得了一份復制的消息。Fanout交換機轉發消息是最快的。
Direct Exchange– 處理路由鍵。需要將一個隊列綁定到交換機上,要求該消息與一個特定的路由鍵完全匹配。這是一個完整的匹配。如果一個隊列綁定到該交換機上要求路由鍵 “dog”,則只有被標記為“dog”的消息才被轉發,不會轉發dog.puppy,也不會轉發dog.guard,只會轉發dog。
Topic Exchange– 將路由鍵和某模式進行匹配。此時隊列需要綁定要一個模式上。符號“#”匹配一個或多個詞,符號“*”匹配不多不少一個詞。因此“audit.#”能夠匹配到“audit.irs.corporate”,但是“audit.*” 只會匹配到“audit.irs”。我在RedHat的朋友做了一張不錯的圖,來表明topic交換機是如何工作的:
4.持久化這些小東西們
4.1關于消息的持久化
如果服務器程序掛了,RabbitMQ重啟之后會干凈的像個新生兒。隊列、交換機和綁定,還有,放在隊列里面但是尚未處理的消息們,全都丟失了。
隊列和交換機有一個創建時候指定的標志durable,直譯叫做堅固的。durable的唯一含義就是具有這個標志的隊列和交換機會在重啟之后重新建立,它不表示說在隊列當中的消息會在重啟后恢復。那么如何才能做到不只是隊列和交換機,還有消息都是持久的呢?
對于一個需要在重啟之后回復的消息來說,它需要被寫入到磁盤上,而即使是最簡單的磁盤操作也是要消耗時間的。如果和消息的內容相比,你更看重的是消息處理的速度,那么不要使用持久化的消息。
4.2消息持久化的操作
當你將消息發布到交換機的時候,可以指定一個標志“Delivery Mode”(投遞模式)。根據你使用的AMQP的庫不同,指定這個標志的方法可能不太一樣(我們后面會討論如何用Python搞定)。簡單的說,就是將Delivery Mode設置成2,也就是持久的(persistent)即可。一般的AMQP庫都是將Delivery Mode設置成1,也就是非持久的。所以要持久化消息的步驟如下:
將交換機設成 durable。
將隊列設成 durable。
將消息的 Delivery Mode 設置成2 。
另外,如果你綁定了一個durable的隊列和一個durable的交換機,RabbitMQ會自動保留這個綁定。類似的,如果刪除了某個隊列或交換機(無論是不是durable),依賴它的綁定都會自動刪除。
注意兩點:
RabbitMQ 不允許你綁定一個非堅固(non-durable)的交換機和一個durable的隊列。反之亦然。要想成功必須隊列和交換機都是durable的。
一旦創建了隊列和交換機,就不能修改其標志了。例如,如果創建了一個non-durable的隊列,然后想把它改變成durable的,唯一的辦法就是刪除這個隊列然后重現創建。因此,最好仔細檢查創建的標志。
5.開始喂蛇了~
【譯注】說喂蛇是因為Python的圖標是條蛇。
AMQP的一個空白地帶是如何在Python當中使用。對于其他語言有一大坨材料。
Java –http://www.rabbitmq.com/java-client.html
Ruby –http://somic.org/2008/06/24/ruby-amqp-rabbitmq-example/
但是對Python老兄來說,你需要花點時間來挖掘一下。所以我寫了這個,這樣別的家伙們就不需要經歷我這種抓狂的過程了。
6.實例:一個基于Python語言的rabbitMQ收發消息代碼段
6.1建立發送消息的完整結構。
首先,我們需要一個Python的AMQP庫。有兩個可選:
py-amqplib– 通用的AMQP
txAMQP– 使用Twisted框架的AMQP庫,因此允許異步I/O。
根據你的需求,py-amqplib或者txAMQP都是可以的。因為是基于Twisted的,txAMQP可以保證用異步IO構建超高性能的AMQP程序。但是Twisted編程本身就是一個很大的主題……因此清晰起見,我們打算用 py-amqplib。更新:請參見Esteve Fernandez關于txAMQP的使用和代碼樣例的回復。
AMQP支持在一個TCP連接上啟用多個MQ通信channel,每個channel都可以被應用作為通信流。每個AMQP程序至少要有一個連接和一個channel。
fromamqplibimportclient_0_8asamqpconn = amqp.Connection(host="localhost:5672 ", userid="guest",password="guest", virtual_host="/", insist=False)chan = conn.channel()
每個channel都被分配了一個整數標識,自動由Connection()類的.channel()方法維護。或者,你可以使用.channel(x)來指定channel標識,其中x是你想要使用的channel標識。通常情況下,推薦使用.channel()方法來自動分配channel標識,以便防止沖突。
現在我們已經有了一個可以用的連接和channel。現在,我們的代碼將分成兩個應用,生產者(producer)和消費者(consumer)。我們先創建一個消費者程序,他會創建一個叫做“po_box”的隊列和一個叫“sorting_room”的交換機:
chan.queue_declare(queue="po_box", durable=True,exclusive=False, auto_delete=False)chan.exchange_declare(exchange="sorting_room",type="direct", durable=True,auto_delete=False,)
這段代碼干了啥?首先,它創建了一個名叫“po_box”的隊列,它是durable的(重啟之后會重新建立),并且最后一個消費者斷開的時候不會自動刪除(auto_delete=False)。在創建durable的隊列(或者交換機)的時候,將auto_delete設置成false是很重要的,否則隊列將會在最后一個消費者斷開的時候消失,與durable與否無關。如果將durable和auto_delete都設置成True,只有尚有消費者活動的隊列可以在RabbitMQ意外崩潰的時候自動恢復。
(你可以注意到了另一個標志,稱為“exclusive”。如果設置成True,只有創建這個隊列的消費者程序才允許連接到該隊列。這種隊列對于這個消費者程序是私有的)。
還有另一個交換機聲明,創建了一個名字叫“sorting_room”的交換機。auto_delete和durable的含義和隊列是一樣的。但是,.excange_declare() 還有另外一個參數叫做type,用來指定要創建的交換機的類型(如前面列出的):fanout,direct和topic.
到此為止,你已經有了一個可以接收消息的隊列和一個可以發送消息的交換機。不過我們需要創建一個綁定,把它們連接起來。
chan.queue_bind(queue=”po_box”, exchange=”sorting_room”,
routing_key=”jason”)
這個綁定的過程非常直接。任何送到交換機“sorting_room”的具有路由鍵“jason” 的消息都被路由到名為“po_box” 的隊列。
6.2從隊列取消息以及反饋:
現在,你有兩種方法從隊列當中取出消息。第一個是調用chan.basic_get(),主動從隊列當中拉出下一個消息(如果隊列當中沒有消息,chan.basic_get()會返回None, 因此下面代碼當中print msg.body 會在沒有消息的時候崩掉):
msg = chan.basic_get("po_box")printmsg.bodychan.basic_ack(msg.delivery_tag)
但是如果你想要應用程序在消息到達的時候立即得到通知怎么辦?這種情況下不能使用chan.basic_get(),你需要用chan.basic_consume()注冊一個新消息到達的回調。
defrecv_callback(msg):print'Received: '+ msg.bodychan.basic_consume(queue='po_box', no_ack=True,callback=recv_callback, consumer_tag="testtag")whileTrue:? ? chan.wait()chan.basic_cancel("testtag")
chan.wait()放在一個無限循環里面,這個函數會等待在隊列上,直到下一個消息到達隊列。chan.basic_cancel()用來注銷該回調函數。參數consumer_tag當中指定的字符串和chan.basic_consume()注冊的一直。在這個例子當中chan.basic_cancel()不會被調用到,因為上面是個無限循環…… 不過你需要知道這個調用,所以我把它放在了代碼里。
需要注意的另一個東西是no_ack參數。這個參數可以傳給chan.basic_get()和chan.basic_consume(),默認是false。當從隊列當中取出一個消息的時候,RabbitMQ需要應用顯式地回饋說已經獲取到了該消息。如果一段時間內不回饋,RabbitMQ會將該消息重新分配給另外一個綁定在該隊列上的消費者。另一種情況是消費者斷開連接,但是獲取到的消息沒有回饋,則RabbitMQ同樣重新分配。如果將no_ack參數設置為true,則py-amqplib會為下一個AMQP請求添加一個no_ack屬性,告訴AMQP服務器不需要等待回饋。但是,大多數時候,你也許想要自己手工發送回饋,例如,需要在回饋之前將消息存入數據庫。回饋通常是通過調用chan.basic_ack()方法,使用消息的delivery_tag屬性作為參數。參見chan.basic_get()的實例代碼。
好了,這就是消費者的全部代碼。(下載:amqp_consumer.py)
6.3發送者將消息發送到交換機
不過沒有人發送消息的話,要消費者何用?所以需要一個生產者。下面的代碼示例表明如何將一個簡單消息發送到交換區“sorting_room”,并且標記為路由鍵“jason” :
msg = amqp.Message("Test message!")msg.properties["delivery_mode"]=2chan.basic_publish(msg,exchange="sorting_room",routing_key="jason")
你也許注意到我們設置消息的delivery_mode屬性為2,因為隊列和交換機都設置為durable的,這個設置將保證消息能夠持久化,也就是說,當它還沒有送達消費者之前如果RabbitMQ重啟則它能夠被恢復。
剩下的最后一件事情(生產者和消費者都需要調用的)是關閉channel和連接:
chan.close()conn.close()
很簡單吧。(下載:amqp_publisher.py)
7.實踐
來真實地跑一下吧……
現在我們已經寫好了生產者和消費者,讓他們跑起來吧。假設你的RabbitMQ在localhost上安裝并且運行。
打開一個終端,執行python ./amqp_consumer.py讓消費者運行,并且創建隊列、交換機和綁定。
然后在另一個終端運行python ./amqp_publisher.py “AMQP rocks.”。如果一切良好,你應該能夠在第一個終端看到輸出的消息。
付諸使用吧
我知道這個教程是非常粗淺的關于AMQP/RabbitMQ和如何使用Python訪問的教程。希望這個可以說明所有的概念如何在Python當中被組合起來。如果你發現任何錯誤,請聯系原作者(williamsjj@digitar.com) 【譯注:如果是翻譯問題請聯系譯者】。同時,我很高興回答我知道的問題。【譯注:譯者也是一樣的】。接下來是,集群化(clustering)!不過我需要先把它弄懂再說。
注:關于RabbitMQ的知識我主要來自這些來源,推薦閱讀:
高級消息隊列協議(Advanced Message Queuing Protocol):協議規約0.8 版本
–完–