人生終將是場單人旅途,孤獨之前是迷茫,孤獨過後是成長。
楔子
本篇是訊息佇列RabbitMQ
的第四彈。
RabbitMQ
我已經寫了三篇了,基礎的收發訊息和基礎的概念我都已經寫了,學任何東西都是這樣,先基礎的上手能用,然後遇到問題再去解決,無法理解就去深入原始碼,隨著時間的積累對這一門技術的理解也會隨之提高。
基礎操作已經熟練後,相信大家不可避免的會生出向那更高處攀登的心來,今天我就羅列一些RabbitMQ
比較高階的用法,有些用得到有些用不上,但是一定要有所瞭解,因為大部分情況我們都是面向面試學習~
- 如何保證訊息的可靠性?
- 訊息佇列如何進行限流?
如何設定延時佇列進行延時消費?
1. ?如何保證訊息的可靠性?
先來看看我們的萬年老圖,從圖上我們大概可以看出來一個訊息會經歷四個節點,只有保證這四個節點的可靠性才能保證整個系統的可靠性。
- 生產者發出後保證到達了MQ。
- MQ收到訊息保證分發到了訊息對應的Exchange。
- Exchange分發訊息入隊之後保證訊息的永續性。
- 消費者收到訊息之後保證訊息的正確消費。
經歷了這四個保證,我們才能保證訊息的可靠性,從而保證訊息不會丟失。
2. ?生產者傳送訊息到MQ失敗
我們的生產者傳送訊息之後可能由於網路閃斷等各種原因導致我們的訊息並沒有傳送到MQ之中,但是這個時候我們生產端又不知道我們的訊息沒有發出去,這就會造成訊息的丟失。
為了解決這個問題,RabbitMQ
引入了事務機制和傳送方確認機制(publisher confirm),由於事務機制過於耗費效能所以一般不用,這裡我著重講述傳送方確認機制。
這個機制很好理解,就是訊息傳送到MQ那端之後,MQ會回一個確認收到的訊息給我們。
開啟此功能需要配置,接下來我來演示一下配置:
spring:
rabbitmq:
addresses: 127.0.0.1
host: 5672
username: guest
password: guest
virtual-host: /
# 開啟訊息確認機制
publisher-confirm-type: correlated
我們只需要在配置裡面開啟訊息確認即可(true是返回客戶端,false是自動刪除)。
生產者:
public void sendAndConfirm() {
User user = new User();
log.info("Message content : " + user);
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend(Producer.QUEUE_NAME,user,correlationData);
log.info("訊息傳送完畢。");
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback(){
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
log.info("CorrelationData content : " + correlationData);
log.info("Ack status : " + ack);
log.info("Cause content : " + cause);
if(ack){
log.info("訊息成功傳送,訂單入庫,更改訂單狀態");
}else{
log.info("訊息傳送失敗:"+correlationData+", 出現異常:"+cause);
}
}
});
}
生產者程式碼裡我們看到又多了一個引數:CorrelationData
,這個引數是用來做訊息的唯一標識,同時我們開啟訊息確認之後需要對rabbitTemplate
多設定一個setConfirmCallback
,引數是一個匿名類,我們訊息確認成功or失敗之後的處理就是寫在這個匿名類裡面。
比如一條訂單訊息,當訊息確認到達MQ確認之後再行入庫或者修改訂單的節點狀態,如果訊息沒有成功到達MQ可以進行一次記錄或者將訂單狀態修改。
Tip:訊息確認失敗不只有訊息沒發過去會觸發,訊息發過去但是找不到對應的Exchange,也會觸發。
3. ?MQ接收失敗或者路由失敗
生產者的傳送訊息處理好了之後,我們就可以來看看MQ端的處理,MQ可能出現兩個問題:
- 訊息找不到對應的Exchange。
- 找到了Exchange但是找不到對應的Queue。
這兩種情況都可以用RabbitMQ
提供的mandatory
引數來解決,它會設定訊息投遞失敗的策略,有兩種策略:自動刪除或返回到客戶端。
我們既然要做可靠性,當然是設定為返回到客戶端。
配置:
spring:
rabbitmq:
addresses: 127.0.0.1
host: 5672
username: guest
password: guest
virtual-host: /
# 開啟訊息確認機制
publisher-confirm-type: correlated
# 開啟訊息返回
publisher-returns: true
template:
mandatory: true
我們只需要在配置裡面開啟訊息返回即可,template.mandatory: true
這一步不要少~
生產者:
public void sendAndReturn() {
User user = new User();
log.info("Message content : " + user);
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
log.info("被退回的訊息為:{}", message);
log.info("replyCode:{}", replyCode);
log.info("replyText:{}", replyText);
log.info("exchange:{}", exchange);
log.info("routingKey:{}", routingKey);
});
rabbitTemplate.convertAndSend("fail",user);
log.info("訊息傳送完畢。");
}
這裡我們可以拿到被退回訊息的所有資訊,然後再進行處理,比如放到一個新的佇列單獨處理,路由失敗一般都是配置問題了。
4. ?訊息入隊之後MQ當機
到這一步基本都是一些很小概率的問題了,比如MQ突然當機了或者被關閉了,這種問題就必須要對訊息做持久化,以便MQ重新啟動之後訊息還能重新恢復過來。
訊息的持久化要做,但是不能只做訊息的持久化,還要做佇列的持久化和Exchange的持久化。
@Bean
public DirectExchange directExchange() {
// 三個構造引數:name durable autoDelete
return new DirectExchange("directExchange", false, false);
}
@Bean
public Queue erduo() {
// 其三個引數:durable exclusive autoDelete
// 一般只設定一下持久化即可
return new Queue("erduo",true);
}
建立Exchange和佇列時只要設定好持久化,傳送的訊息預設就是持久化訊息。
設定持久化時一定要將Exchange和佇列都設定上持久化:
單單隻設定Exchange持久化,重啟之後佇列會丟失。單單隻設定佇列的持久化,重啟之後Exchange會消失,既而訊息也丟失,所以如果不兩個一塊設定持久化將毫無意義。
Tip: 這些都是MQ當機引起的問題,如果出現伺服器當機或者磁碟損壞則上面的手段統統無效,必須引入映象佇列,做異地多活來抵禦這種不可抗因素。
5. ?消費者無法正常消費
最後一步會出問題的地方就在消費者端了,不過這個解決問題的方法我們之前的文章已經說過了,就是消費者的訊息確認。
spring:
rabbitmq:
addresses: 127.0.0.1
host: 5672
username: guest
password: guest
virtual-host: /
# 手動確認訊息
listener:
simple:
acknowledge-mode: manual
開啟手動訊息確認之後,只要我們這條訊息沒有成功消費,無論中間是出現消費者當機還是程式碼異常,只要連線斷開之後這條資訊還沒有被消費那麼這條訊息就會被重新放入佇列再次被消費。
當然這也可能會出現重複消費的情況,不過在分散式系統中冪等性是一定要做的,所以一般重複消費都會被介面的冪等給攔掉。
所謂冪等性就是:一個操作多次執行產生的結果與一次執行產生的結果一致。
冪等性相關內容不在本章討論範圍~所以我就不多做闡述了。
6. ?訊息可靠性案例
這個圖是我很早之前畫的,是為了記錄當時使用RabbitMQ
做訊息可靠性的具體做法,這裡我正好拿出來做個例子給大家看一看。
這個例子中的訊息是先入庫的,然後生產者從DB裡面拿到資料包裝成訊息發給MQ,經過消費者消費之後對DB資料的狀態進行更改,然後重新入庫。
這中間有任何步驟失敗,資料的狀態都是沒有更新的,這時通過一個定時任務不停的去刷庫,找到有問題的資料將它重新扔到生產者那裡進行重新投遞。
這個方案其實和網上的很多方案大同小異,基礎的可靠性保證之後,定時任務做一個兜底進行不斷的掃描,力圖100%可靠性。
後記
越寫越長,因為篇幅緣故限流和延時佇列放到下一篇了,我會盡快發出來供大家閱讀,講真,我真的不是故意多水一篇的!!!
最後再給優狐打個廣告,最近掘金在GitHub上面建立了一個開源計劃 - open-source,旨在收錄各種好玩的好用的開源庫,如果大家有想要自薦或者分享的開源庫都可以參與進去,為這個開源計劃做一份貢獻,同時這個開源庫的Start
也在穩步增長中,參與進去也可以增加自己專案的曝光度,一舉兩得。
同時這個開源庫還有一個兄弟專案 - open-source-translation,旨在招募技術文章翻譯志願者進行技術文章的翻譯工作,
爭做最棒開源翻譯,翻譯業界高質量文稿,為技術人的成長獻一份力。
最近這段時間事情挺多,優狐令我八月底之前升級到三級,所以各位讀者的贊對我很重要,希望大家能夠高抬貴手,幫我一哈~
好了,以上就是本期的全部內容,感謝你能看到這裡,歡迎對本文點贊收藏與評論,?你們的每個點贊都是我創作的最大動力。
我是耳朵,一個一直想做知識輸出的偽文藝程式設計師,我們下期見。