RabbitMQ 問題收集
RabbitMQ可靠性
發送方確認模式
- 將信道設置成
confirm
模式(發送方確認模式),則所有在信道上發佈的消息都會被指派一個唯一的ID
。 - 一旦消息被投遞到目的隊列後,或者消息被寫入磁盤後(可持久化的消息),信道會發送一個確認給生產者(包含消息唯一 ID)。
- 如果
RabbitMQ
發生內部錯誤從而導致消息丟失,會發送一條nack
(notacknowledged
,未確認)消息。 - 發送方確認模式是異步的,生產者應用程序在等待確認的同時,可以繼續發送消息。當確認消息到達生產者應用程序,生產者應用程序的回調方法就會被觸發來處理確認消息。
接收方確認機制
- 消費者接收每一條消息後都必須進行確認(消息接收和消息確認是兩個不同操作)。只有消費者確認了消息,
RabbitMQ
才能安全地把消息從隊列中刪除。 - 這裏並沒有用到超時機制,
RabbitMQ
僅通過Consumer
的連接中斷來確認是否需要重新發送消息。也就是說,只要連接不中斷,RabbitMQ
給了Consumer
足夠長的時間來處理消息。保證數據的最終一致性;
下面羅列幾種特殊情況
- 如果消費者接收到消息,在確認之前斷開了連接或取消訂閱,
RabbitMQ
會認爲消息沒有被分發,然後重新分發給下一個訂閱的消費者。(可能存在消息重複消費的隱患,需要去重) - 如果消費者接收到消息卻沒有確認消息,連接也未斷開,則
RabbitMQ
認爲該消費者繁忙,將不會給該消費者分發更多的消息。
如何保證RabbitMQ
消息的可靠傳輸?
- 消息不可靠的情況可能是消息丟失,劫持等原因;
- 丟失又分爲:生產者丟失消息、消息列表丟失消息、消費者丟失消息;
-
生產者丟失消息:從生產者弄丟數據這個角度來看,
RabbitMQ
提供transaction
和confirm
模式來確保生產者不丟消息;transaction
機制就是說:發送消息前,開啓事務(channel.txSelect()),然後發送消息,如果發送過程中出現什麼異常,事務就會回滾(channel.txRollback()),如果發送成功則提交事務(channel.txCommit())。然而,這種方式有個缺點:吞吐量下降;confirm
模式用的居多:一旦channel
進入confirm
模式,所有在該信道上發佈的消息都將會被指派一個唯一的ID(從1開始),一旦消息被投遞到所有匹配的隊列之後;rabbitMQ
就會發送一個ACK
給生產者(包含消息的唯一ID),這就使得生產者知道消息已經正確到達目的隊列了;如果
rabbitMQ
沒能處理該消息,則會發送一個Nack消息給你,你可以進行重試操作。 -
消息隊列丟數據:消息持久化。
處理消息隊列丟數據的情況,一般是開啓持久化磁盤的配置。
這個持久化配置可以和
confirm
機制配合使用,你可以在消息持久化磁盤後,再給生產者發送一個Ack
信號。這樣,如果消息持久化磁盤之前,
rabbitMQ
陣亡了,那麼生產者收不到Ack
信號,生產者會自動重發。那麼如何持久化呢?
這裏順便說一下吧,其實也很容易,就下面兩步
1. 將
queue
的持久化標識durable
設置爲true
,則代表是一個持久的隊列 2. 發送消息的時候將
deliveryMode=2
這樣設置以後,即使
rabbitMQ
掛了,重啓後也能恢復數據 -
消費者丟失消息:消費者丟數據一般是因爲採用了自動確認消息模式,改爲手動確認消息即可!
消費者在收到消息之後,處理消息之前,會自動回覆
RabbitMQ
已收到消息;如果這時處理消息失敗,就會丟失該消息;
解決方案:處理消息成功後,手動回覆確認消息。
爲什麼不應該對所有的 message 都使用持久化機制?
- 首先,必然導致性能的下降,因爲寫磁盤比寫
RAM
慢的多,message
的吞吐量可能有 10 倍的差距。 - 其次,
message
的持久化機制用在RabbitMQ
的內置cluster
方案時會出現“坑爹”問題。矛盾點在於,若message
設置了persistent
屬性,但queue
未設置durable
屬性,那麼當該queue
的owner node
出現異常後,在未重建該queue
前,發往該queue
的message
將被blackholed
;若message
設置了persistent
屬性,同時queue
也設置了durable
屬性,那麼當queue
的owner node
異常且無法重啓的情況下,則該queue
無法在其他node
上重建,只能等待其owner node
重啓後,才能恢復該queue
的使用,而在這段時間內發送給該queue
的message
將被blackholed
。 - 所以,是否要對 message 進行持久化,需要綜合考慮性能需要,以及可能遇到的問題。若想達到
100,000
條/秒以上的消息吞吐量(單RabbitMQ
服務器),則要麼使用其他的方式來確保message
的可靠delivery
,要麼使用非常快速的存儲系統以支持全持久化(例如使用 SSD)。另外一種處理原則是:僅對關鍵消息作持久化處理(根據業務重要程度),且應該保證關鍵消息的量不會導致性能瓶頸。
如何保證高可用的?RabbitMQ 的集羣
RabbitMQ
是比較有代表性的,因爲是基於主從(非分佈式)做高可用性的,我們就以RabbitMQ
爲例子講解第一種 MQ 的高可用性怎麼實現。RabbitMQ
有三種模式:單機模式、普通集羣模式、鏡像集羣模式。
-
單機模式,就是
Demo
級別的,一般就是你本地啓動了玩玩兒的?,沒人生產用單機模式 -
普通集羣模式:
- 意思就是在多臺機器上啓動多個
RabbitMQ
實例,每個機器啓動一個。 - 你創建的
queue
,只會放在一個RabbitMQ
實例上,但是每個實例都同步queue
的元數據(元數據可以認爲是queue
的一些配置信息,通過元數據,可以找到queue
所在實例)。你消費的時候,實際上如果連接到了另外一個實例,那麼那個實例會從 queue 所在實例上拉取數據過來。這方案主要是提高吞吐量的,就是說讓集羣中多個節點來服務某個queue
的讀寫操作。
- 意思就是在多臺機器上啓動多個
-
鏡像集羣模式:
- 這種模式,纔是所謂的
RabbitMQ
的高可用模式。跟普通集羣模式不一樣的是,在鏡像集羣模式下,你創建的queue
,無論元數據還是queue
裏的消息都會存在於多個實例上,就是說,每個RabbitMQ
節點都有這個queue
的一個完整鏡像,包含queue
的全部數據的意思。然後每次你寫消息到queue
的時候,都會自動把消息同步到多個實例的queue
上。RabbitMQ
有很好的管理控制檯,就是在後臺新增一個策略,這個策略是鏡像集羣模式的策略,指定的時候是可以要求數據同步到所有節點的,也可以要求同步到指定數量的節點,再次創建queue
的時候,應用這個策略,就會自動將數據同步到其他的節點上去了。 - 這樣的好處在於,你任何一個機器宕機了,沒事兒,其它機器(節點)還包含了這個
queue
的完整數據,別的consumer
都可以到其它節點上去消費數據。壞處在於,第一,這個性能開銷也太大了吧,消息需要同步到所有機器上,導致網絡帶寬壓力和消耗很重!RabbitMQ
一個queue
的數據都是放在一個節點裏的,鏡像集羣下,也是每個節點都放這個queue
的完整數據。
- 這種模式,纔是所謂的
如何解決消息隊列的延時以及過期失效問題?消息隊列滿了以後該怎麼處理?有幾百萬消息持續積壓幾小時,怎麼辦?
- 消息積壓處理辦法:臨時緊急擴容:
- 先修復 consumer 的問題,確保其恢復消費速度,然後將現有 cnosumer 都停掉。
新建一個 topic,partition 是原來的 10 倍,臨時建立好原先 10 倍的 queue 數量。
然後寫一個臨時的分發數據的 consumer 程序,這個程序部署上去消費積壓的數據,消費之後不做耗時的處理,直接均勻輪詢寫入臨時建立好的 10 倍數量的 queue。
接着臨時徵用 10 倍的機器來部署 consumer,每一批 consumer 消費一個臨時 queue 的數據。這種做法相當於是臨時將 queue 資源和 consumer 資源擴大 10 倍,以正常的 10 倍速度來消費數據。
等快速消費完積壓數據之後,得恢復原先部署的架構,重新用原先的 consumer 機器來消費消息。
MQ中消息失效:假設你用的是 RabbitMQ,RabbtiMQ 是可以設置過期時間的,也就是 TTL。如果消息在 queue 中積壓超過一定的時間就會被 RabbitMQ 給清理掉,這個數據就沒了。那這就是第二個坑了。這就不是說數據會大量積壓在 mq 裏,而是大量的數據會直接搞丟。我們可以採取一個方案,就是批量重導,這個我們之前線上也有類似的場景幹過。就是大量積壓的時候,我們當時就直接丟棄數據了,然後等過了高峯期以後,比如大家一起喝咖啡熬夜到晚上12點以後,用戶都睡覺了。這個時候我們就開始寫程序,將丟失的那批數據,寫個臨時程序,一點一點的查出來,然後重新灌入 mq 裏面去,把白天丟的數據給他補回來。也只能是這樣了。假設 1 萬個訂單積壓在 mq 裏面,沒有處理,其中 1000 個訂單都丟了,你只能手動寫程序把那 1000 個訂單給查出來,手動發到 mq 裏去再補一次。 - mq消息隊列塊滿了:如果消息積壓在 mq 裏,你很長時間都沒有處理掉,此時導致 mq 都快寫滿了,咋辦?這個還有別的辦法嗎?沒有,誰讓你第一個方案執行的太慢了,你臨時寫程序,接入數據來消費,消費一個丟棄一個,都不要了,快速消費掉所有的消息。然後走第二個方案,到了晚上再補數據吧。
設計MQ思路
- 比如說這個消息隊列系統,我們從以下幾個角度來考慮一下:
- 首先這個 mq 得支持可伸縮性吧,就是需要的時候快速擴容,就可以增加吞吐量和容量,那怎麼搞?設計個分佈式的系統唄,參照一下 kafka 的設計理念,broker -> topic -> partition,每個 partition 放一個機器,就存一部分數據。如果現在資源不夠了,簡單啊,給 topic 增加 partition,然後做數據遷移,增加機器,不就可以存放更多數據,提供更高的吞吐量了?
- 其次你得考慮一下這個 mq 的數據要不要落地磁盤吧?那肯定要了,落磁盤才能保證別進程掛了數據就丟了。那落磁盤的時候怎麼落啊?順序寫,這樣就沒有磁盤隨機讀寫的尋址開銷,磁盤順序讀寫的性能是很高的,這就是 kafka 的思路。
- 其次你考慮一下你的 mq 的可用性啊?這個事兒,具體參考之前可用性那個環節講解的 kafka 的高可用保障機制。多副本 -> leader & follower -> broker 掛了重新選舉 leader 即可對外服務。
- 能不能支持數據 0 丟失啊?可以呀,有點複雜的。