1.可靠性投遞
1)簡述分析
1.可靠性投遞:也就是在使用 RabbitMQ 實現(xiàn)異步通信的時候搀愧,消息丟了怎么辦入偷,消息重復消費怎么辦?
2.明確一個問題:因為效率與可靠性是無法兼得的追驴,如果要保證每一個環(huán)節(jié)都成功,勢必會對消息的收發(fā)效率造成影響疏之。所以如果是一些業(yè)務實時一致性要求不是特別高的場合殿雪,可以犧牲一些可靠性來換取效率。比如發(fā)送通知或者記錄日志的這種場景体捏,如果用戶沒有收到通知冠摄,不會造成業(yè)務影響,只要再次發(fā)送就行了几缭。
3.可靠性保證的環(huán)節(jié):
1.代表消息從生產(chǎn)者發(fā)送到 Broker:生產(chǎn)者把消息發(fā)到 Broker 之后河泳,怎么知道自己的消息有沒有被 Broker 成功接收?
2.代表消息從 Exchange 路由到 Queue:Exchange 是一個綁定列表,如果消息沒有辦法路由到正確的隊列年栓,會發(fā)生什么事情?應該怎么處理?
3.代表消息在 Queue 中存儲:隊列是一個獨立運行的服務拆挥,有自己的數(shù)據(jù)庫(Mnesia),它是真正用來存儲消息的某抓。如果還沒有消費者來消費纸兔,那么消息要一直存儲在隊列里面。如果隊列出了問題否副,消息肯定會丟失汉矿。怎么保證消息在隊列穩(wěn)定地存儲呢?
4.代表消費者訂閱 Queue 并消費消息:隊列的特性是什么?FIFO。隊列里面的消息是一條一條的投遞的备禀,也就是說洲拇,只有上一條消息被消費者接收以后,才能把這一條消息從數(shù)據(jù)庫刪掉曲尸,繼續(xù)投遞下一條消息赋续。那么問題來了,Broker 怎么知道消費者已經(jīng)接收了消息呢?
2)消息發(fā)送到 RabbitMQ 服務器
1.提供機制:服務端確認機制另患,也就是在生產(chǎn)者發(fā)送消息給RabbitMQ 的服務端的時候纽乱,服務端會通過某種方式返回一個應答,只要生產(chǎn)者收到了這個應答昆箕,就知道消息發(fā)送成功了鸦列。
2.服務端確認機制-事務模式:
1)事務模式怎么使用:我們通過一個 channel.txSelect()的方法把信道設置成事務模式,然后就可以發(fā)布消息給 RabbitMQ 了鹏倘,如果 channel.txCommit();的方法調(diào)用成功敛熬,就說明事務提交成功,則消息一定到達了 RabbitMQ 中第股。如果在事務提交執(zhí)行之前由于 RabbitMQ 異常崩潰或者其他原因拋出異常应民,這個時候我們便可以將其捕獲,進而通過執(zhí)行 channel.txRollback()方法來實現(xiàn)事務回滾。
2)缺點:在事務模式里面诲锹,只有收到了服務端的 Commit-OK 的指令繁仁,才能提交成功。所以可以解決生產(chǎn)者和服務端確認的問題归园。但是事務模式有一個缺點黄虱,它是阻塞的,一條消息沒有發(fā)送完畢庸诱,不能發(fā)送下一條消息捻浦,它會榨干 RabbitMQ 服務器的性能。所以不建議大家在生產(chǎn)環(huán)境使用桥爽。
3)Spring Boot 中的設置:
3)服務端確認機制-Confirm(確認)模式:
1.普通確認模式:在生產(chǎn)者這邊通過調(diào)用 channel.confirmSelect()方法將信道設置為 Confirm 模式朱灿,然后發(fā)送消息。一旦消息被投遞到所有匹配的隊列之后钠四,RabbitMQ 就會發(fā)送一個確認(Basic.Ack)給生產(chǎn)者盗扒,也就是調(diào)用 channel.waitForConfirms()返回 true,這樣生產(chǎn)者就知道消息被服務端接收了缀去!----》方式消息效率還不是太高
2.批量確認模式:批量確認侣灶,就是在開啟 Confirm 模式后,先發(fā)送一批消息缕碎。只要channel.waitForConfirmsOrDie();方法沒有拋出異常褥影,就代表消息都被服務端接收了。
批量確認的方式比單條確認的方式效率要高咏雌,但是也有兩個問題伪阶,第一個就是批量的數(shù)量的確定。對于不同的業(yè)務处嫌,到底發(fā)送多少條消息確認一次?數(shù)量太少,效率提升不上去斟湃。數(shù)量多的話熏迹,又會帶來另一個問題,比如我們發(fā) 1000 條消息才確認一次凝赛,如果前面 999 條消息都被服務端接收了注暗,如果第 1000 條消息被拒絕了,那么前面所有的消息都要重發(fā)墓猎。
3.異步確認模式:一邊發(fā)送一邊確認捆昏!異步確認模式需要添加一個 ConfirmListener,并且用一個 SortedSet 來維護沒有被確認的消息毙沾。Confirm 模式是在 Channel 上開啟的骗卜,因為 RabbitTemplate 對 Channel 進行了封裝,叫做 ConfimrCallback。
3)消息從交換機路由到隊列
1.在什么情況下寇仓,消息會無法路由到正確的隊列?可能因為路由鍵錯誤举户,或者隊列不存在。
2.兩種方式處理無法路由的消息:一種就是讓服務端重發(fā)給生產(chǎn)者遍烦,一種是讓交換機路由到另一個備份的交換機俭嘁。
3.消息回發(fā)的方式:使用 mandatory 參數(shù)和 ReturnListener(在 Spring AMQP 中是ReturnCallback)。
4.備份交換機的方式:在創(chuàng)建交換機的時候服猪,從屬性中指定備份交換機供填。
5.注意區(qū)別:隊列可以指定死信交換機;交換機可以指定備份交換機!
4)消息在隊列存儲
1.問題:如果 RabbitMQ 的服務或者硬件發(fā)生故障,比如系統(tǒng)宕機罢猪、重啟近她、關閉等等,可能會導致內(nèi)存中的消息丟失坡脐,所以我們要把消息本身和元數(shù)據(jù)(隊列泄私、交換機、綁定)都保存到磁盤备闲。
2.解決方案:1)隊列持久化 2)交換機持久化 3)消息持久化 4)集群
集群:如果只有一個 RabbitMQ 的節(jié)點晌端,即使交換機、隊列恬砂、消息做了持久化咧纠,如果服務崩潰或者硬件發(fā)生故障,RabbitMQ 的服務一樣是不可用的泻骤,所以為了提高 MQ 服務的可用性漆羔,保障消息的傳輸,我們需要有多個 RabbitMQ 的節(jié)點狱掂!
5)消息投遞到消費者
1.問題:如果消費者收到消息后沒來得及處理即發(fā)生異常演痒,或者處理過程中發(fā)生異常,會導致4失敗趋惨。服務端應該以某種方式得知消費者對消息的接收情況鸟顺,并決定是否重新投遞這條消息給其他消費者。
2.解決方案:RabbitMQ 提供了消費者的消息確認機制(message acknowledgement)器虾,消費者可以自動或者手動地發(fā)送 ACK 給服務端讯嫂。沒有收到 ACK 的消息,消費者斷開連接后兆沙,RabbitMQ 會把這條消息發(fā)送給其他消費者欧芽。如果沒有其他消費者,消費者重啟后會重新消費這條消息葛圃,重復執(zhí)行業(yè)務邏輯千扔。
3.指定參數(shù):消費者在訂閱隊列時憎妙,可以指定 autoAck 參數(shù),當 autoAck 等于 false 時昏鹃,RabbitMQ會等待消費者顯式地回復確認信號后才從隊列中移去消息尚氛。
4.如何設置手動 ACK?
5.三個值的區(qū)別:?NONE:自動 ACK? ?MANUAL: 手動 ACK? ?AUTO:如果方法未拋出異常,則發(fā)送 ack洞渤。
6.auto注意:如果方法未拋出異常阅嘶,則發(fā)送 ack!當拋出 AmqpRejectAndDontRequeueException 異常的時候载迄,則消息會被拒絕讯柔,且不重新入隊。當拋出 ImmediateAcknowledgeAmqpException 異常护昧,則消費者會發(fā)送 ACK魂迄。其他的異常,則消息會被拒絕惋耙,且 requeue = true 會重新入隊捣炬。
7. Spring Boot 中,消費者又怎么調(diào)用 ACK绽榛,或者說怎么獲得 Channel 參數(shù)呢?
8.拒絕的方式:Basic.Reject()拒絕單條湿酸,Basic.Nack()批量拒絕。如果 requeue 參數(shù)設置為 true灭美,可以把這條消息重新存入隊列推溃,以便發(fā)給下一個消費者(當然,只有一個消費者的時候届腐,這種方式可能會出現(xiàn)無限循環(huán)重復消費的情況铁坎。可以投遞到新的隊列中犁苏,或者只打印異常日志)硬萍。
6)消費者回調(diào)
1.調(diào)用生產(chǎn)者 API:例如:提單系統(tǒng)給其他系統(tǒng)發(fā)送了碎屏保消息后,其他系統(tǒng)必須在處理完消息后調(diào)用提單系統(tǒng)提供的 API围详,來修改提單系統(tǒng)中數(shù)據(jù)的狀態(tài)朴乖。只要 API 沒有被調(diào)用,數(shù)據(jù)狀態(tài)沒有被修改短曾,提單系統(tǒng)就認為下游系統(tǒng)沒有收到這條消息。
2.發(fā)送響應消息給生產(chǎn)者:例如:商業(yè)銀行與人民銀行二代支付通信赐劣,無論是人行收到了商業(yè)銀行的消息嫉拐,還是商業(yè)銀行收到了人行的消息,都必須發(fā)送一條響應消息(叫做回執(zhí)報文)魁兼。
7)補償機制
1.問題:如果生產(chǎn)者的 API 就是沒有被調(diào)用婉徘,也沒有收到消費者的響應消息漠嵌,怎么辦?
2.解決方案:設置超時時間
8)消息冪等性
1.消息出現(xiàn)重復可能會有兩個原因:1).生產(chǎn)者的問題,環(huán)節(jié)1重復發(fā)送消息盖呼,比如在開啟了 Confirm 模式但未收到確認儒鹿,消費者重復投遞。2).環(huán)節(jié)4出了問題几晤,由于消費者未發(fā)送 ACK 或者其他原因约炎,消息重復投遞。3)生產(chǎn)者代碼或者網(wǎng)絡問題蟹瘾。
2.解決方案:對于重復發(fā)送的消息圾浅,可以對每一條消息生成一個唯一的業(yè)務 ID,通過日志或者消息落庫來做重復控制憾朴。
9)最終一致
1.如果確實是消費者宕機了狸捕,或者代碼出現(xiàn)了 BUG 導致無法正常消費,在我們嘗試多次重發(fā)以后众雷,消息最終也沒有得到處理灸拍,怎么辦?
例如存款的場景,客戶的錢已經(jīng)被吞了砾省,但是余額沒有增加鸡岗,這個時候銀行出現(xiàn)了長款,應該怎么處理?如果客戶沒有主動通知銀行纯蛾,這個問題是怎么發(fā)現(xiàn)的?銀行最終怎么把這個賬務做平?
在我們的金融系統(tǒng)中纤房,都會有雙方對賬或者多方對賬的操作,通常是在一天的業(yè)務結(jié)束之后翻诉,第二天營業(yè)之前炮姨。我們會約定一個標準,比如 ATM 跟核心系統(tǒng)對賬碰煌,肯定是以核心系統(tǒng)為準舒岸。ATMC 獲取到核心的對賬文件,然后解析芦圾,登記成數(shù)據(jù)蛾派,然后跟自己記錄的流水比較,找出核心有 ATM 沒有个少,或者 ATM 有核心沒有洪乍,或者兩邊都有但是金額不一致的數(shù)據(jù)。對賬之后夜焦,我們再手工平賬壳澳。比如取款記了賬但是沒吐鈔的,做一筆沖正茫经。存款吞了鈔沒記賬的巷波,要么把錢退給客戶萎津,要么補一筆賬。
10)消息的順序性
1.定義:消息的順序性指的是消費者消費消息的順序跟生產(chǎn)者生產(chǎn)消息的順序是一致的抹镊。
2.舉例:商戶信息同步到其他系統(tǒng)锉屈,有三個業(yè)務操作:1、新增門店 2垮耳、綁定產(chǎn)品 3颈渊、激活門店,這種情況下消息消費順序不能顛倒(門店不存在時無法綁定產(chǎn)品和激活)氨菇。
3.現(xiàn)狀:在 RabbitMQ 中儡炼,一個隊列有多個消費者時,由于不同的消費者消費消息的速度是不一樣的查蓉,順序無法保證乌询。只有一個隊列僅有一個消費者的情況才能保證順序消費(不同的業(yè)務消息發(fā)送到不同的專用的隊列)。