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