做者:黃歡html
原文連接:http://sadwxqezc.github.io/HuangHuanBlog/middleware/2018/11/25/RabbitMq.html?utm_source=tuicool&utm_medium=referralgit
出身:誕生於金融行業的消息隊列github
語言:Erlang安全
協議:AMQP(Advanced Message Queuing Protocol 高級消息隊列協議)服務器
關鍵詞:內存隊列,高可用架構
在上圖的模式下,交換器的類型爲Direct,僞代碼表示消息的生產和消費併發
消息生產運維
#消息發送方法 #messageBody 消息體 #exchangeName 交換器名稱 #routingKey 路由鍵 publishMsg(messageBody,exchangeName,routingKey){ ...... } #消息發送 publishMsg("This is a warning log","exchange","log.warning");
RoutingKey=log.warning,和隊列A與交換器的綁定一致,因此消息被路由到了隊列A上異步
消息消費分佈式
對於消息消費而言,消費者直接指定要消費的隊列便可,好比指定消費隊列A的數據。
須要注意的是,在消費者消費完成數據後,返回給RabbitMq ACK消息,RabbitMq會刪掉隊列中的該條信息。
在Exchange這個模塊上,RabbitMq主要支持了Direct,Fanout,Topic三種路由模式,RabbitMq在路由模式上下功夫,也說明了他在設計上想要知足多樣化的需求。
Direct和Fanout模式比較好理解,相似於單播和廣播模式,Topic模式比較有意思,它支持自定義匹配規則,按照規則把全部知足條件的消息路由到指定隊列,可以幫助開發者靈活應對各種需求。
RabbitMQ的消息默認是在內存裏的,實際上不光是消息,Exchange路由等信息實際都在內存中。內存的優勢是高性能,問題在於故障後沒法恢復。因此RabbitMQ也支持持久化的存儲,也就是寫磁盤。
要在RabbitMQ中持久化消息,要同時知足三個條件:
RabbitMQ持久化消息的方式是常見的寫日誌方式:
消息持久化的優缺點很明顯,擁有故障恢復能力的同時,也帶來了性能的急劇降低。同時,因爲RabbitMQ默認狀況下是沒有冗餘的,假設一個持久化節點崩潰,一致到該節點恢復前,消息和隊列都沒法恢復。
1.發後即忘
RabbitMQ默認發佈消息是不會返回任何結果給生產者的,因此存在發送過程當中丟失數據的風險
2.AMQP事務
AMQP事務保證RabbitMQ不只收到了消息,併成功將消息路由到了全部匹配的訂閱隊列,AMQP事務將使得生產者和RabbitMQ產生同步。
雖然事務使得生產者能夠肯定消息已經到達RabbitMQ中的對應隊列,可是卻會下降2~10倍的消息吞吐量。
3.發送方確認
開啓發送方確認模式後,消息會有一個惟一的ID,一旦消息被投遞給全部匹配的隊列後,會回調給發送方應用程序(包含消息的惟一ID),使得生產者知道消息已經安全到達隊列了。
若是消息和隊列是配置成了持久化,這個確認消息只會在隊列將消息寫入磁盤後纔會返回。若是RabbitMQ內部發生了錯誤致使這條消息丟失,那麼RabbitMQ會發送一條nack消息,固然我理解這個是不能保證的。
這種模式因爲不存在事務回滾,同時總體仍然是一個異步過程,因此更加輕量級,對服務器性能的影響很小。
通常的異步服務間,可能會用兩組隊列實現兩個服務模塊以前的異步通訊,有趣的是RabbitMQ就內建了這個功能。
RabbitMQ支持消息應答功能,每一個AMQP消息頭中有一個Reply_to字段,經過該字段指定消息返回到的隊列名稱(這是一個私有隊列)消息的生產者能夠監聽該字段對應的隊列。
RabbitMQ集羣的設計目標:
從實際結果看,RabbitMQ完成設計目標上並不十分出色,主要緣由在於默認的模式下,RabbitMQ的隊列實例子只存在在一個節點上(雖而後續也支持了鏡像隊列),既不能保證該節點崩潰的狀況下隊列還能夠繼續運行,也不能線性擴展該隊列的吞吐量。
RabbitMQ內部的元數據主要有:
雖然RabbitMQ的隊列實際只會在一個節點上,但元數據能夠存在各個節點上。舉個例子來講,當建立一個新的交換器時,RabbitMQ會把該信息同步到全部節點上,這個時候客戶端無論鏈接的那個RabbitMQ節點,均可以訪問到這個新的交換器,也就能找到交換器下的隊列。
如上圖所示,隊列A的實例實際只在一個RabbitMQ節點上,其它節點實際存儲的是隻想該隊列的指針。
爲何RabbitMQ不在各個節點間作複製了,《RabbitMQ實戰》給出了兩個緣由:
我理解成本這個緣由並不徹底成立,複製並不必定要複製到全部節點,好比一個隊列能夠只作兩個副本,複製帶來的內存成本能夠交給使用方來評估,畢竟在內存中沒有堆積的狀況下,實際上隊列是不會佔用多大內存的。
還有一點是RabbitMQ自己並無保證消息消費的有序性,因此實際上隊列被Partition到各個節點上,這樣才能真正達到線性擴容的目的(以RabbitMQ的現狀來講,單隊列實際是沒法擴容的,只有在業務層作切分)。
注:RabbitMQ集羣中的節點能夠是內存節點也能夠是磁盤節點,但要求至少有一個磁盤節點,這樣出現故障時才能恢復數據。
鏡像隊列架構
RabbitMQ本身也考慮到了咱們以前分析的單節點長時間故障沒法恢復的問題,因此RabbitMQ 2.6.0以後它也支持了鏡像隊列,換個說法也就是副本。
除了發送消息,全部的操做實際都在主拷貝上,從拷貝實際只是個冷備(默認的狀況下全部RabbitMQ節點上都會有鏡像隊列的拷貝),若是使用消息確認模式,RabbitMQ會在主拷貝和從拷貝都安全的接受到消息時才通知生產者。
從這個結構上來看,若是從拷貝的節點掛了,實際沒有任何影響,若是主拷貝掛了,那麼會有一個重新選主的過程,這也是鏡像隊列的優勢,除非全部節點都掛了,纔會致使消息丟失。從新選主後,RabbitMQ會給消費者一個消費者取消通知(Consumer Cancellation),讓消費者重連新的主拷貝。
鏡像隊列原理
1.RabbitMQ結構
BackingQueue由Q1,Q2,Delta,Q3,Q4五個子隊列構成,在Backing中,消息的生命週期有四個狀態:
這裏以持久化消息爲例(能夠看到非持久化消息的生命週期會簡單不少),從Q1到Q4,消息實際經歷了一個RAM->DISK->RAM這樣的過程,BackingQueue這麼設計的目的有點相似於Linux的Swap,當隊列負載很高時,經過將部分消息放到磁盤上來節省內存空間,當負載下降時,消息又從磁盤迴到內存中,讓整個隊列有很好的彈性。所以觸發消息流動的主要因素是:1.消息被消費;2.內存不足。
RabbitMQ會更具消息的傳輸速度來計算當前內存中容許保存的最大消息數量(Traget_RAM_Count),當:內存中保存的消息數量+等待ACK的消息數量>Target_RAM_Count 時,RabbitMQ纔會把消息寫到磁盤上,因此說雖然理論上消息會按照Q1->Q2->Delta->Q3->Q4的順序流動,可是並非每條消息都會經歷全部的子隊列以及對應的生命週期。
從RabbitMQ的Backing Queue結構來看,當內部不足時,消息要經歷多個生命週期,在Disk和RAM之間置換,者實際會下降RabbitMQ的處理性能(後續的流控就是關聯的解決方法)。
2.鏡像隊列結構
全部對鏡像隊列主拷貝的操做,都會經過Guarented Multicasting(GM)同步到各個Salve節點,Coodinator負責組播結果的確認。
GM是一種可靠的組播通訊協議,保證組組內的存活節點都收到消息。
GM的主播並非由Master節點來負責通知全部Slave的(目的是爲了不Master壓力過大,同時避免Master失效致使消息沒法最終Ack),RabbitMQ把一個鏡像隊列的全部節點組成一個鏈表,由主拷貝發起,由主拷貝最終確認通知到了全部的Slave,而中間由Slave接力的方式進行消息傳播。
從這個結構來看,消息完成整個鏡像隊列的同步耗時理論上是不低的,可是因爲RabbitMQ消息的消息確認自己是異步的模式,因此總體的吞吐量並不會受到太大影響。
當RabbitMQ出現內存(默認是0.4)或者磁盤資源達到閾值時,會觸發流控機制,阻塞Producer的Connection,讓生產者不能繼續發送消息,直到內存或者磁盤資源獲得釋放。
RabbitMQ基於Erlang/OTP開發,一個消息的生命週期中,會涉及多個進程間的轉發,這些Erlang進程之間不共享內存,每一個進程都有本身獨立的內存空間,若是沒有合適的流控機制,可能會致使某個進程佔用內存過大,致使OOM。所以,要保證各個進程佔用的內容在一個合理的範圍,RabbitMQ的流控採用了一種信用證機制(Credit),爲每一個進程維護了四類鍵值對:
如圖所示,A進程當前能夠發送給B的消息有100條,每發一次,值減1,直到爲0,A纔會被Block住。B消費消息後,會給A增長新的Credit,這樣A才能夠持續的發送消息。這裏只畫了兩個進程,多進程串聯的狀況下,這中影響也就是從底向上傳遞的。
想學習Java工程化、分佈式架構、高併發、高性能、深刻淺出、微服務架構、Spring,MyBatis,Netty源碼分析等技術能夠加羣:479499375,羣裏有阿里大牛直播講解技術,以及Java大型互聯網技術的視頻免費分享給你們,歡迎進羣一塊兒深刻交流學習。
注:本文基於的RabbitMQ材料可能較爲陳舊,新的RabbitMQ可能會有不一樣的功能特性
總體來看,RabbitMQ的功能比較豐富(惋惜沒有看到延遲,優先級等功能),更適用於偏實時的業務場景,與Kafka這樣的隊列定位上有明顯的區別。它自己應該是一個簡單健壯的組件,但若是要應用在一個大規模的分佈式系統中,實際仍是須要作一些外部的再次開發,以解決咱們前面提到的隊列存儲單點,流控等問題。直觀上看它的運維成本是會比較高的,須要使用方有必定的經驗。