一、RabbitMQ如何保證消息不丟失?
這是面試時最喜歡問的問題宙刘,其實這是個所有MQ的一個共性的問題轨功,大致的解決思路也是差不多的,但是針對不同的MQ產(chǎn)品會有不同的解決方案炊苫。而RabbitMQ設(shè)計之處就是針對企業(yè)內(nèi)部系統(tǒng)之間進行調(diào)用設(shè)計的裁厅,所以他的消息可靠性是比較高的。
注意:千萬不要再回答 手動確認了侨艾。
1.哪些環(huán)節(jié)會有丟消息的可能执虹?
我們考慮一個通用的MQ場景:
其中,1唠梨,2袋励,4三個場景都是跨網(wǎng)絡(luò)的,而跨網(wǎng)絡(luò)就肯定會有丟消息的可能当叭。
然后關(guān)于3這個環(huán)節(jié)茬故,通常MQ存盤時都會先寫入操作系統(tǒng)的緩存page cache中,然后再由操作系統(tǒng)異步的將消息寫入硬盤蚁鳖。這個中間有個時間差磺芭,就可能會造成消息丟失。如果服務(wù)掛了才睹,緩存中還沒有來得及寫入硬盤的消息就會丟失徘跪。這也是任何用戶態(tài)的應(yīng)用程序無法避免的。
對于任何MQ產(chǎn)品琅攘,都應(yīng)該從這四個方面來考慮數(shù)據(jù)的安全性垮庐。那我們看看用RabbitMQ時要如何解決這個問題。
2.RabbitMQ消息零丟失方案
1)生產(chǎn)者保證消息正確發(fā)送到RabbitMQ
對于單個數(shù)據(jù)坞琴,可以使用生產(chǎn)者確認機制哨查。通過多次確認的方式,保證生產(chǎn)者的消息能夠正確的發(fā)送到RabbitMQ中剧辐。
RabbitMQ的生產(chǎn)者確認機制分為同步確認和異步確認寒亥。同步確認主要是通過在生產(chǎn)者端使用Channel.waitForConfirmsOrDie()指定一個等待確認的完成時間。異步確認機制則是通過channel.addConfirmListener(ConfirmCallback var1, ConfirmCallback var2)在生產(chǎn)者端注入兩個回調(diào)確認函數(shù)荧关。第一個函數(shù)是在生產(chǎn)者發(fā)送消息時調(diào)用溉奕,第二個函數(shù)則是生產(chǎn)者收到Broker的消息確認請求時調(diào)用。兩個函數(shù)需要通過sequenceNumber自行完成消息的前后對應(yīng)忍啤。sequenceNumber的生成方式需要通過channel的序列獲取加勤。int sequenceNumber = channel.getNextPublishSeqNo();
在RabbitMQ中仙辟,另外還有一種手動事務(wù)的方式,可以保證消息正確發(fā)送鳄梅。
手動事務(wù)機制主要有幾個關(guān)鍵的方法: channel.txSelect() 開啟事務(wù)叠国; channel.txCommit() 提交事務(wù); channel.txRollback() 回滾事務(wù)戴尸; 用這幾個方法來進行事務(wù)管理粟焊。但是這種方式需要手動控制事務(wù)邏輯,并且手動事務(wù)會對channel產(chǎn)生阻塞孙蒙,造成吞吐量下降项棠。
2)RabbitMQ消息存盤不丟消息
這個在RabbitMQ中比較好處理,對于Classic經(jīng)典隊列马篮,直接將隊列聲明成為持久化隊列即可沾乘。而新增的Quorum隊列和Stream隊列,都是明顯的持久化隊列浑测,能更好的保證服務(wù)端消息不會丟失翅阵。
3)RabbitMQ 主從消息同步時不丟消息
這涉及到RabbitMQ的集群架構(gòu)。首先他的普通集群模式迁央,消息是分散存儲的掷匠,不會主動進行消息同步了,是有可能丟失消息的岖圈。而鏡像模式集群讹语,數(shù)據(jù)會主動在集群各個節(jié)點當中同步,這時丟失消息的概率不會太高蜂科。
另外顽决,啟用Federation聯(lián)邦機制,給包含重要消息的隊列建立一個遠端備份导匣,也是一個不錯的選擇才菠。
4)RabbitMQ消費者不丟失消息
RabbitMQ在消費消息時可以指定是自動應(yīng)答,還是手動應(yīng)答贡定。如果是自動應(yīng)答模式赋访,消費者會在完成業(yè)務(wù)處理后自動進行應(yīng)答,而如果消費者的業(yè)務(wù)邏輯拋出異常缓待,RabbitMQ會將消息進行重試蚓耽,這樣是不會丟失消息的,但是有可能會造成消息一直重復(fù)消費旋炒。
將RabbitMQ的應(yīng)答模式設(shè)定為手動應(yīng)答可以提高消息消費的可靠性步悠。
另外這個應(yīng)答模式在SpringBoot集成案例中,也可以在配置文件中通過屬性spring.rabbitmq.listener.simple.acknowledge-mode 進行指定瘫镇《κ蓿可以設(shè)定為 AUTO 自動應(yīng)答芹壕; MANUAL 手動應(yīng)答;NONE 不應(yīng)答接奈; 其中這個NONE不應(yīng)答,就是不啟動應(yīng)答機制通孽,RabbitMQ只管往消費者推送消息后序宦,就不再重復(fù)推送消息了,相當于RocketMQ的sendoneway背苦, 這樣效率更高互捌,但是顯然會有丟消息的可能。
最后行剂,任何用戶態(tài)的應(yīng)用程序都無法保證絕對的數(shù)據(jù)安全秕噪,所以,備份與恢復(fù)的方案也需要考慮到厚宰。
二腌巾、如何保證消息冪等?
RabbitMQ的自動重試功能:當消費者消費消息處理業(yè)務(wù)邏輯時铲觉,如果拋出異常澈蝙,或者不向RabbitMQ返回響應(yīng),默認情況下撵幽,RabbitMQ會無限次數(shù)的重復(fù)進行消息消費灯荧。
處理冪等問題,首先要設(shè)定RabbitMQ的重試次數(shù)盐杂。在SpringBoot集成RabbitMQ時逗载,可以在配置文件中指定spring.rabbitmq.listener.simple.retry開頭的一系列屬性,來制定重試策略链烈。
然后厉斟,需要在業(yè)務(wù)上處理冪等問題。
處理冪等問題的關(guān)鍵是要給每個消息一個唯一的標識测垛。
在SpringBoot框架集成RabbitMQ后捏膨,可以給每個消息指定一個全局唯一的MessageID,在消費者端針對MessageID做冪等性判斷食侮。關(guān)鍵代碼:
要注意下這里用的message要是org.springframework.amqp.core.Message?
在原生API當中号涯,也是支持MessageId的。當然锯七,在實際工作中链快,最好還是能夠添加一個具有業(yè)務(wù)意義的數(shù)據(jù)作為唯一鍵會更好,這樣能更好的防止重復(fù)消費問題對業(yè)務(wù)的影響眉尸。比如域蜗,針對訂單消息巨双,那就用訂單ID來做唯一鍵。在RabbitMQ中霉祸,消息的頭部就是一個很好的攜帶數(shù)據(jù)的地方筑累。
三、如何保證消息的順序丝蹭?
某些場景下慢宗,需要保證消息的消費順序,例如一個下單過程奔穿,需要先完成扣款镜沽,然后扣減庫存,然后通知快遞發(fā)貨贱田,這個順序不能亂缅茉。如果每個步驟都通過消息進行異步通知的話,這一組消息就必須保證他們的消費順序是一致的男摧。
在RabbitMQ當中蔬墩,針對消息順序的設(shè)計其實是比較弱的。唯一比較好的策略就是 單隊列+單消息推送耗拓。即一組有序消息筹我,只發(fā)到一個隊列中,利用隊列的FIFO特性保證消息在隊列內(nèi)順序不會亂帆离。但是蔬蕊,顯然,這是以極度消耗性能作為代價的哥谷,在實際適應(yīng)過程中岸夯,應(yīng)該盡量避免這種場景。
然后在消費者進行消費時们妥,保證只有一個消費者猜扮,同時指定prefetch屬性為1,即每次RabbitMQ都只往客戶端推送一個消息监婶。像這樣:
而在多隊列情況下旅赢,如何保證消息的順序性,目前使用RabbitMQ的話惑惶,還沒有比較好的解決方案煮盼。在使用時,應(yīng)該盡量避免這種情況带污。
四僵控、關(guān)于RabbitMQ的數(shù)據(jù)堆積問題
RabbitMQ一直以來都有一個缺點,就是對于消息堆積問題的處理不好鱼冀。當RabbitMQ中有大量消息堆積時报破,整體性能會嚴重下降悠就。而目前新推出的Quorum隊列以及Stream隊列,目的就在于解決這個核心問題充易。但是這兩種隊列的穩(wěn)定性和周邊生態(tài)都還不夠完善梗脾,因此,在使用RabbitMQ時盹靴,還是要非常注意消息堆積的問題藐唠。盡量讓消息的消費速度和生產(chǎn)速度保持一致。
而如果確實出現(xiàn)了消息堆積比較嚴重的場景鹉究,就需要從數(shù)據(jù)流轉(zhuǎn)的各個環(huán)節(jié)綜合考慮,設(shè)計適合的解決方案踪宠。
1)首先在消息生產(chǎn)者端:
對于生產(chǎn)者端自赔,最明顯的方式自然是降低消息生產(chǎn)的速度。但是柳琢,生產(chǎn)者端產(chǎn)生消息的速度通常是跟業(yè)務(wù)息息相關(guān)的绍妨,一般情況下不太好直接優(yōu)化。但是可以選擇盡量多采用批量消息的方式柬脸,降低IO頻率他去。
2)然后在RabbitMQ服務(wù)端:
從前面的分享中也能看出,RabbitMQ本身其實也在著力于提高服務(wù)端的消息堆積能力倒堕。對于消息堆積嚴重的隊列灾测,可以預(yù)先添加懶加載機制,或者創(chuàng)建Sharding分片隊列垦巴,這些措施都有助于優(yōu)化服務(wù)端的消息堆積能力媳搪。另外,嘗試使用Stream隊列骤宣,也能很好的提高服務(wù)端的消息堆積能力秦爆。
3)接下來在消息消費者端:
要提升消費速度最直接的方式,就是增加消費者數(shù)量了憔披。尤其當消費端的服務(wù)出現(xiàn)問題等限,已經(jīng)有大量消息堆積時。這時芬膝,可以盡量多的申請機器望门,部署消費端應(yīng)用锰霜,爭取在最短的時間內(nèi)消費掉積壓的消息怒允。但是這種方式需要注意對其他組件的性能壓力锈遥。
對于單個消費者端勘畔,可以通過配置提升消費者端的吞吐量。例如:
靈活配置這幾個參數(shù)丽惶,能夠在一定程度上調(diào)整每個消費者實例的吞吐量炫七,減少消息堆積數(shù)量钾唬。
當確實遇到緊急狀況,來不及調(diào)整消費者端時抡秆,可以緊急上線一個消費者組奕巍,專門用來將消息快速轉(zhuǎn)錄。保存到數(shù)據(jù)庫或者Redis儒士,然后再慢慢進行處理的止。
五着撩、RabbitMQ的備份與恢復(fù)
文檔地址:?https://www.rabbitmq.com/backup.html
RabbitMQ有一個data目錄會保存分配到該節(jié)點上的所有消息。我們的實驗環(huán)境中拖叙,默認是在/var/lib/rabbitmq/mnesia目錄下?這個目錄里面的備份分為兩個部分,一個是元數(shù)據(jù)(定義結(jié)構(gòu)的數(shù)據(jù))薯鳍,一個是消息存儲目錄。
對于元數(shù)據(jù)挖滤,可以在Web管理頁面通過json文件直接導(dǎo)出或?qū)搿?/b>
而對于消息,可以手動進行備份恢復(fù)悯舟。
其實對于消息,由于MQ的特性抵怎,是不建議進行備份恢復(fù)的岭参。而RabbitMQ如果要進行數(shù)據(jù)備份恢復(fù)反惕,也非常簡單演侯。
1)首先,要保證要恢復(fù)的RabbitMQ中已經(jīng)有了全部的元數(shù)據(jù),這個可以通過上一步的json文件來恢復(fù)狡汉。
2)然后,備份過程必須要先停止應(yīng)用盾戴。如果是針對鏡像集群,還需要把整個集群全部停止尖啡。
3)最后剩膘,在RabbitMQ的數(shù)據(jù)目錄中衅斩,有按virtual hosts組織的文件夾怠褐。你只需要按照虛擬主機,將整個文件夾復(fù)制到新的服務(wù)中即可惫搏。持久化消息和非持久化消息都會一起備份蚕涤。?我們實驗環(huán)境的默認目錄是/var/lib/rabbitmq/mnesia/rabbit@worker2/msg_stores/vhosts
六、RabbitMQ的性能監(jiān)控
關(guān)于RabbitMQ的性能監(jiān)控揖铜,在管理控制臺中提供了非常豐富的展示。例如在下面這個簡單的集群節(jié)點圖中天吓,就監(jiān)控了非常多系統(tǒng)的關(guān)鍵資源。
還包括消息的生產(chǎn)消費頻率汰规、關(guān)鍵組件使用情況等等非常多的信息,都可以從這個管理控制臺上展現(xiàn)出來溜哮。但是,對于構(gòu)建一個自動化的性能監(jiān)控系統(tǒng)來說色解,這個管理頁面就不太夠用了。為此科阎,RabbitMQ也提供了一系列的HTTP接口,通過這些接口可以非常全面的使用并管理RabbitMQ的各種功能锣笨。
這些HTTP的接口不需要專門去查手冊道批,在部署的管理控制臺頁面下方已經(jīng)集成了詳細的文檔朴读,我們只需要打開HTTP API的頁面就能看到屹徘。
比如最常用的 http://[server:port]/api/overview 接口衅金,會列出非常多的信息,包含系統(tǒng)的資源使用情況氮唯。通過這個接口,就可以很好的對接Promethus豆励、Grafana等工具,構(gòu)建更靈活的監(jiān)控告警體系良蒸。
可以看到,這里面的接口相當豐富嫩痰,不光可以通過GET請求獲取各種消息窍箍,還可以通過其他類型的HTTP請求來管理RabbitMQ中的各種資源串纺,因此在實際使用時椰棘,還需要考慮這些接口的安全性纺棺。
七邪狞、搭建HAProxy,實現(xiàn)高可用集群
我們之前搭建的鏡像集群帆卓,已經(jīng)具備了集群的功能,請求發(fā)送到任何一個節(jié)點上罪郊,數(shù)據(jù)都是在集群內(nèi)共享的。但是悔橄,在企業(yè)使用時,通常還會選擇在集群基礎(chǔ)上增加負載均衡的能力。即希望將客戶端的請求能夠盡量均勻的分配到集群中各個節(jié)點上癣疟,這樣可以讓集群的壓力得到平衡。
實現(xiàn)負載均衡的方式有很多睛挚,HAProxy就是其中一種可選方案。HAProxy是一個免費侧到、快速并且可靠的解決方案,有很多大型互聯(lián)網(wǎng)公司都在使用匠抗。通過HAProxy污抬,應(yīng)用可以直連一個單一的IP地址汞贸,然后HAProxy會將這個IP地址的TCP請求進行轉(zhuǎn)發(fā),并在轉(zhuǎn)發(fā)過程中實現(xiàn)負載均衡印机。
很多有實力的大企業(yè)會采用F5等其他的一些負載均衡工具。
安裝步驟如下:
1射赛、安裝HAProxy
2、配置HAProxy
修改haproxy.cfg文件咒劲。下面是參考配置诫隅。注意將節(jié)點的IP地址和端口換成你自己的環(huán)境。
八逐纬、總結(jié)
基于MQ的事件驅(qū)動機制蛔屹,給龐大的互聯(lián)網(wǎng)應(yīng)用帶來了不一樣的方向豁生。MQ的異步、解耦甸箱、削峰三大功能特點在很多業(yè)務(wù)場景下都能帶來極大的性能提升,在日常工作過程中芍殖,應(yīng)該嘗試總結(jié)這些設(shè)計的思想。
雖然MQ的功能,說起來比較簡單隐锭,但是隨著MQ的應(yīng)用逐漸深化,所需要解決的問題也更深入计贰。對各種細化問題的挖掘程度,很大程度上決定了開發(fā)團隊能不能真正Hold得住MQ產(chǎn)品荞怒。通常面向互聯(lián)網(wǎng)的應(yīng)用場景樱溉,更加注重MQ的吞吐量,需要將消息盡快的保存下來福贞,再供后端慢慢消費。而針對企業(yè)內(nèi)部的應(yīng)用場景挖帘,更加注重MQ的數(shù)據(jù)安全性,在復(fù)雜多變的業(yè)務(wù)場景下逻族,每一個消息都需要有更加嚴格的安全保障。而在當今互聯(lián)網(wǎng)聘鳞,Kafka是第一個場景的不二代表要拂,但是他會丟失消息的特性,讓kafka的使用場景比較局限脱惰。RabbitMQ作為一個老牌產(chǎn)品,是第二個場景最有力的代表拉一。當然,隨著互聯(lián)網(wǎng)應(yīng)用不斷成熟磅氨,也不斷有其他更全能的產(chǎn)品冒出來嫡纠,比如阿里的RocketMQ以及雅虎的Pulsar决瞳。但是不管未來MQ領(lǐng)域會是什么樣子,RabbitMQ依然是目前企業(yè)級最為經(jīng)典也最為重要的一個產(chǎn)品皮胡。他的功能最為全面赏迟,周邊生態(tài)也非常成熟,并且RabbitMQ有龐大的Spring社區(qū)支持锌杀,本身也在吸收其他產(chǎn)品的各種優(yōu)點,持續(xù)進化糕再,所以未來RabbitMQ的重要性也會更加凸顯。
從RabbitMQ的安裝殴蹄、應(yīng)用猾担、擴展等多個方面袭灯,綜合介紹了RabbitMQ的各種常用使用方法以及業(yè)務(wù)場景绑嘹。希望能夠帶你打開一扇大門,更真實姨丈,更深入的理解MQ這個工具。