1. 程式人生 > >MQ初窺門徑【面試必看的Kafka和RocketMQ儲存區別】

MQ初窺門徑【面試必看的Kafka和RocketMQ儲存區別】

 

MQ初窺門徑

全稱(message queue)訊息佇列,一個用於接收訊息、儲存訊息並轉發訊息的中介軟體

應用場景

用於解決的場景,總之是能接收訊息並轉發訊息

  1. 用於非同步處理,比如A服務做了什麼事情,非同步傳送一個訊息給其他B服務。
  2. 用於削峰,有些服務(秒殺),請求量很高,服務處理不過來,那麼請求先放到訊息佇列裡面,後面按照能力處理,相當於蓄水池。
  3. 應用解耦、訊息通訊等等

總之MQ是可以存放訊息並轉發訊息的中介軟體,場景取決於拿這個能力去解決什麼問題

MQ概念模型

MQ向別人承諾的場景是接收訊息,儲存,並可以轉發訊息

接收訊息

接收訊息,那麼接收誰的訊息,為了說明這個問題,那麼mq需要引入一個概念,叫做生產者,也就是傳送訊息的服務,否則沒有辦法來區分是誰發的訊息,生產者通過網路傳送訊息就可以,中間的細節我們先不探討。

那麼還有一個問題就是訊息傳送給誰?

  1. 我在傳送訊息的時候,指明我要傳送給誰,就像傳送簡訊一樣,你需要指明你要傳送給誰?
    這種方案在使用中是有問題的,因為在現在業務很多場景中, 傳送方其實根本不知道對方是誰,他只是將自己的狀態傳送出來,那麼誰需要這個訊息,誰就接收,第二個如果指明瞭接收方,那麼以後增加一個接收方就要改一下配置或者程式碼,將傳送訊息的人跟接收訊息的人繫結在一起了
    那麼有沒有方案,解耦的最好辦法就是中間人,也叫中間層,我只傳送給第三方,誰要訊息,問第三方要,那麼相當於我把傳送的目標改為傳送給第三方,這裡的第三方就是mq,為了說明說明發送的地方,mq引入了topic的概念,傳送方把訊息傳送到mq指定的一個通道中,以後誰想要這個訊息,就跟mq說我想要這個通道的訊息,也就是傳送方傳送的訊息。

消費訊息

消費訊息,那麼同理的一個問題,誰消費訊息,為了說明那麼mq需要引入一個概念,叫做消費者,也就是消費訊息的服務,否則沒有辦法來區分是誰在接收訊息,消費者通過網路接收訊息就可以了,中間的細節我們先不探討。

那麼問題來了,消費者怎麼說明消費誰的訊息,上文已經說了,通過指明mq的topic,來決定我要哪一類訊息。

至此我們總結一下最後的模型

 

也就是最後生產者和消費者通過MQ的topic概念來實現解耦。

儲存

說到儲存,其實效率才是最主要的,容量不是我們關心的,但是說到儲存,不只是mq,所有需要高效率的儲存其實最後利用的核心都是一樣的。

  1. 隨機寫轉換成順序寫
  2. 集中刷盤
為什麼隨機寫要轉換為順序寫?

第一 現在主流的硬碟是機械硬碟
第二 機械硬碟的機械結構一次讀寫時間 = 尋道時間 + 旋轉延遲 + 讀取資料時間
那麼尋道時間比較長,如果是順序寫,只需要一次尋道時間,關於機械硬碟整個過程,讀者可自行google。

為什麼集中刷盤?

因為每次刷盤都會進行系統呼叫,第二還是跟硬碟的本身屬性有關,無論是機械硬碟還是ssd按照一定塊刷盤會比小資料刷盤效率更好

kafka

為什麼先說kafka的儲存,因為kafka是第一個高效能的訊息中介軟體,其中rocketmq也是借鑑於它,所以我們先說。

 

先給出最終模型變化圖。

  1. 為什麼引入消費組概念?
    上一次模型圖我們還沒有消費組,那麼引入消費組,是因為現在一個服務都有很多例項在執行,消費組是對這群一群機器的一個劃分,他還是一個概念而已。
  2. mq內部也發生了變化,一個topic後面又對應了很多partition,partition也是一個概念,他只不過是把一個topic分成了很多份,每一份叫一個partition,你高興也可以叫他xxx,那麼我們來說說為什麼要分成很多份,一份不行嗎?
    因為現在一個服務有很多例項在執行,如果topic只有一份的話,那麼所有的例項都會來消費訊息,並且都是搶佔我們一個topic,這不可避免引入了多例項競爭,以及他們之間怎麼協調,一堆問題需要關注解決,現在我把topic分成了很多份,每一份只給一個例項,那麼就不會引入各例項之間的競爭問題了,簡化了mq的問題。
  3. 生產組的引入也是一樣的,只不過是一組機器的一個概念,一個邏輯的劃分,生產者傳送訊息原先是發往topic,那麼現在topic分成了很多份,生產者傳送訊息,需要說明發往哪個partition或者隨意分配都可以,只不過最終傳送的訊息,會到一個topic下的一份裡面。無論使用哪種對映方式都可以。

那麼模型出來了,我們說說儲存的問題。
對於kafka,一個partition對應一個檔案,每次訊息來都是順序寫這個檔案。並且是定時刷盤,而不是每次寫都刷盤,所以kafka的寫非常高效。

rocketmq

 

上文我們說了rocketmq借鑑於kafka,所以儲存借鑑了kafka,但是rocketmq不是僅僅把partition改成了ConsumeQueue,在這裡做了變化,原先kafka,裡面partition儲存的是整個訊息,但是現在ConsumeQueue裡面是儲存訊息的儲存地址,但是不儲存訊息了。

 

現在每個ConsumeQueue儲存的是每個訊息在commitlog這個檔案的地址,但是訊息存在於commitlog中。
也就是所有的訊息體都寫在了一個檔案裡面,每個ConsumeQueue只是儲存這個訊息在commitlog中地址。

儲存對比

  1. 訊息體儲存的變化
    那麼我們先來看看kafka,假設partition有1000個,一個partition是順序寫一個檔案,總體上就是1000個檔案的順序寫,是不是就變成了隨機寫,所以當partition增加到一定數目後,kafka效能就會下降。而rocketmq是把訊息都寫到一個CommitLog檔案中,所以相當於一個檔案的順序寫。

  2. 為什麼索引檔案(ConsumeQueue)的增加對效能影響沒有那麼partition大?
    (kafka也有索引檔案,在這裡只是想說明索引檔案的增加跟partition增加的區別)
    雖然rocketmq是把訊息都寫到一個CommitLog檔案中,但是按照上面的例項會有1000個ConsumeQueue,也就是一千個檔案,那麼為什麼就沒有把順序寫變成隨機寫,帶來效能的下降呢?首先就要介紹linux的pagecache

     

    我們平常呼叫write或者fwrite的時候,資料還沒有寫到磁碟上,只是寫到一個核心的快取(pagecache),只有當我們主動呼叫flush的時候才會寫到硬碟中。或者需要回寫的pagecache佔總記憶體一定比例的時候或者一個應該回寫的page超過一定時間還沒有寫磁碟的時候,核心會將這些資料通過後臺程序寫到磁碟中(總結就是達到一定比例,或者多長時間還沒有回寫,會被核心自動回寫)。

    然後我們現在來看看為什麼大量索引檔案的順序寫沒有像partition一樣導致效能明顯下降。ConsumeQueue只儲存了(CommitLog Offet + Size + Message Tag Hashcode),一共20個位元組,那麼當commitlog定時任務刷盤之後,應該回寫的pagecache的比例就會下降很多,那麼ConsumeQueue的部分可以不用刷盤,就相當於ConsumeQueue的內容會等待比較長的時間聚合批量寫入,而kafka每個partition都是儲存的訊息體,因為訊息體都相對較大,基本在kb之上。
    當一個partition刷盤的時候,應該回寫的pagecache的比例降低的並不多,不能阻止其他partition的刷盤,所以會大量存在多個partition同時刷盤的場景,變成隨機寫。但是rocketmq訊息都會寫入一個commitLog,也就是順序寫。

所以我們總結下這個點:

1、consumerQueue訊息格式大小固定(20位元組),寫入pagecache之後被觸發刷盤頻率相對較低。就是因為每次寫入的訊息小,造成他佔用的pagecache少,主要佔用方一旦被清理,那麼他就可以不用清理了。

2、kafka中多partition會存在隨機寫的可能性,partition之間刷盤的衝撞率會高,但是rocketmq中commitLog都是順序寫。

 

  歡迎關注博主公眾號,後面會持續更新mq系列知識點,一起討論。

       &n