分佈式系統消息中間件——RabbitMQ的使用思考篇

前言

    前面的兩篇文章分佈式系統消息中間件——RabbitMQ的使用基礎篇分佈式系統消息中間件——RabbitMQ的使用進階篇,咱們簡單介紹了消息中間件與RabbitMQ的一些基本概念、基礎用法以及經常使用的幾個特性。但若是咱們想更好的去結合咱們的業務場景使用好RabbitMQ,咱們還須要思考一些問題。好比:什麼時候去建立隊列,RabbitMQ的持久化,如何保證消息到達RabbitMQ,以及消費者如何確認消息......html

1、什麼時候建立隊列

    從前面的文章咱們知道,RabbitMQ能夠選擇在生產者建立隊列,也能夠在消費者端建立隊列,也能夠提早建立好隊列,而生產者消費者直接使用便可。java

    RabbitMQ的消息存儲在隊列中,交換器的使用並不真正耗費服務器的性能,而隊列會。如在實際業務應用中,須要對所建立的隊列的流量、內存佔用及網卡佔用有一個清晰的認知,預估其平均值和峯值,以便在固定硬件資源的狀況下可以進行合理有效的分配。數據庫

    按照RabbitMQ官方建議,生產者和消費者都應該嘗試建立(這裏指聲明操做)隊列。這雖然是一個很好的建議,可是在我看來這個時間上沒有最好的方案,只有最適合的方案。咱們每每須要結合業務、資源等方面在各類方案裏面選擇一個最適合咱們的方案。緩存

    若是業務自己在架構設計之初己經充分地預估了隊列的使用狀況,徹底能夠在業務程序上線以前在服務器上建立好(好比經過頁面管理、RabbitMQ命令或者更好的是從配置中心下發),這樣業務程序也能夠免去聲明的過程,直接使用便可。預先建立好資源還有一個好處是,能夠確保交換器和隊列之間正確地綁定匹配。不少時候,因爲人爲因素、代碼缺陷等,發送消息的交換器並無綁定任何隊列,那麼消息將會丟失:或者交換器綁定了某個隊列,可是發送消息時的路由鍵沒法與現存的隊列匹配,那麼消息也會丟失。固然能夠配合mandatory參數或者備份交換器(關於mandatory參數的使用詳細可參考個人上一篇文章) 來提升程序的健壯性。與此同時,預估好隊列的使用狀況很是重要,若是在後期運行過程當中超過預約的閾值,能夠根據實際狀況對當前集羣進行擴容或者將相應的隊列遷移到其餘集羣。遷移的過程也能夠對業務程序徹底透明。此種方法也更有利於開發和運維分工,便於相應資源的管理。若是集羣資源充足,而即將使用的隊列所佔用的資源又在可控的範圍以內,爲了增長業務程序的靈活性,也徹底能夠在業務程序中聲明隊列。至因而使用預先分配建立資源的靜態方式仍是動態的建立方式,須要從業務邏輯自己、公司運維體系和公司硬件資源等方面考慮。服務器

2、持久化及策略

    做爲一個內存中間件,在保證了速度的狀況下,不可避免存在如內存數據庫一樣的問題,即丟失問題。持久化能夠提升RabbitMQ 的可靠性,以防在異常狀況(重啓、關閉、宕機等)下的數據丟失。RabbitMQ的持久化分爲三個部分:交換器的持久化、隊列的持久化和消息的持久化。網絡

  1. 交換器的持久化

    交換器的持久化是經過在聲明隊列是將durable 參數置爲true 實現的(該參數默認爲false)。若是交換器不設置持久化,那麼在RabbitMQ 服務重啓以後,相關的交換器元數據會丟失,不過消息不會丟失,只是不能將消息發送到這個交換器中了。對一個長期使用的交換器來講,建議將其置爲持久化的。架構

  1. 隊列的持久化

    隊列的持久化是經過在聲明隊列時將durable 參數置爲true 實現的(該參數默認爲false),若是隊列不設置持久化,那麼在RabbitMQ 服務重啓以後,相關隊列的元數據會丟失,此時數據也會丟失。正所謂"皮之不存,毛將焉附",隊列都沒有了,消息又能存在哪裏呢?併發

  1. 消息的持久化

    隊列的持久化能保證其自己的元數據不會因異常狀況而丟失,可是並不能保證內部所存儲的消息不會丟失。要確保消息不會丟失,須要將其設置爲持久化。經過將消息的投遞模式(BasicProperties中的DeliveryMode屬性)設置爲2便可實現消息的持久化。運維

    所以,消息若是要想在Rabbit重啓、關閉、宕機時可以恢復,須要作到如下三點:異步

  • 把消息的投遞模式設置爲2
  • 發送到持久化的交換器
  • 到達持久化的隊列

    注意:RabbitMQ 確保持久化消息能從服務器重啓中恢復的方式是將它們寫入磁盤上的一個持久化日誌文件中。當發佈一條持久化消息到持久化交換器時,Rabbit會在日誌提交到日誌文件後才發送響應(開啓生產者確認機制)。以後,若是消息到了非持久化隊列,它會自動從日誌文件中刪除,而且沒法在服務器重啓後恢復。所以單單隻設置隊列持久化,重啓以後消息會丟失;單單隻設置消息的持久化,重啓以後隊列消失,繼而消息也丟失。單單設置消息持久化而不設置隊列的持久化是毫無心義的。當從持久化隊列中消費了消息後(而且確認後),RabbitMQ會在持久化日誌中把這條消息標記爲等待垃圾收集。而在消費持久化消息以前,若RabbitMQ服務器重啓,會自動重建交換器、隊列以及綁定,重播持久化日誌文件中的消息到合適的隊列或者交換器上(取決於宕機時,消息處在路由的哪一個環節)。

    爲了保障消息不會丟失,也許咱們能夠簡單粗暴的將全部的消息標記爲持久化,但這樣咱們會付出性能的代價。寫入磁盤的速度比寫入內存的速度慢得不僅一點點。對於可靠性不是那麼高的消息能夠不採用持久化處理以提升總體的吞吐量。在選擇是否要將消息持久化時,須要在可靠性和吐吞量之間作一個權衡。

    將交換器、隊列、消息都設置了持久化以後就能百分之百保證數據不丟失了嗎?

  • 從消費者來講,若是在訂閱消費隊列時將noAck參數設置爲true ,那麼當消費者接收到相關消息以後,還沒來得及處理就宕機了,這樣也算數據丟失。
  • 在持久化的消息正確存入RabbitMQ 以後,還須要有一段時間(雖然很短,可是不可忽視〉才能存入磁盤之中。RabbitMQ 並不會爲每條消息都進行同步存盤的處理,可能僅僅保存到操做系統緩存之中而不是物理磁盤之中。若是在這段時間內RabbitMQ 服務節點發生了巖機、重啓等異常狀況,消息保存還沒來得及落盤,那麼這些消息將會丟失。

    關於第一個問題,能夠經過消費者確認機制來解決。而第二個問題能夠經過生產者確認機制來解決,也可使用鏡像隊列機制(鏡像隊列機制,將在運維篇總結)。生產者確認消費者確認請往下看。加羣探討學習:874811168  免費獲取java視頻資料一份

3、生產者確認

    上文咱們知道,在使用RabbitMQ的時候,能夠經過消息持久化操做來解決由於服務器的異常崩潰而致使的消息丟失,除此以外,咱們還會遇到一個問題,當消息的生產者將消息發送出去以後,消息到底有沒有正確地到達服務器呢?若是不進行特殊配置,默認狀況下發送消息的操做是不會返回任何信息給生產者的,也就是默認狀況下生產者是不知道消息有沒有正確地到達服務器。若是在消息到達服務器以前己經丟失,持久化操做也解決不了這個問題,由於消息根本沒有到達服務器,何談持久化?

    RabbitMQ針對這個問題,提供了兩種解決方式:

  • 經過事務機制實現:
  • 經過發送方確認(publisher confirm)機制實現。

3.1 RabbitMQ 事務機制

    RabbitMQ 客戶端中與事務機制相關的方法有三個:channel.TxSelect(用於將當前信道設置爲事務模式);channel.TxCommit(用於提交事務),channel.TxRollback(用於回滾事務)。在經過channel.TxSelect方法開啓事務以後,咱們即可以發佈消息給RabbitMQ了,若是事務提交成功,則消息必定到達了RabbitMQ 中,若是在事務提交執行以前因爲RabbitMQ異常崩潰或者其餘緣由拋出異常,這個時候咱們即可以將其捕獲,進而經過執行channel.TxRollback方法來實現事務回滾。示例代碼以下所示:

channel.TxSelect();//將信道設置爲事務模式
  try
  {
      //do something
      var message = Encoding.UTF8.GetBytes("TestMsg");
      channel.BasicPublish("normalExchange", "NormalRoutingKey", true, null, message);
      //do something
      channel.TxCommit();//提交事務
  }
  catch (Exception ex)
  {
      //log(ex);
      channel.TxRollback();
  }

    事務確實可以解決消息發送方和RabbitMQ之間消息確認的問題,只有消息成功被RabbitMQ接收,事務才能提交成功,不然即可在捕獲異常以後進行事務回滾,與此同時能夠進行消息重發。可是使用事務一樣會帶來一些問題。

  • 會阻塞,發佈者必須等待broker處理每一個消息。
  • 事務是重量級的,每次提交都須要fsync(),須要耗費大量的時間
  • 事務很是耗性能,會下降RabbitMQ的消息吞吐量。

3.2 發送方確認機制

    前面介紹了RabbitMQ可能會遇到的一個問題,即消息發送方(生產者〉並不知道消息是否真正地到達了RabbitMQ。隨後瞭解到在AMQP協議層面提供了事務機制來解決這個問題,可是採用事務機制實現會嚴重下降RabbitMQ的消息吞吐量,這裏就引入了一種輕量級的方式一發送方確認(publisher confirm)機制。生產者將信道設置成confirm確認)模式,一旦信道進入confirm模式,全部在該信道上面發佈的消息都會被指派一個惟一的ID( 從1開始),一旦消息被投遞到全部匹配的隊列以後,RabbitMQ就會發送一個確認(BasicAck) 給生產者(包含消息的惟一ID),這就使得生產者知曉消息已經正確到達了目的地了。若是消息和隊列是可持久化的,那麼確認消息會在消息寫入磁盤以後發出。

RabbitMQ發送方確認機制

    發送方確認模式,示例代碼以下:

//示例1--同步等待
 channel.ConfirmSelect();//開啓確認模式
 var message = Encoding.UTF8.GetBytes("TestMsg");
 channel.ExchangeDeclare("normalExchange", "direct", true, false, null);
 channel.QueueDeclare("normalQueue", true, false, false, null);
 channel.QueueBind("normalQueue", "normalExchange", "NormalRoutingKey");
 channel.BasicPublish("normalExchange", "NormalRoutingKey", true, null, message);
 //var result=channel.WaitForConfirmsOrDie(Timeout); 
 //WaitForConfirmsOrDie 使用WaitForConfirmsOrDie 在Rabbit發送Nack命令或超時時會拋出一個異常
 var result = channel.WaitForConfirms();//等待該信道全部未確認的消息結果
 if(!result){
     //send message failed;
 }
//示例2--異步通知
 channel.ConfirmSelect();//開啓確認模式
 var message = Encoding.UTF8.GetBytes("TestMsg");
 channel.ExchangeDeclare("normalExchange", "direct", true, false, null);
 channel.QueueDeclare("normalQueue", true, false, false, null);
 channel.QueueBind("normalQueue", "normalExchange", "NormalRoutingKey");
 channel.BasicPublish("normalExchange", "NormalRoutingKey", true, null, message);
 channel.BasicAcks += (model, ea) =>
 {
     //消息被投遞到全部匹配的隊列以後,RabbitMQ就會發送一個確認(Basic.Ack)給生產者(包含消息的惟一ID)
     //ea.Multiple爲True表明 ea.DeliveryTag編號以前的消息均已被確認。
    //do something;
 };
 channel.BasicNacks += (model, ea) =>
 {
     //若是RabbitMQ 由於自身內部錯誤致使消息丟失,就會發送一條nack(BasicNack) 命令
    //do something;
 };

    關於生產者確認機制一樣會有一些問題,broker不能保證消息會被confirm,只知道將會進行confirm。這樣若是broker與生產者之間的鏈接斷開,致使生產者不能收到確認消息,可能會重複進行發佈。總之,生產者確認模式給客戶端提供了一種較爲輕量級的方式,可以跟蹤哪些消息被broker處理,哪些可能由於broker宕掉或者網絡失敗的狀況而從新發布。

    注意:事務機制和publisher confirm機制二者是互斥的,不能共存。若是企圖將已開啓事務模式的信道再設置爲publisher confmn模式, RabbitMQ會報錯,或者若是企圖將已開啓publisher confirm模式的信道設置爲事務模式, RabbitMQ也會報錯。在性能上來看,而到底應該選擇事務機制仍是Confirm機制,則須要結合咱們的業務場景。

4、消費者確認

    爲了保證消息從隊列可靠地達到消費者,RabbitMQ提供了消息確認機制(message acknowledgement)。消費者在訂閱隊列時,能夠指定noAck參數,當noAck等於false時,RabbitMQ會等待消費者顯式地回覆確認信號後才從內存(或者磁盤)中移去消息(實質上是先打上刪除標記,以後再刪除)。當noAck等於true時,RabbitMQ會自動把發送出去的消息置爲確認,而後從內存(或者磁盤)中刪除,而無論消費者是否真正地消費到了這些消息。

    採用消息確認機制後,只要設置noAck參數爲false,消費者就有足夠的時間處理消息(任務),不用擔憂處理消息過程當中消費者進程掛掉後消息丟失的問題,由於RabbitMQ會一直等待持有消息直到消費者顯式調用BasicAck命令爲止。

    當noAck參數置爲false,對於RabbitMQ服務端而言,隊列中的消息分紅了兩個部分:一部分是等待投遞給消費者的消息:一部分是己經投遞給消費者,可是尚未收到消費者確認信號的消息。若是RabbitMQ 一直沒有收到消費者的確認信號,而且消費此消息的消費者己經斷開鏈接,則RabbitMQ會安排該消息從新進入隊列,等待投遞給下一個消費者,固然也有可能仍是原來的那個消費者。

    RabbitMQ不會爲未確認的消息設置過時時間,它判斷此消息是否須要從新投遞給消費者的惟一依據是消費該消息的消費者鏈接是否己經斷開,這麼設計的緣由是RabbitMQ 容許消費者消費一條消息的時間能夠好久好久。加羣探討學習:874811168  免費獲取java視頻資料一份

    關於RabbitMQ消費者確認機制示例代碼以下:

//推模式
  EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
  //定義消費者回調事件
  consumer.Received += (model, ea) =>
  {
      //do someting;
      //channel.BasicReject(ea.DeliveryTag, requeue: true);//拒絕
      //requeue參數爲true會從新將這條消息存入隊列,以即可以發送給下一個訂閱的消費者
      channel.BasicAck(ea.DeliveryTag, multiple: false);//確認
      //若:multiple參數爲true,則確認DeliverTag這個編號以前的消息
  };
  channel.BasicConsume(queue: "queueName",
                      noAck: false,
                     consumer: consumer);

  //拉模式
  BasicGetResult result = channel.BasicGet("queueName", noAck: false);
  //確認
  channel.BasicAck(result.DeliveryTag, multiple: false);

RabbitMQ 消費者確認

    如上,消費者在消費消息的同時,Rabbit會同步給予消費者一個DeliveryTag,這個DeliveryTag就像咱們數據庫中的主鍵,消費者在消費完畢後拿着這個DeliveryTag去Rabbit確認或拒絕這個消息。

void BasicAck(ulong deliveryTag, bool multiple);

void BasicReject(ulong deliveryTag, bool requeue);

void BasicNack(ulong deliveryTag, bool multiple, bool requeue);
  • deliveryTag:能夠看做消息的編號,它是一個64位的長整型值,最大值是9223372036854775807。
  • requeue:若是requeue 參數設置爲true,則RabbitMQ會從新將這條消息存入隊列,以即可以發送給下一個訂閱的消費者;若是requeue 參數設置爲false,則RabbitMQ當即會把消息從隊列中移除,而不會把它發送給新的消費者。
  • BasicReject命令一次只能拒絕一條消息,若是想要批量拒絕消息,則可使用Basic.Nack這個命令。
  • multiple:在BasicAck中,multiple 參數設置爲true 則表示確認deliveryTag編號以前全部已被當前消費者確認的消息。在BasicNack中,multiple 參數設置爲true 則表示拒絕deliveryTag 編號以前全部未被當前消費者確認的消息。

    說明:將channel.BasicReject 或者channel.BasicNack中的requeue設置爲false ,能夠啓用"死信隊列"的功能。(關於死信隊列請看個人上一篇文章 http://www.javashuo.com/article/p-suqlfqpk-kw.html)。

    上述requeue,都會將消息從新存入隊列發送給下一個消費者(也有多是其它消費者)。關於requeue還有下面一種用法。能夠選擇是否補發給當前的consumer。

//補發消息 true退回到queue中 /false只補發給當前的consumer
channel.BasicRecover(true);

    注意:RabbitMQ僅僅經過Consumer的鏈接中斷來確認該Message並無被正確處理。也就是說,RabbitMQ給了Consumer足夠長的時間來作數據處理。若是忘記了ack,那麼後果很嚴重。當Consumer退出時,Message會從新分發。而後RabbitMQ會佔用愈來愈多的內存,因爲RabbitMQ會長時間運行,這個「內存泄漏」是致命的。

5、消息分發與順序

5.1 消息分發

    當RabbitMQ 隊列擁有多個消費者時,隊列收到的消息將以輪詢(round-robin)的分發方式發送給消費者。每條消息只會發送給訂閱列表裏的一個消費者。這種方式很是適合擴展,並且它是專門爲併發程序設計的。若是如今負載加劇,那麼只須要建立更多的消費者來消費處理消息便可。
    不少時候輪詢的分發機制也不是那麼優雅。默認狀況下,若是有n個消費者,那麼RabbitMQ會將第m條消息分發給第m%n (取餘的方式)個消費者, RabbitMQ 無論消費者是否消費並己經確認了消息。試想一下,若是某些消費者任務繁重,來不及消費那麼多的消息,而某些其餘消費者因爲某些緣由(好比業務邏輯簡單、機器性能卓越等)很快地處理完了所分配到的消息,進而進程空閒,這樣就會形成總體應用吞吐量的降低。那麼該如何處理這種狀況呢?這裏就要用到channel.BasicQos(int prefetchCount)這個方法,channel.BasicQos方法容許限制信道上的消費者所能保持的最大未確認消息的數量。
    舉例說明,在訂閱消費隊列以前,消費端程序調用了channel.BasicQos(5),以後訂閱了某個隊列進行消費。RabbitMQ 會保存一個消費者的列表,每發送一條消息都會爲對應的消費者計數,若是達到了所設定的上限,那麼RabbitMQ 就不會向這個消費者再發送任何消息。直到消費者確認了某條消息以後, RabbitMQ 將相應的計數減1,以後消費者能夠繼續接收消息,直到再次到達計數上限。

注意:Basic.Qos 的使用對於拉模式的消費方式無效.

void BasicQos(uint prefetchSize, ushort prefetchCount, bool global);
  • prefetchCount:容許限制信道上的消費者所能保持的最大未確認消息的數量,設置爲0表示沒有上限。
  • prefetchSize:消費者所能接收未確認消息的整體大小的上限,單位爲B,設置爲0表示沒有上限。
  • global:對於一個信道來講,它能夠同時消費多個隊列,當設置了prefetchCount 大於0 時,這個信道須要和各個隊列協調以確保發送的消息都沒有超過所限定的prefetchCount 的值,這樣會使RabbitMQ 的性能下降,尤爲是這些隊列分散在集羣中的多個Broker節點之中。RabbitMQ 爲了提高相關的性能,在AMQPO-9-1 協議之上從新定義了global這個參數。以下表所示:
global參數 AMQP 0-9-1 RabbitMQ
false 信道上全部的消費者都須要聽從prefetchCount 的限信道上新的消費者須要聽從prefetchCount 的限定值 信道上新的消費者須要聽從prefetchCount 的限定值
true 當前通訊鏈路( Connection) 上全部的消費者都需信道上全部的消費者都須要聽從prefetchCount的限定值 信道上全部的消費者須要聽從prefetchCount 的限定值

注意:

  1. 對於同一個信道上的多個消費者而言,若是設置了prefetchCount 的值,那麼都會生效。
//僞代碼
Consumer consumer1 = ...;
Consumer consumer2 = ...;
channel.BasicQos(10) ; 
channel.BasicConsume("my-queue1" , false , consumer1);
channel.BasicConsume("my-queue2" , false , consumer2);
//兩個消費者各自的能接收到的未確認消息的上限都爲10 。
  1. 若是在訂閱消息以前,既設置了global 爲true 的限制,又設置了global爲false的限制,RabbitMQ 會確保二者都會生效。但會增長RabbitMQ的負載由於RabbitMQ 須要更多的資源來協調完成這些限制。
//僞代碼
Channel channel = ...;
Consumer consumerl = ...;
Consumer consumer2 = ...;
channel.BasicQos(3 , false); 
channel.BasicQos(5 , true); 
channel.BasicConsume("queuel" , false , consumerl) ;
channel.BasicConsume("queue2" , false , consumer2) ;
//這裏每一個消費者最多隻能收到3個未確認的消息,兩個消費者能收到的未確認的消息個數之和的上限爲5

5.2 消息順序

    消息的順序性是指消費者消費到的消息和發送者發佈的消息的順序是一致的。舉個例子,不考慮消息重複的狀況,若是生產者發佈的消息分別爲msgl、msg二、msg3,那麼消費者必然也是按照msgl、msg二、msg3的順序進行消費的。
    目前不少資料顯示RabbitMQ的消息可以保障順序性,這是不正確的,或者說這個觀點有很大的侷限性。在不使用任何RabbitMQ的高級特性,也沒有消息丟失、網絡故障之類異常的狀況發生,而且只有一個消費者的狀況下,最好也只有一個生產者的狀況下能夠保證消息的順序性。若是有多個生產者同時發送消息,沒法肯定消息到達Broker 的先後順序,也就沒法驗證消息的順序性。
    那麼哪些狀況下RabbitMQ 的消息順序性會被打破呢?下面介紹幾種常見的情形。

  • 若是生產者使用了事務機制,在發送消息以後遇到異常進行了事務回滾,那麼須要從新補償發送這條消息,若是補償發送是在另外一個線程實現的,那麼消息在生產者這個源頭就出現了錯序。一樣,若是啓用publisher confirm時,在發生超時、中斷,又或者是收到RabbitMQ的BasicNack命令時,那麼一樣須要補償發送,結果與事務機制同樣會錯序。或者這種說法有些牽強,咱們能夠執拗地認爲消息的順序性保障是從存入隊列以後開始的,而不是在發迭的時候開始的。

  • 考慮另外一種情形,若是生產者發送的消息設置了不一樣的超時時間,井且也設置了死信隊列,總體上來講至關於一個延遲隊列,那麼消費者在消費這個延遲隊列的時候,消息的順序必然不會和生產者發送消息的順序一致。

  • 若是消息設置了優先級,那麼消費者消費到的消息也必然不是順序性的。

  • 若是一個隊列按照先後順序分有msg1, msg二、msg三、msg4這4 個消息,同時有ConsumerA和ConsumerB 這兩個消費者同時訂閱了這個隊列。隊列中的消息輪詢分發到各個消費者之中,ConsumerA 中的消息爲msg1和msg3,ConsumerB中的消息爲msg二、msg4。ConsumerA收到消息msg1以後並不想處理而調用了BasicNack/BasicReject將消息拒絕,與此同時將requeue設置爲true,這樣這條消息就能夠從新存入隊列中。消息msg1以後被髮送到了ConsumerB中,此時ConsumerB己經消費了msg二、msg4,以後再消費msg1.這樣消息順序性也就錯亂了。

    包括但不只限於以上幾種情形會使RabbitMQ 消息錯序。若是要保證消息的順序性,須要業務方使用的時候作進一步的處理。如在消息體內添加全局有序標識等。加羣探討學習:874811168  免費獲取java視頻資料一份

6、消息傳輸保障

    消息可靠傳輸通常是業務系統接入消息中間件時首要考慮的問題,通常消息中間件的消息
傳輸保障分爲三個層級。

  • At most once: 最多一次。消息可能會丟失,但毫不會重複傳輸。
  • At least once: 最少一次。消息毫不會丟失,但可能會重複傳輸。
  • Exactly once: 剛好一次。每條消息確定會被傳輸一次且僅傳輸一次。

    RabbitMQ 支持其中的"最多一次"和"最少一次"。其中"最少一次"投遞實現須要考慮如下這個幾個方面的內容:

  1. 消息生產者須要開啓事務機制或者publisher confirm 機制,以確保消息能夠可靠地傳
    輸到RabbitMQ 中。
  2. 消息生產者須要配合使用mandatory參數或者備份交換器來確保消息可以從交換器
    路由到隊列中,進而可以保存下來而不會被丟棄。
  3. 消息和隊列都須要進行持久化處理,以確保RabbitMQ服務器在遇到異常狀況時不會形成消息丟失。
  4. 消費者在消費消息的同時須要將noAck設置爲false,而後經過手動確認的方式去確認己經正確消費的消息,以免在消費端引發沒必要要的消息丟失。

    "最多一次"的方式就無須考慮以上那些方面,生產者隨意發送,消費者隨意消費,不過這樣很難確保消息不會重複消費。

    "剛好一次"是RabbitMQ目前沒法保障的(目前我也不知道哪一個中間件可以保證)。消費者在消費完一條消息以後向RabbitMQ 發送確認BasicAck命令,此時因爲網絡斷開或者其餘緣由形成RabbitMQ並無收到這個確認命令,那麼RabbitMQ不會將此條消息標記刪除。在從新創建鏈接以後,消費者仍是會消費到這一條消息,這就形成了重複消費。再考慮一種狀況,生產者在使用publisher confirm機制的時候,發送完一條消息等待RabbitMQ 返回確認通知,此時網絡斷開,生產者捕獲到異常狀況,爲了確保消息可靠性選擇從新發送,這樣RabbitMQ中就有兩條一樣的消息,在消費的時候,消費者就會重複消費。而解決重複消費能夠經過消費者冪等等方式來解決。

結束語

    本篇文章,咱們思考了使用RabbitMQ過程當中須要注意的幾個問題,而前兩篇文章對RabbitMQ的概念以及如何使用作了簡單的介紹,相信通過這些介紹已經對RabbitMQ有了基本的瞭解。但這些遠遠不夠,想要更好的利用好RabbitMQ還須要結合咱們的業務場景來更多的去使用它(切記不要爲了使用技術而使用技術!)。關於RabbitMQ的運維篇,會在之後的文章中繼續給你們分享。

 

出處::https://www.cnblogs.com/hunternet

相關文章
相關標籤/搜索