MQ

complexlong發表於2024-09-27

為什麼用MQ?

解耦:

  1. A系統向BCD系統傳送資料,呼叫介面傳送,如果新來的E系統需要資料,舊的D系統不需要資料,那麼就需要頻繁的修改A系統的程式碼,並且還需要考慮BCD系統掛掉的問題,資料非常重要的話,是要重發呢?還是暫時存起來?
  2. 但是如果使用MQ,A系統只需要考慮把資料傳送到MQ就不用操心別的了,不用管傳送給誰,不用考慮人家是否呼叫成功/失敗,是否超時?新來的系統需要就消費MQ的訊息,不需要了就不消費
  3. 透過MQ這種pub/sub釋出訂閱模型,A系統就能和BCD系統徹底解歐,一個系統呼叫了多個系統,呼叫複雜,維護難度高,但是這個呼叫不需要同步呼叫介面才能實現,那麼就可以使用MQ非同步化解藕,

非同步:

  1. A系統接收請求,需要入庫123,同步定義的介面內部處理方式的處理時間為5(傳送請求) + 300+500+800ms(處理請求),響應給使用者的,使用者等待時間過長
  2. 如果採用MQ非同步的方式,A系統傳送訊息到MQ,然後直接A系統直接返回結果,剩下的入庫操作123由其它服務非同步完成,給使用者那麼只需5ms,響應時間非常短。

削峰:

  1. 某個時間段,每秒併發請求數量(QPS)會增加至5000左右,請求會打到mysql,mysql的併發2000就差不多了,這麼大的併發量會把mysql打掛,導致系統崩潰
  2. 如果使用MQ,請求寫入到MQ中,A系統根據自身消費能力去MQ中慢慢消費,這樣的話服務mysql不會被打掛,但是MQ會有大量訊息堆積

缺點:

  1. MQ掛掉,整個系統就完了,系統可用性降低了
  2. 傳送訊息如何保證沒重複消費,如何處理訊息丟失,如何保證訊息處理的順序性
  3. 非同步處理時,你返回給使用者成功,但是入庫123操作有異常,你這到底是成功還是失敗?資料一致性怎麼保證?

訊息佇列選型?

  1. 吞吐量,達到10萬級別
  2. 時效性,在ms級別
  3. 高可用性,分散式架構,一臺掛了另一臺能頂上
  4. 訊息可靠性,經過引數最佳化配置可以做到0丟失

如何保證訊息佇列的高可用?

  1. Producer 與 NameServer叢集中的其中一個節點(隨機選擇)建立長連線,定期從 NameServer 獲取 Topic 路由資訊,並向提供 Topic 服務的 Broker Master/Slave 建立長連線,且定時向 Broker 傳送心跳。Producer 只能將訊息傳送到 Broker master,但是 Consumer 則不一樣,它同時和提供 Topic 服務的 Master 和 Slave建立長連線,既可以從 Broker Master 訂閱訊息,也可以從 Broker Slave 訂閱訊息。

如何保證訊息不被重複消費?

  1. 首先重複消費很正常,各種意外情況都有可能導致重複消費,這不是訊息佇列來保證的,而是我們自己要保證的,比如kafaka的消費資料後提交offset,然後系統重啟可以根據offset繼續消費,但是如果kill程序重啟系統,有可能出現offset沒提交到zookeeper中進而zookeeper也無法給kafaka,那麼下次重啟就會重複消費之前消費過的資訊,如果消費訊息是插入資料庫行為就會導致資料重複插入
  2. 重複消費不可怕,只要做好冪等即可,比如插入資料時先判斷一下是否已經消費過了,具體做就是根據業務場景來做了,比如資料唯一索引(唯一主鍵),redis的寫入set集合,傳送訊息時有全域性唯一id,這樣同一條訊息的全域性唯一id一樣,消費第二次時判斷一下就能判斷出來已經消費過了

如何保證訊息可靠性(處理訊息丟失問題)?

  1. 生產者到MQ丟失問題
    • 事務機制,傳送訊息失敗,生產者收到異常報錯,可以回滾事務,嘗試重發訊息,傳送訊息成功可以提交事務,缺點就是事務是同步的,事務開啟後到提交/回滾之前都是阻塞的
    • 開啟生產者的confirm機制,訊息傳送到MQ後,成功則返回ack訊息,MQ接受失敗則回撥nack介面告知生產者失敗,可以重試,好處就是confirm機制是非同步的,傳送訊息後可以繼續傳送訊息做其他操作,MQ會自動非同步回撥nack介面
  2. MQ自身丟失問題
    • 開啟持久化機制儲存訊息到磁碟中,除非極小機率發生還沒持久化就掛掉的問題,可能性很小
    • 可以配合confim機制,只有訊息持久化到磁碟中才返回ack,如果沒有ack/回撥了nack介面,那麼生產者還可以重試,重發訊息
  3. 消費端丟失問題
    • 也就是剛消費到資料,還沒處理,程序掛了,比如重啟了,那麼MQ認為你消費了資料就相當於丟失了
    • 利用MQ的ack機制,關閉自動ack,手動進行ack,只有處理完才ack,這樣這邊沒處理完,不會ack,MQ那邊不會丟失訊息

如何保證訊息消費的順序?

  1. mysql的binlog日誌傳送到mq,然後再消費出來執行資料同步,其中增刪改的順序就很重要

  2. 問題就在於一個queue繫結多個消費者consumer,同一條訊息只會被一個消費者consumer消費,這樣對於消費順序有要求的場景就無法區分誰先消費,誰先處理,無法保證順序

  3. 解決方案:

    • 一個queue對應一個consumer,弄多個queue,這樣發資料多份到每個queue,每一個消費者consumer消費的都是有序的
    • 或者一個queue對一個consumer,維護多個記憶體佇列
  4. 場景:

    訂單建立,訂單付款,訂單完成,同一個訂單有序即可,不同訂單不用有序,同一分割槽內的訊息保證順序,不同分割槽之間的訊息順序不做要求

  5. RabbitMQ出現消費順序錯亂的情況

    • 為了提高處理效率,一個queue存在多個consumer
    • 一個queue只存在一個consumer,但是為了提高處理效率,consumer中使用了多執行緒進行處理----->不能解決
  6. 保證訊息順序性的方法:

    • 將原來的一個queue拆分成多個queue,每個queue都有一個自己的consumer。該種方案的核心是生產者在投遞訊息的時候根據業務資料關鍵值(例如訂單ID雜湊值對訂單佇列數取模)來將需要保證先後順序的同一類資料(同一個訂單的資料) 傳送到同一個queue當中,這樣正常單執行緒消費就可以
    • 一個queue就一個consumer,在consumer中維護多個記憶體佇列,根據業務資料關鍵值(例如訂單ID雜湊值對記憶體佇列數取模)將訊息加入到不同的記憶體佇列中,然後多個真正負責處理訊息的執行緒去各自對應的記憶體佇列當中獲取訊息進行消費。同分割槽有序相同