RabbitMQ如何解決各種情況下丟資料的問題
1.生產者丟資料
生產者的訊息沒有投遞到MQ中怎麼辦?從生產者弄丟資料這個角度來看,RabbitMQ提供transaction和confirm模式來確保生產者不丟訊息。 transaction機制就是說,傳送訊息前,開啟事物(channel.txSelect()),然後傳送訊息,如果傳送過程中出現什麼異常,事物就會回滾(channel.txRollback()),如果傳送成功則提交事 物(channel.txCommit())。 然而缺點就是吞吐量下降了。因此,按照博主的經驗,生產上用confirm模式的居多。一旦channel進入confirm模式,所有在該通道上面釋出的訊息都將會被指派一個唯一的ID(從1開始),一旦 訊息被投遞到所有匹配的佇列之後,rabbitMQ就會傳送一個Ack給生產者(包含訊息的唯一ID),這就使得生產者知道訊息已經正確到達目的佇列了.如果rabiitMQ沒能處理該訊息,則會傳送一個N ack訊息給你,你可以進行重試操作。
下面演示一下confirm模式:
//測試確認後回撥 @Service public class HelloSender1 implements RabbitTemplate.ConfirmCallback { @Autowired private RabbitTemplate rabbitTemplate; public void send() { String context = "你好現在是 " + new Date() +""; System.out.println("HelloSender傳送內容 : " + context); this.rabbitTemplate.setConfirmCallback(this); //exchange,queue 都正確,confirm被回撥, ack=true //this.rabbitTemplate.convertAndSend("exchange","topic.message", context); //exchange 錯誤,queue 正確,confirm被回撥, ack=false //this.rabbitTemplate.convertAndSend("fasss","topic.message", context); //exchange 正確,queue 錯誤 ,confirm被回撥, ack=true; return被回撥 replyText:NO_ROUTE //this.rabbitTemplate.convertAndSend("exchange","", context); //exchange 錯誤,queue 錯誤,confirm被回撥, ack=false this.rabbitTemplate.convertAndSend("fasss","fass", context); } @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println("confirm--:correlationData:"+correlationData+",ack:"+ack+",cause:"+cause); } }
2.訊息佇列丟資料
處理訊息佇列丟資料的情況,一般是開啟持久化磁碟的配置。這個持久化配置可以和confirm機制配合使用,你可以在訊息持久化磁碟後,再給生產者傳送一個Ack訊號。這樣,如果訊息持久化磁碟 之前,rabbitMQ陣亡了,那麼生產者收不到Ack訊號,生產者會自動重發。 那麼如何持久化呢,這裡順便說一下吧,其實也很容易,就下面兩步 ①、將queue的持久化標識durable設定為true,則代表是一個持久的佇列 ②、傳送訊息的時候將deliveryMode=2 這樣設定以後,rabbitMQ就算掛了,重啟後也能恢復資料。在訊息還沒有持久化到硬碟時,可能服務已經死掉,這種情況可以通過引入mirrored-queue即映象佇列,但也不能保證訊息百分百不丟 失(整個叢集都掛掉)
/** * 第二個引數:queue的持久化是通過durable=true來實現的。 * 第三個引數:exclusive:排他佇列,如果一個佇列被宣告為排他佇列,該佇列僅對首次申明它的連線可見,並在連線斷開時自動刪除。這裡需要注意三點: 1. 排他佇列是基於連線可見的,同一連線的不同通道是可以同時訪問同一連線建立的排他佇列; 2.“首次”,如果一個連線已經宣告瞭一個排他佇列,其他連線是不允許建立同名的排他佇列的,這個與普通佇列不同; 3.即使該佇列是持久化的,一旦連線關閉或者客戶端退出,該排他佇列都會被自動刪除的,這種佇列適用於一個客戶端傳送讀取訊息的應用場景。 * 第四個引數:自動刪除,如果該佇列沒有任何訂閱的消費者的話,該佇列會被自動刪除。這種佇列適用於臨時佇列。 * @param * @return * @Author zxj */ @Bean public Queue queue() { Map<String, Object> arguments = new HashMap<>(); arguments.put("x-message-ttl", 25000);//25秒自動刪除 Queue queue = new Queue("topic.messages", true, false, true, arguments); return queue; }
MessageProperties properties=new MessageProperties(); properties.setContentType(MessageProperties.DEFAULT_CONTENT_TYPE); properties.setDeliveryMode(MessageProperties.DEFAULT_DELIVERY_MODE);//持久化設定 properties.setExpiration("2018-12-15 23:23:23");//設定到期時間 Message message=new Message("hello".getBytes(),properties); this.rabbitTemplate.sendAndReceive("exchange","topic.message",message);
3.消費者丟資料
啟用手動確認模式可以解決這個問題 ①自動確認模式,消費者掛掉,待ack的訊息迴歸到佇列中。消費者丟擲異常,訊息會不斷的被重發,直到處理成功。不會丟失訊息,即便服務掛掉,沒有處理完成的訊息會重回佇列,但是異常會讓 訊息不斷重試。 ②手動確認模式 ③不確認模式,acknowledge="none" 不使用確認機制,只要訊息傳送完成會立即在佇列移除,無論客戶端異常還是斷開,只要傳送完就移除,不會重發。
指定Acknowledge的模式: spring.rabbitmq.listener.direct.acknowledge-mode=manual,表示該監聽器手動應答訊息 針對手動確認模式,有以下特點: 1.使用手動應答訊息,有一點需要特別注意,那就是不能忘記應答訊息,因為對於RabbitMQ來說處理訊息沒有超時,只要不應答訊息,他就會認為仍在正常處理訊息,導致訊息佇列出現阻塞,影響 業務執行。 2.如果消費者來不及處理就死掉時,沒有響應ack時,會專案啟動後會重複傳送一條資訊給其他消費者; 3.可以選擇丟棄訊息,這其實也是一種應答,如下,這樣就不會再次收到這條訊息。 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false); 4.如果消費者設定了手動應答模式,並且設定了重試,出現異常時無論是否捕獲了異常,都是不會重試的 5.如果消費者沒有設定手動應答模式,並且設定了重試,那麼在出現異常時沒有捕獲異常會進行重試,如果捕獲了異常不會重試。
重試機制:
spring.rabbitmq.listener.simple.retry.max-attempts=5 最大重試次數 spring.rabbitmq.listener.simple.retry.enabled=true 是否開啟消費者重試(為false時關閉消費者重試,這時消費端程式碼異常會一直重複收到訊息) spring.rabbitmq.listener.simple.retry.initial-interval=5000 重試間隔時間(單位毫秒) spring.rabbitmq.listener.simple.default-requeue-rejected=false 重試次數超過上面的設定之後是否丟棄(false不丟棄時需要寫相應程式碼將該訊息加入死信佇列)
如果設定了重試模式,那麼在出現異常時沒有捕獲異常會進行重試,如果捕獲了異常不會重試。
當出現異常時,我們需要把這個訊息回滾到訊息佇列,有兩種方式:
//ack返回false,並重新回到佇列,api裡面解釋得很清楚
//ack返回false,並重新回到佇列,api裡面解釋得很清楚 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); //拒絕訊息 channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
經過開發中的實際測試,當訊息回滾到訊息佇列時,這條訊息不會回到佇列尾部,而是仍是在佇列頭部,這時消費者會立馬又接收到這條訊息進行處理,接著丟擲異常,進行 回滾,如此反覆進行。這種情況會導致訊息佇列處理出現阻塞,訊息堆積,導致正常訊息也無法執行。對於訊息回滾到訊息佇列,我們希望比較理想的方式時出現異常的訊息到 達訊息佇列尾部,這樣既保證訊息不會丟失,又保證了正常業務的進行,因此我們採取的解決方案是,將訊息進行應答,這時訊息佇列會刪除該訊息,同時我們再次傳送該訊息 到訊息佇列,這時就實現了錯誤訊息進行訊息佇列尾部的方案。
//手動進行應答 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //重新傳送訊息到隊尾 channel.basicPublish(message.getMessageProperties().getReceivedExchange(), message.getMessageProperties().getReceivedRoutingKey(), MessageProperties.PERSISTENT_TEXT_PLAIN, JSON.toJSONBytes(new Object()));
如果一個訊息體本身有誤,會導致該訊息體,一直無法進行處理,而伺服器中刷出大量無用日誌。解決這個問題可以採取兩種方案:
1.一種是對於日常細緻處理,分清哪些是可以恢復的異常,哪些是不可以恢復的異常。對於可以恢復的異常我們採取第三條中的解決方案,對於不可以處理的異常,我們採用記錄日誌,直接丟棄該訊息方案。
2.另一種是我們對每條訊息進行標記,記錄每條訊息的處理次數,當一條訊息,多次處理仍不能成功時,處理次數到達我們設定的值時,我們就丟棄該訊息,但需要記錄詳細的日誌。
訊息監聽內的異常處理有兩種方式:
1.內部catch後直接處理,然後使用channel對訊息進行確認
2.配置RepublishMessageRecoverer將處理異常的訊息傳送到指定佇列專門處理或記錄。監聽的方法內丟擲異常貌似沒有太大用處。因為丟擲異常就算是重試也非常有可能會繼續出現異常,當重試次數完了之後訊息就只有重啟應用才能接收到了,很有可能導致訊息消費不及時。當然可以配置RepublishMessageRecoverer來解決,但是萬一RepublishMessageRecoverer傳送失敗了呢。。那就可能造成訊息消費不及時了。所以即使需要將處理出現異常的訊息統一放到另外佇列去處理,個人建議兩種方式:
①catch異常後,手動傳送到指定佇列,然後使用channel給rabbitmq確認訊息已消費
②給Queue繫結死信佇列,使用nack(requque為false)確認訊息消費失敗
相關文章
- 資料庫事務併發問題----各種事務隔離下的情況資料庫
- 如何解決重要資料檔案各種問題?
- RabbitMQ如何解決被重複消費和資料丟失的問題?MQ
- 呼叫layoutSubviews各種情況分析View
- 當儲存EVA出現故障這種方法可高效解決資料丟失的情況
- 如何解決各個行業出現的資料洩露問題?行業
- MYSQL索引失效的各種情況小結MySql索引
- java語言的各種輸入情況Java
- Redo 丟失的4種情況的處理方法
- vue資料丟失的4中情況和解決方法(附影片教程)Vue
- VC 各種情況下的視窗控制程式碼的獲取
- Redo丟失的4種情況及處理方法
- Asp.net 2.0 Session 丟失的幾種情況ASP.NETSession
- Latex請問這種情況怎麼解決
- 高併發高負載情況下常見的3種效能問題負載
- Mysql兩種情況下更新欄位中部分資料的方法MySql
- 遇到RAID5陣列硬碟出現問題的情況該如何解決?AI陣列硬碟
- 安裝rabbitmq遇到各種問題,多數是erlang與rabbitmq版本不匹配MQ
- 如何解決大資料安全問題大資料
- 如何解決資料庫配置問題資料庫
- 資料治理--結構化資料處理 各種情況的資料重跑,流水錶用拉鍊表
- 資料結構連結串列各種問題資料結構
- RabbitMQ-如何保證訊息在99.99%的情況下不丟失MQ
- 資料檔案OFFLINE的3種情況
- 解決RabbitMQ訊息丟失與重複消費問題MQ
- 請問python遇到這種情況怎麼解決?Python
- 資料庫各種檔案丟失恢復大全。資料庫
- javascript中的各種問題JavaScript
- RMQ問題的各種解法MQ
- 高併發下資料冪等問題的9種解決方案
- thinkphp5丟失日誌問題,該如何解決?PHP
- sqlplus 下恢復active 日誌丟失的情況SQL
- 對多種情況下控制檔案的理解與疑問,請指正。
- NoClassDefFoundError的兩種情況Error
- 【故障處理】DG環境主庫丟失歸檔情況下資料檔案的恢復
- undo表空間出現問題的幾種情況與處理
- 在大資料情況下MySQL的一種簡單分頁最佳化方法大資料MySql
- 變數轉化為判斷條件時的各種情況變數