Kafka、ActiveMQ、RabbitMQ、RocketMQ 區別以及高可用原理
為什麼使用訊息佇列
其實就是問問你訊息佇列都有哪些使用場景,然後你專案裡具體是什麼場景,說說你在這個場景裡用訊息佇列是什麼?
面試官問你這個問題,期望的一個回答是說,你們公司有個什麼業務場景,這個業務場景有個什麼技術挑戰,如果不用 MQ 可能會很麻煩,但是你現在用了 MQ 之後帶給了你很多的好處。
先說一下訊息佇列常見的使用場景吧,其實場景有很多,但是比較核心的有 3 個:解耦、非同步、削峰。
解耦
看這麼個場景。A 系統傳送資料到 BCD 三個系統,通過介面呼叫傳送。如果 E 系統也要這個資料呢?那如果 C 系統現在不需要了呢?A 系統負責人幾乎崩潰......
在這個場景中,A 系統跟其它各種亂七八糟的系統嚴重耦合,A 系統產生一條比較關鍵的資料,很多系統都需要 A 系統將這個資料傳送過來。A 系統要時時刻刻考慮 BCDE 四個系統如果掛了該咋辦?要不要重發,要不要把訊息存起來?頭髮都白了啊!
如果使用 MQ,A 系統產生一條資料,傳送到 MQ 裡面去,哪個系統需要資料自己去 MQ 裡面消費。如果新系統需要資料,直接從 MQ 裡消費即可;如果某個系統不需要這條資料了,就取消對 MQ 訊息的消費即可。這樣下來,A 系統壓根兒不需要去考慮要給誰傳送資料,不需要維護這個程式碼,也不需要考慮人家是否呼叫成功、失敗超時等情況。
總結:通過一個 MQ,Pub/Sub 釋出訂閱訊息這麼一個模型,A 系統就跟其它系統徹底解耦了。
面試技巧:你需要去考慮一下你負責的系統中是否有類似的場景,就是一個系統或者一個模組,呼叫了多個系統或者模組,互相之間的呼叫很複雜,維護起來很麻煩。但是其實這個呼叫是不需要直接同步呼叫介面的,如果用 MQ 給它非同步化解耦,也是可以的,你就需要去考慮在你的專案裡,是不是可以運用這個 MQ 去進行系統的解耦。在簡歷中體現出來這塊東西,用 MQ 作解耦。
非同步
再來看一個場景,A 系統接收一個請求,需要在自己本地寫庫,還需要在 BCD 三個系統寫庫,自己本地寫庫要 3ms,BCD 三個系統分別寫庫要 300ms、450ms、200ms。最終請求總延時是 3 + 300 + 450 + 200 = 953ms,接近 1s,使用者感覺搞個什麼東西,慢死了慢死了。使用者通過瀏覽器發起請求,等待個 1s,這幾乎是不可接受的。
一般網際網路類的企業,對於使用者直接的操作,一般要求是每個請求都必須在 200 ms 以內完成,對使用者幾乎是無感知的。
如果使用 MQ,那麼 A 系統連續傳送 3 條訊息到 MQ 佇列中,假如耗時 5ms,A 系統從接受一個請求到返回響應給使用者,總時長是 3 + 5 = 8ms,對於使用者而言,其實感覺上就是點個按鈕,8ms 以後就直接返回了,爽!網站做得真好,真快!
削峰
每天 0:00 到 12:00,A 系統風平浪靜,每秒併發請求數量就 50 個。結果每次一到 12:00 ~ 13:00 ,每秒併發請求數量突然會暴增到 5k+ 條。但是系統是直接基於 MySQL 的,大量的請求湧入 MySQL,每秒鐘對 MySQL 執行約 5k 條 SQL。
一般的 MySQL,扛到每秒 2k 個請求就差不多了,如果每秒請求到 5k 的話,可能就直接把 MySQL 給打死了,導致系統崩潰,使用者也就沒法再使用系統了。
但是高峰期一過,到了下午的時候,就成了低峰期,可能也就 1w 的使用者同時在網站上操作,每秒中的請求數量可能也就 50 個請求,對整個系統幾乎沒有任何的壓力。
如果使用 MQ,每秒 5k 個請求寫入 MQ,A 系統每秒鐘最多處理 2k 個請求,因為 MySQL 每秒鐘最多處理 2k 個。A 系統從 MQ 中慢慢拉取請求,每秒鐘就拉取 2k 個請求,不要超過自己每秒能處理的最大請求數量就 ok,這樣下來,哪怕是高峰期的時候,A 系統也絕對不會掛掉。而 MQ 每秒鐘 5k 個請求進來,就 2k 個請求出去,結果就導致在中午高峰期(1 個小時),可能有幾十萬甚至幾百萬的請求積壓在 MQ 中。
這個短暫的高峰期積壓是 ok 的,因為高峰期過了之後,每秒鐘就 50 個請求進 MQ,但是 A 系統依然會按照每秒 2k 個請求的速度在處理。所以說,只要高峰期一過,A 系統就會快速將積壓的訊息給解決掉。
訊息佇列有什麼優缺點
優點上面已經說了,就是在特殊場景下有其對應的好處,解耦、非同步、削峰。
缺點有以下幾個:
-
系統可用性降低
系統引入的外部依賴越多,越容易掛掉。本來你就是 A 系統呼叫 BCD 三個系統的介面就好了,人 ABCD 四個系統好好的,沒啥問題,你偏加個 MQ 進來,萬一 MQ 掛了咋整,MQ 一掛,整套系統崩潰的,你不就完了?如何保證訊息佇列的高可用
-
系統複雜度提高
硬生生加個 MQ 進來,你怎麼保證訊息沒有重複消費?怎麼處理訊息丟失的情況?怎麼保證訊息傳遞的順序性?頭大頭大,問題一大堆,痛苦不已。
-
一致性問題
A 系統處理完了直接返回成功了,人都以為你這個請求就成功了;但是問題是,要是 BCD 三個系統那裡,BD 兩個系統寫庫成功了,結果 C 系統寫庫失敗了,咋整?你這資料就不一致了。
所以訊息佇列實際是一種非常複雜的架構,你引入它有很多好處,但是也得針對它帶來的壞處做各種額外的技術方案和架構來規避掉,做好之後,你會發現,媽呀,系統複雜度提升了一個數量級,也許是複雜了 10 倍。但是關鍵時刻,用,還是得用的。
Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什麼優缺點?
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
單機吞吐量 | 萬級,比 RocketMQ、Kafka 低一個數量級 | 同 ActiveMQ | 10 萬級,支撐高吞吐 | 10 萬級,高吞吐,一般配合大資料類的系統來進行實時資料計算、日誌採集等場景 |
topic 數量對吞吐量的影響 | topic 可以達到幾百/幾千的級別,吞吐量會有較小幅度的下降,這是 RocketMQ 的一大優勢,在同等機器下,可以支撐大量的 topic | topic 從幾十到幾百個時候,吞吐量會大幅度下降,在同等機器下,Kafka 儘量保證 topic 數量不要過多,如果要支撐大規模的 topic,需要增加更多的機器資源 | ||
時效性 | ms 級 | 微秒級,這是 RabbitMQ 的一大特點,延遲最低 | ms 級 | 延遲在 ms 級以內 |
可用性 | 高,基於主從架構實現高可用 | 同 ActiveMQ | 非常高,分散式架構 | 非常高,分散式,一個數據多個副本,少數機器宕機,不會丟失資料,不會導致不可用 |
訊息可靠性 | 有較低的概率丟失資料 | 基本不丟 | 經過引數優化配置,可以做到 0 丟失 | 同 RocketMQ |
功能支援 | MQ 領域的功能極其完備 | 基於 erlang 開發,併發能力很強,效能極好,延時很低 | MQ 功能較為完善,還是分散式的,擴充套件性好 | 功能較為簡單,主要支援簡單的 MQ 功能,在大資料領域的實時計算以及日誌採集被大規模使用 |
綜上,各種對比之後,有如下建議:
一般的業務系統要引入 MQ,最早大家都用 ActiveMQ,但是現在確實大家用的不多了,沒經過大規模吞吐量場景的驗證,社群也不是很活躍,所以大家還是算了吧,我個人不推薦用這個了;
後來大家開始用 RabbitMQ,但是確實 erlang 語言阻止了大量的 Java 工程師去深入研究和掌控它,對公司而言,幾乎處於不可控的狀態,但是確實人家是開源的,比較穩定的支援,活躍度也高;
不過現在確實越來越多的公司,會去用 RocketMQ,確實很不錯(阿里出品),但社群可能有突然黃掉的風險,對自己公司技術實力有絕對自信的,推薦用 RocketMQ,否則回去老老實實用 RabbitMQ 吧,人家有活躍的開源社群,絕對不會黃。
所以中小型公司,技術實力較為一般,技術挑戰不是特別高,用 RabbitMQ 是不錯的選擇;大型公司,基礎架構研發實力較強,用 RocketMQ 是很好的選擇。
如果是大資料領域的實時計算、日誌採集等場景,用 Kafka 是業內標準的,絕對沒問題,社群活躍度很高,絕對不會黃,何況幾乎是全世界這個領域的事實性規範
如何保證訊息佇列的高可用?
RabbitMQ 的高可用性
RabbitMQ 是比較有代表性的,因為是基於主從(非分散式)做高可用性的,我們就以 RabbitMQ 為例子講解第一種 MQ 的高可用性怎麼實現。
RabbitMQ 有三種模式:單機模式、普通叢集模式、映象叢集模式。
單機模式
單機模式,就是 Demo 級別的,一般就是你本地啟動了玩玩兒的 ,沒人生產用單機模式。
普通叢集模式(無高可用性)
普通叢集模式,意思就是在多臺機器上啟動多個 RabbitMQ 例項,每個機器啟動一個。你建立的 queue,只會放在一個 RabbitMQ 例項上,但是每個例項都同步 queue 的元資料(元資料可以認為是 queue 的一些配置資訊,通過元資料,可以找到 queue 所在例項)。你消費的時候,實際上如果連線到了另外一個例項,那麼那個例項會從 queue 所在例項上拉取資料過來。
這種方式確實很麻煩,也不怎麼好,沒做到所謂的分散式,就是個普通叢集。因為這導致你要麼消費者每次隨機連線一個例項然後拉取資料,要麼固定連線那個 queue 所在例項消費資料,前者有資料拉取的開銷,後者導致單例項效能瓶頸。
而且如果那個放 queue 的例項宕機了,會導致接下來其他例項就無法從那個例項拉取,如果你開啟了訊息持久化,讓 RabbitMQ 落地儲存訊息的話,訊息不一定會丟,得等這個例項恢復了,然後才可以繼續從這個 queue 拉取資料。
所以這個事兒就比較尷尬了,這就沒有什麼所謂的高可用性,這方案主要是提高吞吐量的,就是說讓叢集中多個節點來服務某個 queue 的讀寫操作。
映象叢集模式(高可用性)
這種模式,才是所謂的 RabbitMQ 的高可用模式。跟普通叢集模式不一樣的是,在映象叢集模式下,你建立的 queue,無論元資料還是 queue 裡的訊息都會存在於多個例項上,就是說,每個 RabbitMQ 節點都有這個 queue 的一個完整映象,包含 queue 的全部資料的意思。然後每次你寫訊息到 queue 的時候,都會自動把訊息同步到多個例項的 queue 上。
那麼如何開啟這個映象叢集模式呢?其實很簡單,RabbitMQ 有很好的管理控制檯,就是在後臺新增一個策略,這個策略是映象叢集模式的策略,指定的時候是可以要求資料同步到所有節點的,也可以要求同步到指定數量的節點,再次建立 queue 的時候,應用這個策略,就會自動將資料同步到其他的節點上去了。
這樣的話,好處在於,你任何一個機器宕機了,沒事兒,其它機器(節點)還包含了這個 queue 的完整資料,別的 consumer 都可以到其它節點上去消費資料。壞處在於,第一,這個效能開銷也太大了吧,訊息需要同步到所有機器上,導致網路頻寬壓力和消耗很重!第二,這麼玩兒,不是分散式的,就沒有擴充套件性可言了,如果某個 queue 負載很重,你加機器,新增的機器也包含了這個 queue 的所有資料,並沒有辦法線性擴充套件你的 queue。你想,如果這個 queue 的資料量很大,大到這個機器上的容量無法容納了,此時該怎麼辦呢?
Kafka 的高可用性
Kafka 一個最基本的架構認識:由多個 broker 組成,每個 broker 是一個節點;你建立一個 topic,這個 topic 可以劃分為多個 partition,每個 partition 可以存在於不同的 broker 上,每個 partition 就放一部分資料。
這就是天然的分散式訊息佇列,就是說一個 topic 的資料,是分散放在多個機器上的,每個機器就放一部分資料。
實際上 RabbmitMQ 之類的,並不是分散式訊息佇列,它就是傳統的訊息佇列,只不過提供了一些叢集、HA(High Availability, 高可用性) 的機制而已,因為無論怎麼玩兒,RabbitMQ 一個 queue 的資料都是放在一個節點裡的,映象叢集下,也是每個節點都放這個 queue 的完整資料。
Kafka 0.8 以前,是沒有 HA 機制的,就是任何一個 broker 宕機了,那個 broker 上的 partition 就廢了,沒法寫也沒法讀,沒有什麼高可用性可言。
比如說,我們假設建立了一個 topic,指定其 partition 數量是 3 個,分別在三臺機器上。但是,如果第二臺機器宕機了,會導致這個 topic 的 1/3 的資料就丟了,因此這個是做不到高可用的。
Kafka 0.8 以後,提供了 HA 機制,就是 replica(複製品) 副本機制。每個 partition 的資料都會同步到其它機器上,形成自己的多個 replica 副本。所有 replica 會選舉一個 leader 出來,那麼生產和消費都跟這個 leader 打交道,然後其他 replica 就是 follower。寫的時候,leader 會負責把資料同步到所有 follower 上去,讀的時候就直接讀 leader 上的資料即可。只能讀寫 leader?很簡單,要是你可以隨意讀寫每個 follower,那麼就要 care 資料一致性的問題,系統複雜度太高,很容易出問題。Kafka 會均勻地將一個 partition 的所有 replica 分佈在不同的機器上,這樣才可以提高容錯性。
這麼搞,就有所謂的高可用性了,因為如果某個 broker 宕機了,沒事兒,那個 broker上面的 partition 在其他機器上都有副本的,如果這上面有某個 partition 的 leader,那麼此時會從 follower 中重新選舉一個新的 leader 出來,大家繼續讀寫那個新的 leader 即可。這就有所謂的高可用性了。
寫資料的時候,生產者就寫 leader,然後 leader 將資料落地寫本地磁碟,接著其他 follower 自己主動從 leader 來 pull 資料。一旦所有 follower 同步好資料了,就會發送 ack 給 leader,leader 收到所有 follower 的 ack 之後,就會返回寫成功的訊息給生產者。(當然,這只是其中一種模式,還可以適當調整這個行為)
消費的時候,只會從 leader 去讀,但是隻有當一個訊息已經被所有 follower 都同步成功返回 ack 的時候,這個訊息才會被消費者讀到。
看到這裡,相信你大致明白了 Kafka 是如何保證高可用機制的了,對吧?不至於一無所知,現場還能給面試官畫畫圖。要是遇上面試官確實是 Kafka 高手,深挖了問,那你只能說不好意思,太深入的你沒研究過。