MQ問題收集

MQ問題收集

MQ的優缺點

優點:

  • 異步處理:例如短信通知、終端狀態推送、App推送、用戶註冊等
  • 數據同步:業務數據推送同步
  • 重試補償:記賬失敗重試
  • 系統解耦:通訊上下行、終端異常監控、分佈式事件中心
  • 流量消峯:秒殺場景下的下單處理
  • 發佈訂閱:HSF的服務狀態變化通知、分佈式事件中心
  • 高併發緩衝:日誌服務、監控上報

使用消息隊列比較核心的作用就是:解耦異步削峯

缺點:

  • 系統可用性降低系統引入的外部依賴越多,越容易掛掉?如何保證消息隊列的高可用?
  • 系統複雜度提高怎麼保證消息沒有重複消費?怎麼處理消息丟失的情況?怎麼保證消息傳遞的順序性?
  • 一致性問題A系統處理完了直接返回成功了,人都以爲你這個請求就成功了;但是問題是,要是BCD三個系統那裏,BD兩個系統寫庫成功了,結果C系統寫庫失敗了,咋整?你這數據就不一致了。

重複消費

現在消息隊列一般都能保證atleastonce的,也就是消息至少一次投遞。
爲什麼會出現重複消費的問題呢?
通常都是由於網絡原因造成的?
通常消息被成功消費後消費者都會發送一個成功標誌給MQ,MQ收到這個標誌就表示消息已經成功消費了,就不會在發送給其他消費者了。
但是如果因爲網絡這個標誌沒有送到MQ就丟失了,MQ就認爲這個消息沒有被成功消費,就會再次發送給其他消費者消費,就造成了重複了。

冪等性需要根據業務需求來具體看,但是主要的原理就是去重一般可分爲強校驗、弱校驗

  • 強校驗一般與金融相關的操作都是強校驗
    比如消費者是一個打款服務,在付款成功後都加一條流水紀錄。且兩個操作放入一個事務中。
    再次消費的時候就去流水錶查一下有沒有這條紀錄,如果有表示已經消費過了,直接返回。流水錶也能起到對賬的作用!
    一些簡單的場景也可以依賴數據庫唯一約束實現
  • 弱校驗這個就沒那麼嚴格,重複一下也沒那麼重要的情況。
    可以將ID保存在redisset中,過期時間看情況設置。
    如果ID不能保證唯一可以選擇生產方生成一個token存入redis,消費方在消費後將其刪除(redis的操作能夠保證其原子性,刪除失敗會返回0)

RocketMQDedupListener

消息丟失

一般來講消息丟失的途徑有三個:

  • 生產者弄丟數據
    主流的MQ都有確認機制或者事務機制,可以保證生產者將消息送達到MQ。比如RabbitMQ就有事務模式和生產者confirm模式。
  • 消息隊列弄丟數據
    一般只要開啓MQ的持久化磁盤配置就能解決這個問題,寫入了磁盤就可以.
  • 消費者弄丟數據
    消費者丟數據一般是因爲採用了自動確認消息模式。MQ收到確認消息後會刪除消息,如果這時消費者異常了,那消息就沒了。改用手動確認就能解決這個問題!

順序消息

順序消息的場景可能用的比較少,但是還是有的比如一個電商的下單操作,下單後先減庫存然後生成訂單,這個操作就需要順序執行的
那怎麼保證順序呢?

  1. 首先生產者需要保證入隊的順序,入隊都是亂的那再厲害的MQ也不行.
  2. 一般的MQ都能保證內部Queue是FIFO的(先進先出),但是隻是針對一個Queue,所以在發送消息的時候可以使用Hash取模法將同一個操作的消息發送到同一個Queue裏面,這樣就能保證出隊時是順序的了。
  3. 消費者也需要注意,如果多個消費者同時消費一個隊列。一樣可能出現順序錯亂的情況。這就相當於是多線程消費了!

消息堆積如何處理?

答案:主要是消息的消費速度跟不上生產速度,從而導致消息堆積。解決思路:

1、可能是剛上線的業務,或者大促活動,流量評估不到位,這時需要增加消費組的機器數量,提升整體消費能力

2、也可能是消費端的問題,正常情況,一條消息處理需要10ms,但是優化不到位或者線上bug,現在要500ms,那麼消費端的整體處理速度會下降50倍。這時,我們就要針對性的排查業務代碼。以前就出現過這個問題,當時是數據庫的一條sql沒有命中索引,導致單條消息處理耗時拉長,進而導致消息堆積,線上報警。

如何保證數據一致性問題?

答案:爲了解耦,引入異步消息機制。先進行本地數據庫操作,處理成功後,再發送MQ消息,由消費端進行後續操作。比如:電商訂單下單成功後,要通知扣減庫存。

這兩者一定要保證事務操作,否則就會出現數據不一致問題。這時候,我們就需要引入事務消息來解決這個問題。

另外,在消費環節,也可能出現數據不一致情況。我們可以採用最終一致性原則,增加重試機制

事務消息是如何實現?

  1. 生產者先發送一條半事務消息到MQServer
  2. MQServer收到消息後返回ack確認
  3. 生產者開始執行本地事務
  4. 如果本地事務執行成功,發送commitMQServer,如果失敗,發送rollbackMQserver
  5. 如果MQServer時間未收到生產者的二次確認commitrollbackMQServer對生產者發起反向回查
  6. 生產者查詢事務執行最終狀態
  7. 根據查詢事務狀態,再次提交二次確認到MQServer

img

Kafka如何實現高吞吐量?

答案:

1、消息的批量處理

2、消息壓縮,節省傳輸帶寬和存儲空間

3、零拷貝

4、磁盤的順序寫入

5、pagecache頁緩存,kafka僅寫入內存,由操作系統異步將緩存中的數據刷到磁盤,以及高效的內存讀取

6、分區設計,一個邏輯topic下面掛載N個分區,每個分區可以對應不同的機器消費消息,併發設計。

Kafka爲什麼性能這麼快?4大核心原因詳解

頁緩存技術

爲什麼Kafka性能這麼高?當遇到這個問題的時候很多人都會想到上面的順序寫盤這一點。其實在順序寫盤前面還有頁緩存(PageCache)這一層的優化。

頁緩存是操作系統實現的一種主要的磁盤緩存,以此用來減少對磁盤I/O的操作。具體來說,就是把磁盤中的數據緩存到內存中,把對磁盤的訪問變爲對內存的訪問。爲了彌補性能上的差異,現代操作系統越來越“激進地”將內存作爲磁盤緩存,甚至會非常樂意將所有可用的內存用作磁盤緩存。

當一個進程準備讀取磁盤上的文件內容時,操作系統會先查看待讀取的數據所在的頁(page)是否在頁緩存(pagecache)中,如果存在(命中)則直接返回數據,從而避免了對物理磁盤的I/O操作;如果沒有命中,則操作系統會向磁盤發起讀取請求並將讀取的數據頁存入頁緩存,之後再將數據返回給進程。同樣,如果一個進程需要將數據寫入磁盤,那麼操作系統也會檢測數據對應的頁是否在頁緩存中,如果不存在,則會先在頁緩存中添加相應的頁,最後將數據寫入對應的頁。被修改過後的頁也就變成了髒頁,操作系統會在合適的時間把髒頁中的數據寫入磁盤,以保持數據的一致性。

對一個進程而言,它會在進程內部緩存處理所需的數據,然而這些數據有可能還緩存在操作系統的頁緩存中,因此同一份數據有可能被緩存了兩次。並且,除非使用Direct/IO的方式,否則頁緩存很難被禁止。此外,用過Java的人一般都知道兩點事實,對象的內存開銷非常大,通常會是真實數據大小的幾倍甚至更多,空間使用率低下;Java的垃圾回收會隨着堆內數據的增多而變得越來越慢。基於這些因素,使用文件系統並依賴於頁緩存的做法明顯要優於維護一個進程內緩存或其他結構,至少我們可以省去了一份進程內部的緩存消耗,同時還可以通過結構緊湊的字節碼來替代使用對象的方式以節省更多的空間。如此,我們可以在32GB的機器上使用28GB至30GB的內存而不用擔心GC所帶來的性能問題。此外,即使Kafka服務重啓,頁緩存還是會保持有效,然而進程內的緩存卻需要重建。這樣也極大地簡化了代碼邏輯,因爲維護頁緩存和文件之間的一致性交由操作系統來負責,這樣會比進程內維護更加安全有效。

Kafka中大量使用了頁緩存,這是Kafka實現高吞吐的重要因素之一。雖然消息都是先被寫入頁緩存,然後由操作系統負責具體的刷盤任務的。

Kafka的零拷貝

先來看看非零拷貝的情況,如下圖所示:

img

可以看到數據的拷貝從內存拷貝到Kafka服務進程那塊,又拷貝到Socket緩存那塊,整個過程耗費的時間比較高。

Kafka利用了LinuxsendFile技術(NIO),省去了進程切換和一次數據拷貝,讓性能變得更好,如下圖所示:

img

通過零拷貝技術,就不需要把oscache裏的數據拷貝到應用緩存,再從應用緩存拷貝到Socket緩存了,兩次拷貝都省略了,所以叫做零拷貝。

Kafka爲什麼不支持讀寫分離?

答案:我們知道,生產端寫入消息、消費端拉取消息都是與leader副本交互的,並沒有像mysql數據庫那樣,master負責寫,slave負責讀。

這種設計主要是從兩個方面考慮:

1、數據一致性。一主多從,leader副本的數據同步到follower副本有一定的延時,因此每個follower副本的消息位移也不一樣,而消費端是通過消費位移來控制消息拉取進度,多個副本間要維護同一個消費位移的一致性。如果引入分佈式鎖,保證併發安全,非常耗費性能。

2、實時性。leader副本的數據同步到follower副本有一定的延時,如果網絡較差,延遲會很嚴重,無法滿足實時性業務需求。

綜上考慮,讀寫操作都是針對leader副本進行的,而follower副本主要是用於數據的備份。

MQ框架如何做到高可用性?

答案:以Kafka框架爲例,其他的MQ框架原理類似。

Kafka由多個broker組成,每個broker是一個節點。你創建一個topic,這個topic可以劃分爲多個partition,每個partition存放在不同的broker上,每個partition存放一部分數據,每個partition有多個replica副本。

寫的時候,leader會負責把數據同步到所有follower上去,讀的時候就直接讀leader上的數據即可。

如果某個broker宕機了,沒事兒,那個broker上面的partition在其他機器上都有副本,此時會從follower中重新選舉一個新的leader出來,大家繼續讀寫那個新的leader即可。這就是所謂的高可用性。

關於Kafka,面試官一般喜歡考察哪些問題?

答案:

  • 消息壓縮
  • 消息解壓縮
  • 分區策略
  • 生產者如何實現冪等、事務
  • KafkaBroker是如何存儲數據?備份機制
  • 爲什麼要引入消費組?

RabbitMQ延時隊列,死信

【RabbitMQ】一文帶你搞定RabbitMQ死信隊列

【RabbitMQ】一文帶你搞定RabbitMQ延遲隊列

【RabbitMQ】如何進行消息可靠投遞【上篇】

【RabbitMQ】如何進行消息可靠投遞【下篇】

參考:

字節跳動面試官這樣問消息隊列:高可用、不重複消費、可靠傳輸、順序消費、消息堆積,我整理了下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章