RabbitMQ是一個開源的
AMQP
實現,服務器端用Erlang
語言編寫,支持多種客戶端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用於在分佈式系統
中存儲轉發消息,在易用性、擴展性、高可用性等方面表現不俗。html
AMQP,即Advanced Message Queuing Protocol,高級消息隊列協議,是
應用層協議
的一個開放標準,爲面向消息的中間件設計。消息中間件主要用於組件之間的解耦,消息的發送者無需知道消息使用者的存在,反之亦然。
它可使對應的客戶端(client)與對應的消息中間件(broker)進行交互。消息中間件從發佈者(publisher)那裏收到消息(發佈消息的應用,也稱爲producer),而後將他們轉發給消費者(consumers,處理消息的應用)。因爲AMQP是一個網絡協議,因此發佈者、消費者以及消息中間件能夠部署到不一樣的物理機器上面。node
雖然在同步消息通信的世界裏有不少公開標準(如 COBAR的 IIOP ,或者是 SOAP 等),可是在異步消息處理中卻不是這樣,只有大企業有一些商業實現(如微軟的 MSMQ ,IBM 的 Websphere MQ 等),所以,在 2006 年的 6 月,Cisco 、Redhat、iMatix 等聯合制定了 AMQP 的公開標準。mysql
RabbitMQ是由RabbitMQ Technologies Ltd開發而且提供商業支持的。該公司在2010年4月被SpringSource(VMWare的一個部門)收購。在2013年5月被併入Pivotal。其實VMWare,Pivotal和EMC本質上是一家的。不一樣的是VMWare是獨立上市子公司,而Pivotal是整合了EMC的某些資源,如今並無上市。git
消息中間件是一種由消息傳送機制或消息隊列模式組成的中間件技術,利用高效可靠的消息傳遞機制進行平臺無關的數據交流,並基於數據通訊來進行分佈式系統的集成。github
是一個Key-Value的NoSQL數據庫,開發維護很活躍,雖然它是一個Key-Value數據庫存儲系統,但它自己支持MQ功能,因此完成能夠當作一個輕量級的隊列服務來使用。對於RabbitMQ和Redis的入隊和出隊操做,各執行100萬次,每10萬次記錄一次執行時間。測試數據分爲128Bytes、512Bytes、1K和10K四個不一樣大小的數據。實驗代表:入隊時,當數據比較小時,Redis的性能要高於RabbitMQ,而如否數據大小超過了10K,Redis則慢的沒法忍受;出隊時,不管數據大小,Redis都表現出很是好的性能,而RabbitMQ的出隊性能則遠低於Redis。redis
持久化消息隊列(簡稱mcq)是一個輕量級的消息隊列,特性以下:算法
這是微軟的產品力惟一被認爲有價值的東西。若是MSMQ能證實能夠應對這種任務,他們將選擇使用它。sql
ZeroMQ是一個很是輕量級的消息系統,號稱最快的消息隊列系統,專門爲高吞吐量/低延遲的場景開發,在金融界的應用中常常能夠發現它。數據庫
Kafka(能將消息分散到不一樣的節點上)是LinkedIn於2010年12月開發並開源的一個分佈式MQ系統,如今是Apache的一個孵化項目,是一個高性能跨語言分佈式Publish/Subscribe消息隊列系統,而Jafka是在Kafka之上孵化而來的,即Kafka的一個升級版。具備如下特性:apache
ActiveMQ居於(RabbitMQ&ZeroMQ)之間,相似於ZemoMQ,它能夠部署於代理模式和P2P模式。
RabbitMQ是使用Erlang編寫的一個開源消息隊列,自己支持不少的協議:AMQP, XMPP, SMTP, STONP,也正是如此,使的它變的很是重量級,更適合於企業級的開發。
最終,上述同類產品:
下面是網友的對四種消息隊列的具體測試結果,顯示的是發送和接受的每秒鐘的消息數,整個過程共產生1百萬條1K的消息,測試的執行是在WIndows Vista上進行的。(有待本身驗證
)
如上圖所見,ZeroMQ和其餘的不是一個級別,它的性能驚人的高。結論很清楚:若是但願一個應用程序發送消息越快越好,選擇ZeroMQ,而且在你不太在乎偶然會丟失某些消息的狀況下更有價值。
或者說AMPQ爲什麼會出現,它的應用場景又是什麼?
消息服務擅長於解決多系統、異構系統間的數據交換(消息通知/通信)問題,你也能夠把它用於系統間服務的相互調用(RPC)經過使用消息隊列,咱們能夠異步處理請求,從而緩解系統的壓力。
對於一個大型的軟件系統來講,它會有不少的組件或者說模塊或者說子系統或者(Subsystem or Component or Submodule)。那麼這些模塊的如何通訊?這和傳統的IPC有很大的區別。傳統的IPC不少都是在單一系統上的,模塊耦合性很大,不適合擴展(Scalability);若是使用socket那麼不一樣的模塊的確能夠部署到不一樣的機器上,可是仍是有不少問題須要解決。好比:
AMDQ協議解決了以上的問題,而RabbitMQ實現了AMQP。
broker server
,它不是運送食物的卡車,而是一種傳輸服務。原話是RabbitMQ isn’t a food truck, it’s a delivery service. 他的角色就是維護一條從Producer到Consumer的路線,保證數據可以按照指定的方式進行傳輸。可是這個保證也不是100%的保證,可是對於普通的應用來講這已經足夠了。固然對於商業系統來講,能夠再作一層數據一致性的guard,就能夠完全保證系統的一致性了。Producer
,數據的發送方。Create messages and Publish (Send) them to a broker server (RabbitMQ)。一個Message有兩個部分:Payload(有效載荷)和Label(標籤)。Payload顧名思義就是傳輸的數據,Label是Exchange的名字或者說是一個tag,它描述了payload,並且RabbitMQ也是經過這個label來決定把這個Message發給哪一個Consumer。AMQP僅僅描述了label,而RabbitMQ決定了如何使用這個label的規則。Consumer
,數據的接收方。Consumers attach to a broker server (RabbitMQ) and subscribe to a queue。把queue比做是一個有名字的郵箱。當有Message到達某個郵箱後,RabbitMQ把它發送給它的某個訂閱者即Consumer。固然可能會把同一個Message發送給不少的Consumer。在這個Message中,只有payload,label已經被刪掉了。對於Consumer來講,它是不知道誰發送的這個信息的。就是協議自己不支持。可是固然了若是Producer發送的payload包含了Producer的信息就另當別論了。對於一個數據從Producer到Consumer的正確傳遞,還有三個概念須要明確:exchanges, queues and bindings。
還有幾個概念是上述圖中沒有標明的,那就是Connection(鏈接),Channel(通道,頻道),Vhost(虛擬主機)。
那麼,爲何使用Channel,而不是直接使用TCP鏈接?
對於OS來講,創建和關閉TCP鏈接是有代價的,頻繁的創建關閉TCP鏈接對於系統的性能有很大的影響,並且TCP的鏈接數也有限制,這也限制了系統處理高併發的能力。可是,在TCP鏈接中創建Channel是沒有上述代價的。對於Producer或者Consumer來講,能夠併發的使用多個Channel進行Publish或者Receive。
有實驗代表,1s的數據能夠Publish10K的數據包。固然對於不一樣的硬件環境,不一樣的數據包大小這個數據確定不同,可是我只想說明,對於普通的Consumer或者Producer來講,這已經足夠了。若是不夠用,你考慮的應該是如何細化split你的設計。
Exchange接收到消息後,就根據消息的key和已經設置的Binding,進行消息路由,將消息投遞到一個或多個隊列裏。有三種類型的Exchanges:direct,fanout,topic,每一個實現了不一樣的路由算法(routing algorithm):
更多消息隊列相關設計介紹請參考:
Consumer和Procuder均可以經過 queue.declare 建立queue。對於某個Channel來講,Consumer不能declare一個queue,卻訂閱其餘的queue。固然也能夠建立私有的queue。這樣只有app自己纔可使用這個queue。queue也能夠自動刪除,被標爲auto-delete的queue在最後一個Consumer unsubscribe後就會被自動刪除。那麼若是是建立一個已經存在的queue呢?那麼不會有任何的影響。須要注意的是沒有任何的影響,也就是說第二次建立若是參數和第一次不同,那麼該操做雖然成功,可是queue的屬性並不會被修改。
那麼誰應該負責建立這個queue呢?是Consumer,仍是Producer?
若是queue不存在,固然Consumer不會獲得任何的Message。可是若是queue不存在,那麼Producer Publish的Message會被丟棄。因此,仍是爲了數據不丟失,Consumer和Producer都try to create the queue!反正無論怎麼樣,這個接口都不會出問題。
Queue對load balance的處理是完美的。對於多個Consumer來講,RabbitMQ 使用循環的方式(round-robin)的方式均衡的發送給不一樣的Consumer。
默認狀況下,若是Message 已經被某個Consumer正確的接收到了,那麼該Message就會被從queue中移除。固然也可讓同一個Message發送到不少的Consumer。
若是一個queue沒被任何的Consumer Subscribe(訂閱),那麼,若是這個queue有數據到達,那麼這個數據會被cache,不會被丟棄。當有Consumer時,這個數據會被當即發送到這個Consumer,這個數據被Consumer正確收到時,這個數據就被從queue中刪除。
那麼什麼是正確收到呢?經過ack。
每一個Message都要被acknowledged(確認,ack)。咱們能夠顯示的在程序中去ack(Consumer的basic.ack),也能夠自動的ack(訂閱Queue時指定auto_ack爲true)。
若是有數據沒有被ack,那麼RabbitMQ Server會把這個信息發送到下一個Consumer。
若是這個app有bug,忘記了ack,那麼RabbitMQ Server不會再發送數據給它,由於Server認爲這個Consumer處理能力有限。
並且ack的機制能夠起到限流的做用(Benefit to throttling):在Consumer處理完成數據後發送ack,甚至在額外的延時後發送ack,將有效的balance Consumer的load。
固然對於實際的例子,好比咱們可能會對某些數據進行merge,好比merge 4s內的數據,而後sleep 4s後再獲取數據。特別是在監聽系統的state,咱們不但願全部的state實時的傳遞上去,而是但願有必定的延時。這樣能夠減小某些IO,並且終端用戶也不會感受到。
沒有正確響應呢?
若是Consumer接收了一個消息就尚未發送ack就與RabbitMQ斷開了,RabbitMQ會認爲這條消息沒有投遞成功會從新投遞到別的Consumer。
若是Consumer自己邏輯有問題沒有發送ack的處理,RabbitMQ不會再向該Consumer發送消息。RabbitMQ會認爲這個Consumer尚未處理完上一條消息,沒有能力繼續接收新消息。
咱們能夠善加利用這一機制,若是須要處理過程是至關複雜的,應用程序能夠延遲發送ack直處處理完成爲止。這能夠有效控制應用程序這邊的負載,不致於被大量消息衝擊。
因爲要拒絕消息,因此ack響應消息尚未發出,這裏拒絕消息能夠有兩種選擇:
Consumer直接斷開RabbitMQ這樣RabbitMQ將把這條消息從新排隊,交由其它Consumer處理。這個方法在RabbitMQ各版本都支持,這樣作的壞處就是鏈接斷開增長了RabbitMQ的額外負擔,特別是consumer出現異常每條消息都沒法正常處理的時候。
RabbitMQ 2.0.0可使用 basic.reject 命令,收到該命令RabbitMQ會從新投遞到其它的Consumer。若是設置requeue爲false,RabbitMQ會直接將消息從queue中移除。
其實還有一種選擇就是直接忽略這條消息併發送ACK,當你明確知道這條消息是異常的不會有Consumer能處理,能夠這樣作拋棄異常數據。
爲何要發送basic.reject消息而不是ACK?RabbitMQ後面的版本可能會引入」dead letter」隊列,若是想利用dead letter作點文章就使用basic.reject並設置requeue爲false。
RabbitMQ支持消息的持久化,也就是數據寫在磁盤上,爲了數據安全考慮,大多數用戶都會選擇持久化。消息隊列持久化包括3個部分:
若Exchange和Queue都是持久化的,那麼它們之間的Binding也是持久化的;而Exchange和Queue二者之間有一個持久化,一個非持久化,就不容許創建綁定。
Consumer從durable queue中取回一條消息以後併發回了ack消息,RabbitMQ就會將其標記,方便後續垃圾回收。若是一條持久化的消息沒有被consumer取走,RabbitMQ重啓以後會自動重建exchange和queue(以及bingding關係),消息經過持久化日誌重建再次進入對應的queues,exchanges。
因爲RabbitMQ是用erlang開發的,RabbitMQ徹底依賴erlang的Cluster,由於erlang天生就是一門分佈式語言,集羣很是方便,但其自己並不支持負載均衡。Erlang的集羣中各節點是經由過程一個magic cookie來實現的,這個cookie存放在
$home/.erlang.cookie
中(像個人root用戶安裝的就是放在個人root/.erlang.cookie中),文件是400的權限。因此必須保證各節點cookie內容一致,否則節點之間就沒法通訊。
Rabbitmq集羣大概分爲二種方式:
普通模式:默認的集羣模式。
對於Queue來講,消息實體只存在於其中一個節點,A、B兩個節點僅有相同的元數據,即隊列結構,但隊列的元數據僅保存有一份,即建立該隊列的rabbitmq節點(A節點),當A節點宕機,你能夠去其B節點查看,./rabbitmqctl list_queues 發現該隊列已經丟失,但聲明的exchange還存在。
當消息進入A節點的Queue中後,consumer從B節點拉取時,RabbitMQ會臨時在A、B間進行消息傳輸,把A中的消息實體取出並通過B發送給consumer,因此consumer應平均鏈接每個節點,從中取消息。
該模式存在一個問題就是當A節點故障後,B節點沒法取到A節點中還未消費的消息實體。若是作了隊列持久化或消息持久化,那麼得等A節點恢復,而後纔可被消費,而且在A節點恢復以前其它節點不能再建立A節點已經建立過的持久隊列;若是沒有持久化的話,消息就會失丟。
這種模式更適合非持久化隊列,只有該隊列是非持久的,客戶端才能從新鏈接到集羣裏的其餘節點,並從新建立隊列。假如該隊列是持久化的,那麼惟一辦法是將故障節點恢復起來。
鏡像模式:把須要的隊列作成鏡像隊列,存在於多個節點。
該模式解決了普通模式的問題,其實質不一樣之處在於,消息實體會主動在鏡像節點間同步,而不是在consumer取數據時臨時拉取。
該模式帶來的反作用也很明顯,除了下降系統性能外,若是鏡像隊列數量過多,加之大量的消息進入,集羣內部的網絡帶寬將會被這種同步通信大大消耗掉。
因此在對可靠性要求較高的場合中適用,一個隊列想作成鏡像隊列,須要先設置policy,而後客戶端建立隊列的時候,rabbitmq集羣根據「隊列名稱」自動設置是普通集羣模式或鏡像隊列。具體以下:
隊列經過策略來使能鏡像。策略能在任什麼時候刻改變,rabbitmq隊列也近可能的將隊列隨着策略變化而變化;非鏡像隊列和鏡像隊列之間是有區別的,前者缺少額外的鏡像基礎設施,沒有任何slave,所以會運行得更快。
爲了使隊列稱爲鏡像隊列,你將會建立一個策略來匹配隊列,設置策略有兩個鍵「ha-mode和 ha-params(可選)」。ha-params根據ha-mode設置不一樣的值,下面表格說明這些key的選項。
爲何RabbitMQ不將隊列複製到集羣裏每一個節點呢?這與它的集羣的設計本意相沖突,集羣的設計目的就是增長更多節點時,能線性的增長性能(CPU、內存)和容量(內存、磁盤)。理由以下:
固然RabbitMQ新版本集羣也支持隊列複製(有個選項能夠配置)。好比在有五個節點的集羣裏,能夠指定某個隊列的內容在2個節點上進行存儲,從而在性能與高可用性之間取得一個平衡(應該就是指鏡像模式)。
RabbitMQ的集羣節點包括內存節點、磁盤節點。顧名思義內存節點就是將全部數據放在內存,磁盤節點將數據放在磁盤。不過,若是在投遞消息時,打開了消息的持久化,那麼即便是內存節點,數據仍是安全的放在磁盤。
一個rabbitmq集 羣中能夠共享 user,vhost,queue,exchange等,全部的數據和狀態都是必須在全部節點上覆制的,一個例外是,那些當前只屬於建立它的節點的消息隊列,儘管它們可見且可被全部節點讀取。rabbitmq節點能夠動態的加入到集羣中,一個節點它能夠加入到集羣中,也能夠從集羣環集羣會進行一個基本的負載均衡。
集羣中有兩種節點:
內存節點雖然不寫入磁盤,可是它執行比磁盤節點要好。集羣中,只須要一個磁盤節點來保存狀態 就足夠了若是集羣中只有內存節點,那麼不能中止它們,不然全部的狀態,消息等都會丟失。
參考:
RabbitMQ消息隊列(一): Detailed Introduction 詳細介紹
http://blog.csdn.net/butcher8/article/details/44274731(以上內容轉自此篇文章)