MQ-初識RabbitMQ和可靠性保障

模型架構

相關概念

Producer生產者:生產者創建消息,然後發佈到RabbitMQ中。消息包含兩部分:消息體(Payload)和標籤(Label)。消息體是一個帶有業務邏輯結構的數據。消息標籤用來表述這條消息,比如一個交換器的名稱和一個路由鍵。生產者把消息交給RabbitMQ之後會根據標籤把消息發送給感興趣的消費者。

Consumer消費者:接收消息的一方。消費者連接到RabbitMQ服務器,並訂閱到隊列上。當消費一條消息時,只是消費消息體,在消息路由的過程中,消息的標籤會丟棄,存入到隊列的只有消息體。

Broker:RabbitMQ的服務節點。

Queue隊列:RabbitMQ的內部對象。用於存儲消息。多個消費者可以訂閱同一個隊列,這時隊列中的消息會被平均分攤(Round-Robin,即輪詢)給多個消費者。

Exchange交換器:生產者將消息發送到交換器,由交換器將消息路由到一個或多個隊列中。如果路由不到,或許會返回給生產者,或許直接丟棄。交換器有四種類型,不同的類型有自己的路由策略。交換器類型:

  1. fanout:把所有發送到該交換器的消息路由到所有與該交換器綁定的隊列中,無視RoutingKey和BindingKey的綁定規則。
  2. direct:把消息路由到BindingKey和RoutingKey完全匹配的隊列中。
  3. topic:與direct類型類似,但是更加靈活:支持用“.”號分隔字符串,支持“*”和“#”用於模糊匹配,其中“*”用於匹配一個單詞,“#”用於匹配多規格單詞(可以是零個)。
  4. headers:不依賴路由鍵的匹配規則,而是根據發送的消息內容中的 headers(K-V形式) 屬性進行匹配。

RoutingKey路由鍵:生產者將消息發送給交換器的時候,一般會指定一個RoutingKey,用來指定這個消息的路由規則,而這個RoutingKey需要與交換器類型和綁定鍵(BindingKey)聯合使用才能生效。

Binding綁定:RabbitMQ中通過綁定將交換器與隊列關聯起來,在綁定的時候一般會指定一個綁定鍵(BindingKey),這樣RabbitMQ就直到如何正確的將消息路由到隊列了。

Connection連接&Channel信道:生產者或消費者連接到RabbitMQ Broker,建立一個連接(Connection),開啓一個信道(Channel)。這個鏈接就是一條TCP連接,一旦TCP連接建立起來,客戶端緊接着可以創建一個AMQP信道(Channel),每條信道都會被指派一個唯一ID。信道是建立在Connection之上的虛擬連接,RabbitMQ處理的每條AMQP指令都是通過信道完成的。RabbitMQ採用類似NIO(Non-blocking I/O)的做法,選擇TCP連接服用,以減少性能開銷,且便於管理。

AMQP協議:RabbitMQ是遵從AMQP協議的,換句話說RabbitMQ就是AMQP協議的Erlang實現。AMQP協議本身包括三層:

  • Module Layer:位於協議最高層,只要定義了一些供客戶端調用的命令,客戶端可以利用這些命令實現自己的業務邏輯。
  • Session Layer:位於中間層,只要負責將客戶端的命令發送給服務器,再將服務器端的應答返回給客戶端,主要爲客戶端與服務端之間的通信提供可靠性同步機制和錯誤處理。
  • Transport Layer:位於最底層,主要傳輸二進制數據流,提供幀的處理/信道複用/錯誤檢測和數據表示等。

數據流轉流程

生產者核心參數

mandatory:布爾類型

  • true:交換器無法找到符合條件的隊列時,RabbitMQ調用Basic.return命令將消息返回給生產者。
  • false:出現上述情形,消息直接丟棄。

此參數需要生產者配合調用channel.addReturnListener來添加ReturnListener監聽器來實現。

immediate:布爾類型

true:如果交換器在將消息路由到隊列時發現隊列上並不存在任何消費者,那麼這條消息將不會存入隊列中。RabbitMQ調用Basic.return命令將消息返回給生產者。

RabbitMQ3.0版本開始去掉了immediate參數支持,官方解釋:immediate參數會影響隊列的性能,增加了代碼的複雜性,建議採用TTL和DLX的方法代替

Alternate Exchange備份交換器

簡稱AE,生產者在發送消息的時候如果不設置mandatory參數,那麼消息在未被路由的情況下將會丟失;如果設置了mandatory參數,那麼需要添加RetuenListener的編程邏輯,生產者的代碼將變得複雜。如果既不想複雜化生產者的代碼邏輯,又不想消息丟失,那麼可以使用備份交換器,這樣可以將未被路由的消息存儲在RabbitMQ中國呢,在需要的時候去處理這些消息。

可以通過在生命交換器(調用channel.exchangeDeclare方法)的時候添加 alternate-exchange 參數來實現,也可以通過策略(Policy)的方式實現,如果兩者同時使用,則前者的優先級更高,會覆蓋掉後者。

備份交換器和普通交換器沒有太大區別,爲了方便使用,建議設置爲 fanout 類型,以避免路由鍵匹配規則帶來的不便。

注意事項:

  • 如果設置的備份交換器不存在,客戶端和服務端都不會有異常出現,此時消息會丟失。
  • 如果備份交換器沒有綁定任何隊列,
  • 如果備份交換器沒有任何匹配的隊列,
  • 如果備份交換器和mandatory參數一起使用,那麼mandatory參數無效

TTL過期時間

Time to Live的簡稱,RabbitMQ可以對消息和隊列設置TTL。

消息在隊列中的生存時間一旦超過TTl值,就會變成“死信”(Dead Message),消費者將無法再收到該消息。

通過在 channel.queueDeclare 方法中加入 x-message-ttl 參數實現消息的TTL設置,單位毫秒。也可以通過策略(Policy)的方式實現。還可以通過調用 HTTP-API 接口設置。

通過 channel.queueDeclare 方法中的 x-expires 參數實現控制隊列被自動刪除前處於未使用狀態的時間,單位毫秒。未使用的意思是隊列上沒有任何消費者,隊列也沒有被重新聲明,並且在過期時間段內也未調用過 Basic.Get 命令。

注意事項:

  • 如果設置消息TTL爲0,則表示除非此時可以直接將消息投遞到消費者,否則該消息會被立即丟棄。這個特性可以部分替代RabbitMQ3.0版本之前的immediate參數,之所以是部分替代,是因爲immediate參數在投遞失敗時會調用Basic.Return將消息返回(這個功能可以使用死信隊列實現)。
  • 隊列TTL不能設置爲0
  • 如果同時設置了消息和隊列的TTL,則消息的TTL以兩者中較小的那個數值爲準。

DLX死信隊列

全稱 Dead-Letter_Exchange。當消息在一個隊列中變成死信(dead message)之後,它能被重新發送到DLX交換器中,綁定DXL交換器的隊列稱之爲死信隊列。

通過在 channel.queueDeclare 方法中設置 x-dead-letter-exchange 參數來爲這個隊列添加DLX。可以爲這個DLX添加指定路由鍵,如果沒有添加,則使用原隊列的路由鍵。

消息變成死信一般是由於以下幾種情況:

  • 消息被拒絕(Basic.Reject/Basic.Nack),並設置requeue參數爲false。
  • 消息過期。
  • 隊列達到最大長度。

DLX也是一個正常的交換器,可以監聽這個隊列中的消息進行響應的處理,與將消息的TTL設置爲0配合使用可以彌補immediate參數的功能。

延遲隊列

在AMQP協議中或者RabbitMQ本身並沒有直接支持延遲隊列的功能,但是可以通過DLX和TTL實現延遲隊列。

優先級隊列

優先級高的隊列具有高的優先權,優先級高的消息具備優先被消費的特權。

通過設置隊列的 x-max-priority 參數實現。

注意事項:

  • 如果在消費者的消費速度大於生產者的速度,且Broker中沒有消息堆積的情況下,對消息設置優先級就沒有什麼實際意義。

持久化

持久化可以提高RabbitMQ的可靠性,以防在異常情況(重啓/關閉/宕機等)下的數據丟失。RabbitMQ的持久化分爲三個部分:交換器持久化/隊列持久化和消息持久化。

交換器持久化:如果交換器不設置持久化,那麼在RabbitMQ服務重啓後,交換器的元數據會丟失,不過消息不會丟失,只是生產者不能將效法發送到這個交換器中了。對於一個長期使用的交換器來說,建議將其設置爲持久化的。

通過在聲明交換器時將 durable 參數設置爲true實現。

隊列持久化:保證隊列本身的元數據不會因異常情況丟失,但是不能保證內部存儲的消息不會丟失。

通過在聲明隊列時將 durable 參數設置爲true實現。

消息持久化:通過將消息的投遞模式(BasicProperties中的deliveryMode屬性)設置爲2實現。單單設置隊列或消息持久化都不能保證消息不丟失,必須同時設置。

生產者確認

生產者確認機制,是消息正確到達RabbitMQ服務器的保證。RabbitMQ提供兩種解決方案:

  1. 事務機制
  2. 發送方確認(publisher confirm)機制

事務機制:

相關方法有三個:

  1. channel.txSelect:將當前信道設置成事務模式
  2. channel.txCommit:用於提交事務
  3. channel.txRollBack:用於回滾事務

注意:事務機制在一條消息發送之後會使發送端阻塞,以等待RabbitMQ的迴應,之後才能繼續發送下一條消息,嚴重降低RabbitMQ的消息吞吐量。

發送方確認(publisher confirm)機制:

生產者通過調用channel.congirmSelect方法(即Confirm.Select命令)將信道設置成 confirm(確認)模式,所有在該信道中發佈的消息都會被指派一個唯一ID(從1開始),當消息被成功投遞到所有匹配的隊列時,RabbitMQ將發送一個確認(Basic.Ack)給生產者(包含消息的唯一ID)。如果消息是持久化的,那麼確認的消息會在寫入磁盤後發出。此外,也可以設置channel.basicAck方法中的 multiple 參數,表示到這個序號之前的所有消息都已經得到了處理。如果RabbitMQ因爲自身內部錯誤導致消息投遞失敗,會發送一條nack(Basic.Nack)命令給生產者。

confirm機制是異步的,生產者在發送消息之後,在等待信道返回確認的同時繼續發送下體條消息。

注意事項:

  • 事務機制和confirm機制是互斥的,同時使用RabbitMQ將會報錯。
  • 事務機制和confirm機制是確保消息能夠正確發送至RabbitMQ交換器,如果此交換器沒有匹配的隊列,消息將會丟失。所以需要配合mandatory參數或者備份交換器一起使用來提高消息傳輸的可靠性。

消費端要點

消息分發

RabbitMQ默認以輪詢(round-robin)的分發方式發送消息給消費者,但是如果某些消費者由於某些原因(如機器性能/網絡吞吐量等)出現消費能力不同,將造成整體應用的吞吐量下降。此時可以根據消費者消費能力的不同,在消費者訂閱消費隊列之前,調用 channel.basicQos(int num) 方法,來定製分發策略。

RabbitMQ會保存一個消費者列表,每發送一條消息都對爲對應的消費者計數,如果達到了所設定的上限,那麼RabbitMQ將不會向這個消費者繼續發送消息,直到消費者確認了某條消息之後,RabbitMQ將響應的計數減1,之後消費者可以繼續接收消息。

注意事項:

  • Basic.Qos對於拉模式無效

消息順序性

消息的順序性是指消費者消費到的消息和發送者發佈的消息的順序是一致的。

消息失序的情況(包括但不限於):

  • 生產者使用事務機制,在發送消息遇到異常進行回滾,重新補發消息,如果消息補償發送是在另一個線程實現。
  • 生產者使用confirm機制,在發成超時/中斷,或者收到RabbitMQ的nack時,同樣遇到和事務機制相同的問題。
  • 生產者對發送的消息設置了不同的TTL,並且設置了死信隊列,整體上來說是一個延遲隊列。
  • 消息設置優先級。
  • 一個隊列按順序發送了m1/m2/m3/m4四個消息,同時這個隊列有ConsumerA和ConsumerB兩個消費者,ConsumerA收到m1和m3,CunsumerB收到m2和m4,此時CunsumerA收到m1時調用了Basic.Nack/.Reject將消息拒絕,與此同時將requeue設置爲true,這樣m1由重新進入隊列中,之後m1又被髮送到了ConsumerB,此時CunsumerB已經消費了m2和m4,之後再消費m1

如果要保證消息的順序性,需要在業務中做進一步處理,比如在消息體內添加全局有序標識,維護一個有序隊列來根據這個標識存儲消息。

消息傳輸保障

一般消息中間件的消息傳輸保障分爲三個層級:

  1. At most once:最多一次。消息可能會丟失,但絕不會重複。
  2. At least once:最少一次。消息不會丟失,但可能會重複。
  3. Exactly once:恰好一次。消息不會丟失,也不會重複。

RabbitMQ支持“最多一次”和“最少一次”。其中“最少一次”需要考慮以下內容:

  1. 生產者開啓事務機制或confirm機制,確保消息可靠的傳輸到RabbitMQ中。
  2. 生產者需要配合使用mandatory參數或者備份交換器,確保消息能夠從交換器路由到隊列中而不被丟棄。
  3. 消息和隊列都進行持久化處理,確保RabbitMQ服務器在遇到異常情況時消息不會丟失。
  4. 消費者在消費消息時開啓手動確認(將autoAck設置爲false)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章