rabbitmq 取消消息_认识RabbitMQ从这篇文章开始
關(guān)于RabbitMQ
出身:誕生于金融行業(yè)的消息隊列
語言:Erlang
協(xié)議:AMQP(Advanced Message Queuing Protocol 高級消息隊列協(xié)議)
關(guān)鍵詞:內(nèi)存隊列,高可用
一條消息
隊列結(jié)構(gòu)
- Producer/Consumer:生產(chǎn)者消費者
- Exchange:交換器,可以理解為隊列的路由邏輯,交換器主要有三種,圖中是Direct交換器
- Queue:隊列
- Binding:綁定關(guān)系,實際是交換器上映射隊列的規(guī)則
發(fā)送和消費一條消息
在上圖的模式下,交換器的類型為Direct,偽代碼表示消息的生產(chǎn)和消費
消息生產(chǎn)
#消息發(fā)送方法 #messageBody 消息體 #exchangeName 交換器名稱 #routingKey 路由鍵 publishMsg(messageBody,exchangeName,routingKey){ ...... } #消息發(fā)送 publishMsg("This is a warning log","exchange","log.warning");
RoutingKey=log.warning,和隊列A與交換器的綁定一致,所以消息被路由到了隊列A上
消息消費
對于消息消費而言,消費者直接指定要消費的隊列即可,比如指定消費隊列A的數(shù)據(jù)。
需要注意的是,在消費者消費完成數(shù)據(jù)后,返回給RabbitMq ACK消息,RabbitMq會刪掉隊列中的該條信息。
多種消息路由模式
在Exchange這個模塊上,RabbitMq主要支持了Direct,Fanout,Topic三種路由模式,RabbitMq在路由模式上下功夫,也說明了他在設(shè)計上想要滿足多樣化的需求。
Direct和Fanout模式比較好理解,類似于單播和廣播模式,Topic模式比較有意思,它支持自定義匹配規(guī)則,按照規(guī)則把所有滿足條件的消息路由到指定隊列,能夠幫助開發(fā)者靈活應(yīng)對各類需求。
消息的存儲
RabbitMQ的消息默認(rèn)是在內(nèi)存里的,實際上不光是消息,Exchange路由等信息實際都在內(nèi)存中。內(nèi)存的優(yōu)點是高性能,問題在于故障后無法恢復(fù)。所以RabbitMQ也支持持久化的存儲,也就是寫磁盤。
要在RabbitMQ中持久化消息,要同時滿足三個條件:
RabbitMQ持久化消息的方式是常見的寫日志方式:
消息持久化的優(yōu)缺點很明顯,擁有故障恢復(fù)能力的同時,也帶來了性能的急劇下降。同時,由于RabbitMQ默認(rèn)情況下是沒有冗余的,假設(shè)一個持久化節(jié)點崩潰,一致到該節(jié)點恢復(fù)前,消息和隊列都無法恢復(fù)。
消息投遞模式
1.發(fā)后即忘
RabbitMQ默認(rèn)發(fā)布消息是不會返回任何結(jié)果給生產(chǎn)者的,所以存在發(fā)送過程中丟失數(shù)據(jù)的風(fēng)險
2.AMQP事務(wù)
AMQP事務(wù)保證RabbitMQ不僅收到了消息,并成功將消息路由到了所有匹配的訂閱隊列,AMQP事務(wù)將使得生產(chǎn)者和RabbitMQ產(chǎn)生同步。
雖然事務(wù)使得生產(chǎn)者可以確定消息已經(jīng)到達RabbitMQ中的對應(yīng)隊列,但是卻會降低2~10倍的消息吞吐量。
3.發(fā)送方確認(rèn)
開啟發(fā)送方確認(rèn)模式后,消息會有一個唯一的ID,一旦消息被投遞給所有匹配的隊列后,會回調(diào)給發(fā)送方應(yīng)用程序(包含消息的唯一ID),使得生產(chǎn)者知道消息已經(jīng)安全到達隊列了。
如果消息和隊列是配置成了持久化,這個確認(rèn)消息只會在隊列將消息寫入磁盤后才會返回。如果RabbitMQ內(nèi)部發(fā)生了錯誤導(dǎo)致這條消息丟失,那么RabbitMQ會發(fā)送一條nack消息,當(dāng)然我理解這個是不能保證的。
這種模式由于不存在事務(wù)回滾,同時整體仍然是一個異步過程,所以更加輕量級,對服務(wù)器性能的影響很小。
RabbitMQ RPC
一般的異步服務(wù)間,可能會用兩組隊列實現(xiàn)兩個服務(wù)模塊之前的異步通信,有趣的是RabbitMQ就內(nèi)建了這個功能。
RabbitMQ支持消息應(yīng)答功能,每個AMQP消息頭中有一個Reply_to字段,通過該字段指定消息返回到的隊列名稱(這是一個私有隊列)消息的生產(chǎn)者可以監(jiān)聽該字段對應(yīng)的隊列。
RabbitMQ集群
RabbitMQ集群的設(shè)計目標(biāo):
從實際結(jié)果看,RabbitMQ完成設(shè)計目標(biāo)上并不十分出色,主要原因在于默認(rèn)的模式下,RabbitMQ的隊列實例子只存在在一個節(jié)點上(雖然后續(xù)也支持了鏡像隊列),既不能保證該節(jié)點崩潰的情況下隊列還可以繼續(xù)運行,也不能線性擴展該隊列的吞吐量。
集群結(jié)構(gòu)
RabbitMQ內(nèi)部的元數(shù)據(jù)主要有:
雖然RabbitMQ的隊列實際只會在一個節(jié)點上,但元數(shù)據(jù)可以存在各個節(jié)點上。舉個例子來說,當(dāng)創(chuàng)建一個新的交換器時,RabbitMQ會把該信息同步到所有節(jié)點上,這個時候客戶端不管連接的那個RabbitMQ節(jié)點,都可以訪問到這個新的交換器,也就能找到交換器下的隊列。
如上圖所示,隊列A的實例實際只在一個RabbitMQ節(jié)點上,其它節(jié)點實際存儲的是只想該隊列的指針。
為什么RabbitMQ不在各個節(jié)點間做復(fù)制了,《RabbitMQ實戰(zhàn)》給出了兩個原因:
我理解成本這個原因并不完全成立,復(fù)制并不一定要復(fù)制到所有節(jié)點,比如一個隊列可以只做兩個副本,復(fù)制帶來的內(nèi)存成本可以交給使用方來評估,畢竟在內(nèi)存中沒有堆積的情況下,實際上隊列是不會占用多大內(nèi)存的。
還有一點是RabbitMQ本身并沒有保證消息消費的有序性,所以實際上隊列被Partition到各個節(jié)點上,這樣才能真正達到線性擴容的目的(以RabbitMQ的現(xiàn)狀來說,單隊列實際是無法擴容的,只有在業(yè)務(wù)層做切分)。
注:RabbitMQ集群中的節(jié)點可以是內(nèi)存節(jié)點也可以是磁盤節(jié)點,但要求至少有一個磁盤節(jié)點,這樣出現(xiàn)故障時才能恢復(fù)數(shù)據(jù)。
鏡像隊列
鏡像隊列架構(gòu)
RabbitMQ自己也考慮到了我們之前分析的單節(jié)點長時間故障無法恢復(fù)的問題,所以RabbitMQ 2.6.0之后它也支持了鏡像隊列,換個說法也就是副本。
除了發(fā)送消息,所有的操作實際都在主拷貝上,從拷貝實際只是個冷備(默認(rèn)的情況下所有RabbitMQ節(jié)點上都會有鏡像隊列的拷貝),如果使用消息確認(rèn)模式,RabbitMQ會在主拷貝和從拷貝都安全的接受到消息時才通知生產(chǎn)者。
從這個結(jié)構(gòu)上來看,如果從拷貝的節(jié)點掛了,實際沒有任何影響,如果主拷貝掛了,那么會有一個從新選主的過程,這也是鏡像隊列的優(yōu)點,除非所有節(jié)點都掛了,才會導(dǎo)致消息丟失。重新選主后,RabbitMQ會給消費者一個消費者取消通知(Consumer Cancellation),讓消費者重連新的主拷貝。
鏡像隊列原理
1.RabbitMQ結(jié)構(gòu)
- AMQPQueue:負(fù)責(zé)AMQP協(xié)議相關(guān)的消息處理,包括接收消息,投遞消息,Confirm消息等
- BackingQueue:提供AMQQueue調(diào)用的接口,完成消息的存儲和持久化工作
BackingQueue由Q1,Q2,Delta,Q3,Q4五個子隊列構(gòu)成,在Backing中,消息的生命周期有四個狀態(tài):
這里以持久化消息為例(可以看到非持久化消息的生命周期會簡單很多),從Q1到Q4,消息實際經(jīng)歷了一個RAM->DISK->RAM這樣的過程,BackingQueue這么設(shè)計的目的有點類似于Linux的Swap,當(dāng)隊列負(fù)載很高時,通過將部分消息放到磁盤上來節(jié)省內(nèi)存空間,當(dāng)負(fù)載降低時,消息又從磁盤回到內(nèi)存中,讓整個隊列有很好的彈性。因此觸發(fā)消息流動的主要因素是:1.消息被消費;2.內(nèi)存不足。
RabbitMQ會更具消息的傳輸速度來計算當(dāng)前內(nèi)存中允許保存的最大消息數(shù)量(Traget_RAM_Count),當(dāng):內(nèi)存中保存的消息數(shù)量+等待ACK的消息數(shù)量>Target_RAM_Count 時,RabbitMQ才會把消息寫到磁盤上,所以說雖然理論上消息會按照Q1->Q2->Delta->Q3->Q4的順序流動,但是并不是每條消息都會經(jīng)歷所有的子隊列以及對應(yīng)的生命周期。
從RabbitMQ的Backing Queue結(jié)構(gòu)來看,當(dāng)內(nèi)部不足時,消息要經(jīng)歷多個生命周期,在Disk和RAM之間置換,者實際會降低RabbitMQ的處理性能(后續(xù)的流控就是關(guān)聯(lián)的解決方法)。
2.鏡像隊列結(jié)構(gòu)
所有對鏡像隊列主拷貝的操作,都會通過Guarented Multicasting(GM)同步到各個Salve節(jié)點,Coodinator負(fù)責(zé)組播結(jié)果的確認(rèn)。
GM是一種可靠的組播通信協(xié)議,保證組組內(nèi)的存活節(jié)點都收到消息。
GM的主播并不是由Master節(jié)點來負(fù)責(zé)通知所有Slave的(目的是為了避免Master壓力過大,同時避免Master失效導(dǎo)致消息無法最終Ack),RabbitMQ把一個鏡像隊列的所有節(jié)點組成一個鏈表,由主拷貝發(fā)起,由主拷貝最終確認(rèn)通知到了所有的Slave,而中間由Slave接力的方式進行消息傳播。
從這個結(jié)構(gòu)來看,消息完成整個鏡像隊列的同步耗時理論上是不低的,但是由于RabbitMQ消息的消息確認(rèn)本身是異步的模式,所以整體的吞吐量并不會受到太大影響。
流控
當(dāng)RabbitMQ出現(xiàn)內(nèi)存(默認(rèn)是0.4)或者磁盤資源達到閾值時,會觸發(fā)流控機制,阻塞Producer的Connection,讓生產(chǎn)者不能繼續(xù)發(fā)送消息,直到內(nèi)存或者磁盤資源得到釋放。
RabbitMQ基于Erlang/OTP開發(fā),一個消息的生命周期中,會涉及多個進程間的轉(zhuǎn)發(fā),這些Erlang進程之間不共享內(nèi)存,每個進程都有自己獨立的內(nèi)存空間,如果沒有合適的流控機制,可能會導(dǎo)致某個進程占用內(nèi)存過大,導(dǎo)致OOM。因此,要保證各個進程占用的內(nèi)容在一個合理的范圍,RabbitMQ的流控采用了一種信用證機制(Credit),為每個進程維護了四類鍵值對:
如圖所示,A進程當(dāng)前可以發(fā)送給B的消息有100條,每發(fā)一次,值減1,直到為0,A才會被Block住。B消費消息后,會給A增加新的Credit,這樣A才可以持續(xù)的發(fā)送消息。這里只畫了兩個進程,多進程串聯(lián)的情況下,這中影響也就是從底向上傳遞的。
總結(jié)
整體來看,RabbitMQ的功能比較豐富(可惜沒有看到延遲,優(yōu)先級等功能),更適用于偏實時的業(yè)務(wù)場景,與Kafka這樣的隊列定位上有明顯的區(qū)別。它本身應(yīng)該是一個簡單健壯的組件,但如果要應(yīng)用在一個大規(guī)模的分布式系統(tǒng)中,實際還是需要做一些外部的再次開發(fā),以解決我們前面提到的隊列存儲單點,流控等問題。直觀上看它的運維成本是會比較高的,需要使用方有一定的經(jīng)驗。
添加Java高級架構(gòu)交流群 378461078
關(guān)注微信公眾號“托尼的技術(shù)成長之路”
總結(jié)
以上是生活随笔為你收集整理的rabbitmq 取消消息_认识RabbitMQ从这篇文章开始的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小米再出手!深圳这家精密传动科技公司是什
- 下一篇: 第二代骁龙7+的AI再升级:越级体验不是