<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          懵了,Kafka、RabbitMQ到底選哪個(gè)?

          共 5083字,需瀏覽 11分鐘

           ·

          2021-12-31 17:40

          經(jīng)常有人問(wèn)我

          有個(gè) xx 需求,我應(yīng)該用 Kafka 還是 RabbitMQ ?

          這個(gè)問(wèn)題很常見(jiàn),而且很多人對(duì)二者的選擇也把握不好。

          所以我決定寫篇文章來(lái)詳細(xì)說(shuō)一下:Kafka 和 RabbitMQ 的區(qū)別,適用于什么場(chǎng)景?

          同時(shí),這個(gè)問(wèn)題在面試中也經(jīng)常問(wèn)到。

          下面我會(huì)通過(guò) 6 個(gè)場(chǎng)景,來(lái)對(duì)比分析一下 Kafka 和 RabbitMQ 的優(yōu)劣。

          一、消息的順序

          有這樣一個(gè)需求:當(dāng)訂單狀態(tài)變化的時(shí)候,把訂單狀態(tài)變化的消息發(fā)送給所有關(guān)心訂單變化的系統(tǒng)。

          訂單會(huì)有創(chuàng)建成功、待付款、已支付、已發(fā)貨的狀態(tài),狀態(tài)之間是單向流動(dòng)的。

          好,現(xiàn)在我們把訂單狀態(tài)變化消息要發(fā)送給所有關(guān)心訂單狀態(tài)的系統(tǒng)上去,實(shí)現(xiàn)方式就是用消息隊(duì)列。

          在這種業(yè)務(wù)下,我們最想要的是什么?

          1. 消息的順序:對(duì)于同一筆訂單來(lái)說(shuō),狀態(tài)的變化都是有嚴(yán)格的先后順序的。

          2. 吞吐量:像訂單的業(yè)務(wù),我們自然希望訂單越多越好。訂單越多,吞吐量就越大。

          在這種情況下,我們先看看 RabbitMQ 是怎么做的。

          首先,對(duì)于發(fā)消息,并廣播給多個(gè)消費(fèi)者這種情況,RabbitMQ 會(huì)為每個(gè)消費(fèi)者建立一個(gè)對(duì)應(yīng)的隊(duì)列。也就是說(shuō),如果有 10 個(gè)消費(fèi)者,RabbitMQ 會(huì)建立 10 個(gè)對(duì)應(yīng)的隊(duì)列。然后,當(dāng)一條消息被發(fā)出后,RabbitMQ 會(huì)把這條消息復(fù)制 10 份放到這 10 個(gè)隊(duì)列里。

          當(dāng) RabbitMQ 把消息放入到對(duì)應(yīng)的隊(duì)列后,我們緊接著面臨的問(wèn)題就是,我們應(yīng)該在系統(tǒng)內(nèi)部啟動(dòng)多少線程去從消息隊(duì)列中獲取消息。

          如果只是單線程去獲取消息,那自然沒(méi)有什么好說(shuō)的。但是多線程情況,可能就會(huì)有問(wèn)題了……

          RabbitMQ 有這么個(gè)特性,它在官方文檔就聲明了自己是不保證多線程消費(fèi)同一個(gè)隊(duì)列的消息,一定保證順序的。而不保證的原因,是因?yàn)槎嗑€程時(shí),當(dāng)一個(gè)線程消費(fèi)消息報(bào)錯(cuò)的時(shí)候,RabbitMQ 會(huì)把消費(fèi)失敗的消息再入隊(duì),此時(shí)就可能出現(xiàn)亂序的情況。

          T0 時(shí)刻,隊(duì)列中有四條消息 A1、B1、B2、A2。其中 A1、A2 表示訂單 A 的兩個(gè)狀態(tài):待付款、已付款。B1、B2 也同理,是訂單 B 的待付款、已付款。

          到了 T1 時(shí)刻,消息 A1 被線程 1 收到,消息 B1 被線程 2 收到。此時(shí),一切都還正常。

          到了 T3 時(shí)刻,B1 消費(fèi)出錯(cuò)了,同時(shí)呢,由于線程 1 處理速度快,又從消息隊(duì)列中獲取到了 B2。此時(shí),問(wèn)題開(kāi)始出現(xiàn)。

          到了 T4 時(shí)刻,由于 RabbitMQ 線程消費(fèi)出錯(cuò),可以把消息重新入隊(duì)的特性,此時(shí) B1 會(huì)被重新放到隊(duì)列頭部。所以,如果不湊巧,線程 1 獲取到了 B1,就出現(xiàn)了亂序情況,B2 狀態(tài)明明是 B1 的后續(xù)狀態(tài),卻被提前處理了。

          所以,可以看到了,這個(gè)場(chǎng)景用 RabbitMQ,出現(xiàn)了三個(gè)問(wèn)題:

          1. 為了實(shí)現(xiàn)發(fā)布訂閱功能,從而使用的消息復(fù)制,會(huì)降低性能并耗費(fèi)更多資源
          2. 多個(gè)消費(fèi)者無(wú)法嚴(yán)格保證消息順序
          3. 大量的訂單集中在一個(gè)隊(duì)列,吞吐量受到了限制

          那么 Kafka 怎么樣呢?Kafka 正好在這三個(gè)問(wèn)題上,表現(xiàn)的要比 RabbitMQ 要好得多。

          首先,Kafka 的發(fā)布訂閱并不會(huì)復(fù)制消息,因?yàn)?Kafka 的發(fā)布訂閱就是消費(fèi)者直接去獲取被 Kafka 保存在日志文件中的消息就好。無(wú)論是多少消費(fèi)者,他們只需要主動(dòng)去找到消息在文件中的位置即可。

          其次,Kafka 不會(huì)出現(xiàn)消費(fèi)者出錯(cuò)后,把消息重新入隊(duì)的現(xiàn)象。

          最后,Kafka 可以對(duì)訂單進(jìn)行分區(qū),把不同訂單分到多個(gè)分區(qū)中保存,這樣,吞吐量能更好。

          所以,對(duì)于這個(gè)需求 Kafka 更合適。

          二、消息的匹配

          我曾經(jīng)做過(guò)一套營(yíng)銷系統(tǒng)。這套系統(tǒng)中有個(gè)非常顯著的特點(diǎn),就是非常復(fù)雜非常靈活地匹配規(guī)則。

          比如,要根據(jù)推廣內(nèi)容去匹配不同的方式做宣傳。又比如,要根據(jù)不同的活動(dòng)去匹配不同的渠道去做分發(fā)。

          總之,數(shù)不清的匹配規(guī)則是這套系統(tǒng)中非常重要的一個(gè)特點(diǎn)。

          首先,先看看 RabbitMQ 的,你會(huì)發(fā)現(xiàn) RabbitMQ 是允許在消息中添加 routing_key 或者自定義消息頭,然后通過(guò)一些特殊的 Exchange,很簡(jiǎn)單的就實(shí)現(xiàn)了消息匹配分發(fā)。開(kāi)發(fā)幾乎不用成本。

          而 Kafka 呢?如果你要實(shí)現(xiàn)消息匹配,開(kāi)發(fā)成本高多了。

          首先,通過(guò)簡(jiǎn)單的配置去自動(dòng)匹配和分發(fā)到合適的消費(fèi)者端這件事是不可能的。

          其次,消費(fèi)者端必須先把所有消息不管需要不需要,都取出來(lái)。然后,再根據(jù)業(yè)務(wù)需求,自己去實(shí)現(xiàn)各種精準(zhǔn)和模糊匹配??赡芤?yàn)檫^(guò)度的復(fù)雜性,還要引入規(guī)則引擎。

          這個(gè)場(chǎng)景下 RabbitMQ 扳回一分。

          三、消息的超時(shí)

          在電商業(yè)務(wù)里,有個(gè)需求:下單之后,如果用戶在 15 分鐘內(nèi)未支付,則自動(dòng)取消訂單。

          你可能奇怪,這種怎么也會(huì)用到消息隊(duì)列的?

          我來(lái)先簡(jiǎn)單解釋一下,在單一服務(wù)的系統(tǒng),可以起個(gè)定時(shí)任務(wù)就搞定了。

          但是,在 SOA 或者微服務(wù)架構(gòu)下,這樣做就不行了。因?yàn)楹芏鄠€(gè)服務(wù)都關(guān)心是否支付這件事,如果每種服務(wù),都自己實(shí)現(xiàn)一套定時(shí)任務(wù)的邏輯,既重復(fù),又難以維護(hù)。

          在這種情況下,我們往往會(huì)做一層抽象:把要執(zhí)行的任務(wù)封裝成消息。當(dāng)時(shí)間到了,直接扔到消息隊(duì)列里,消息的訂閱者們獲取到消息后,直接執(zhí)行即可。

          希望把消息延遲一定時(shí)間再處理的,被稱為延遲隊(duì)列。

          對(duì)于訂單取消的這種業(yè)務(wù),我們就會(huì)在創(chuàng)建訂單的時(shí)候,同時(shí)扔一個(gè)包含了執(zhí)行任務(wù)信息的消息到延遲隊(duì)列,指定15分鐘后,讓訂閱這個(gè)隊(duì)列的各個(gè)消費(fèi)者,可以收到這個(gè)消息。隨后,各個(gè)消費(fèi)者所在的系統(tǒng)就可以去執(zhí)行相關(guān)的掃描訂單的任務(wù)了。

          RabbitMQ 和 Kafka 消息隊(duì)列如何選?

          先看下 RabbitMQ 的。

          RabbitMQ 的消息自帶手表,消息中有個(gè) TTL 字段,可以設(shè)置消息在 RabbitMQ 中的存放的時(shí)間,超時(shí)了會(huì)被移送到一個(gè)叫死信隊(duì)列的地方。

          所以,延遲隊(duì)列 RabbitMQ 最簡(jiǎn)單的實(shí)現(xiàn)方式就是設(shè)置 TTL,然后一個(gè)消費(fèi)者去監(jiān)聽(tīng)死信隊(duì)列。當(dāng)消息超時(shí)了,監(jiān)聽(tīng)死信隊(duì)列的消費(fèi)者就收到消息了。

          不過(guò),這樣做有個(gè)大問(wèn)題:假設(shè),我們先往隊(duì)列放入一條過(guò)期時(shí)間是 10 秒的 A 消息,再放入一條過(guò)期時(shí)間是 5 秒的 B 消息。那么問(wèn)題來(lái)了,B 消息會(huì)先于 A 消息進(jìn)入死信隊(duì)列嗎?

          答案是否定的。B 消息會(huì)優(yōu)先遵守隊(duì)列的先進(jìn)先出規(guī)則,在 A 消息過(guò)期后,和其一起進(jìn)入死信隊(duì)列被消費(fèi)者消費(fèi)。

          在 RabbitMQ 的 3.5.8 版本以后,官方推薦的 rabbitmq delayed message exchange 插件可以解決這個(gè)問(wèn)題。

          • 用了這個(gè)插件,我們?cè)诎l(fā)送消息的時(shí)候,把消息發(fā)往一個(gè)特殊的 Exchange。
          • 同時(shí),在消息頭里指定要延遲的時(shí)間。
          • 收到消息的 Exchange 并不會(huì)立即把消息放到隊(duì)列里,而是在消息延遲時(shí)間到達(dá)后,才會(huì)把消息放入。

          再看下 Kafka 的:

          Kafka 要實(shí)現(xiàn)延遲隊(duì)列就很麻煩了。

          • 你先需要把消息先放入一個(gè)臨時(shí)的 topic。
          • 然后得自己開(kāi)發(fā)一個(gè)做中轉(zhuǎn)的消費(fèi)者。讓這個(gè)中間的消費(fèi)者先去把消息從這個(gè)臨時(shí)的 topic 取出來(lái)。
          • 取出來(lái),這消息還不能馬上處理啊,因?yàn)闆](méi)到時(shí)間呢。也沒(méi)法保存在自己的內(nèi)存里,怕崩潰了,消息沒(méi)了。所以,就得把沒(méi)有到時(shí)間的消息存入到數(shù)據(jù)庫(kù)里。
          • 存入數(shù)據(jù)庫(kù)中的消息需要在時(shí)間到了之后再放入到 Kafka 里,以便真正的消費(fèi)者去執(zhí)行真正的業(yè)務(wù)邏輯。
          • ……

          想想就已經(jīng)頭大了,這都快搞成調(diào)度平臺(tái)了。再高級(jí)點(diǎn),還要用時(shí)間輪算法才能更好更準(zhǔn)確。

          這次,RabbitMQ 上那一條條戴手表的消息,才是最好的選擇。

          四、消息的保持

          在微服務(wù)里,事件溯源模式是經(jīng)常用到的。如果想用消息隊(duì)列實(shí)現(xiàn),一般是把事件當(dāng)成消息,依次發(fā)送到消息隊(duì)列中。

          事件溯源有個(gè)最經(jīng)典的場(chǎng)景,就是事件的重放。簡(jiǎn)單來(lái)講就是把系統(tǒng)中某段時(shí)間發(fā)生的事件依次取出來(lái)再處理。而且,根據(jù)業(yè)務(wù)場(chǎng)景不同,這些事件重放很可能不是一次,更可能是重復(fù) N 次。

          假設(shè),我們現(xiàn)在需要一批在線事件重放,去排查一些問(wèn)題。

          RabbitMQ 此時(shí)就真的不行了,因?yàn)橄⒈蝗巳〕鰜?lái)就被刪除了。想再次被重復(fù)消費(fèi)?對(duì)不起。

          而 Kafka 呢,消息會(huì)被持久化一個(gè)專門的日志文件里。不會(huì)因?yàn)楸幌M(fèi)了就被刪除。

          所以,對(duì)消息不離不棄的 Kafka 相對(duì)用過(guò)就拋的 RabbitMQ,請(qǐng)選擇 Kafka。

          五、消息的錯(cuò)誤處理

          很多時(shí)候,在做記錄數(shù)據(jù)相關(guān)業(yè)務(wù)的時(shí)候,Kafka 一般是不二選擇。不過(guò),有時(shí)候在記錄數(shù)據(jù)吞吐量不大時(shí),我自己倒是更喜歡用 RabbitMQ。

          原因就是 Kafka 有一個(gè)我很不喜歡的設(shè)計(jì)原則:

          當(dāng)單個(gè)分區(qū)中的消息一旦出現(xiàn)消費(fèi)失敗,就只能停止而不是跳過(guò)這條失敗的消息繼續(xù)消費(fèi)后面的消息。即不允許消息空洞。

          只要消息出現(xiàn)失敗,不管是 Kafka 自身消息格式的損壞,還是消費(fèi)者處理出現(xiàn)異常,是不允許跳過(guò)消費(fèi)失敗的消息繼續(xù)往后消費(fèi)的。

          所以,在數(shù)據(jù)統(tǒng)計(jì)不要求十分精確的場(chǎng)景下選了 Kafka,一旦出現(xiàn)了消息消費(fèi)問(wèn)題,就會(huì)發(fā)生項(xiàng)目不可用的情況。這真是徒增煩惱。

          而 RabbitMQ 呢,它由于會(huì)在消息出問(wèn)題或者消費(fèi)錯(cuò)誤的時(shí)候,可以重新入隊(duì)或者移動(dòng)消息到死信隊(duì)列,繼續(xù)消費(fèi)后面的,會(huì)省心很多。

          壞消息就像群眾中的壞蛋那樣,Kafka 處理這種壞蛋太過(guò)殘暴,非得把壞蛋揪出來(lái)不行。相對(duì)來(lái)說(shuō),RabbitMQ 就溫柔多了,群眾是群眾,壞蛋是壞蛋,分開(kāi)處理嘛。

          六、消息的吞吐量

          Kafka 是每秒幾十萬(wàn)條消息吞吐,而 RabbitMQ 的吞吐量是每秒幾萬(wàn)條消息。

          其實(shí),在一家公司內(nèi)部,有必須用到 Kafka 那么大吞吐量的項(xiàng)目真的很少。大部分項(xiàng)目,像 RabbitMQ 那樣每秒幾萬(wàn)的消息吞吐,已經(jīng)非常夠了。

          在一些沒(méi)那么大吞吐量的項(xiàng)目中引入 Kafka,我覺(jué)得就不如引入 RabbitMQ。

          為什么呢?

          因?yàn)?Kafka 為了更好的吞吐量,很大程度上增加了自己的復(fù)雜度。而這些復(fù)雜度對(duì)項(xiàng)目來(lái)說(shuō),就是麻煩,主要體現(xiàn)在兩個(gè)方面:

          1、配置復(fù)雜、維護(hù)復(fù)雜

          Kafka 的參數(shù)配置相對(duì) RabbitMQ 是很復(fù)雜的。比如:磁盤管理相關(guān)參數(shù),集群管理相關(guān)參數(shù),ZooKeeper 交互相關(guān)參數(shù),Topic 級(jí)別相關(guān)參數(shù)等,都需要一些思考和調(diào)優(yōu)。

          另外,Kafka 本身集群和參與管理集群的 ZooKeeper,這就帶來(lái)了更多的維護(hù)成本。Kafka 要用好,你要考慮 JVM,消息持久化,集群本身交互,以及 ZooKeeper 本身和它與 Kafka 之間的可靠和效率。

          2、用好,用對(duì)存在門檻

          Kafka 的 Producer 和 Consumer 本身要用好用對(duì)也存在很高的門檻。

          比如,Producer 消息可靠性保障、冪等性、事務(wù)消息等,都需要對(duì) KafkaProducer 有深入的了解。

          而 Consumer 更不用說(shuō)了,光是一個(gè)日志偏移管理就讓一大堆人掉了不少頭發(fā)。

          相對(duì)來(lái)說(shuō),RabbitMQ 就簡(jiǎn)單得多。你可能都不用配置什么,直接啟動(dòng)起來(lái)就能很穩(wěn)定可靠地使用了。就算配置,也是寥寥幾個(gè)參數(shù)設(shè)置即可。

          所以,大家在項(xiàng)目中引入消息隊(duì)列的時(shí)候,真的要好好考慮下,不要因?yàn)榇蠹叶脊拇?Kafka 好,就無(wú)腦引入。

          總結(jié)

          可以看到,如果我們要做消息隊(duì)列選型,有兩件事是必須要做好的:

          1. 列出業(yè)務(wù)最重要的幾個(gè)特點(diǎn)

          2. 深入到消息隊(duì)列的細(xì)節(jié)中去比較

          等我們對(duì)這些中間件的特點(diǎn)非常熟悉之后,甚至可以把業(yè)務(wù)分解成不同的子業(yè)務(wù),再根據(jù)不同的子業(yè)務(wù)的特征,引入不同的消息隊(duì)列,即消息隊(duì)列混用。這樣,我們就可能會(huì)最大化我們的獲益,最小化我們的成本。

          說(shuō)了這么多,其實(shí)還有很多 Kafka 和 RabbitMQ 的比較沒(méi)有說(shuō),比如二者集群的區(qū)別,占用資源多少的比較等。以后有機(jī)會(huì)可以再提提。

          總之,期待大家看完這篇文章后,能對(duì) Kafka 和 RabbitMQ 的區(qū)別有了更細(xì)節(jié)性的了解。

          最后,分享一個(gè)網(wǎng)上的比較全的對(duì)比圖:

          最最后,可能這是今年最后一次發(fā)文章了,在這里提前祝大家元旦快樂(lè)。

          原創(chuàng)不易,求贊、求在看。


          我寫公眾號(hào)也挺長(zhǎng)時(shí)間了,原創(chuàng)了不少文章,最近把其中的一些精華文章做了個(gè)匯總整理,搞了一份PDF,起名叫《爬坡》。包括了 15 篇技術(shù)文章(包括學(xué)習(xí)編程技巧、架構(gòu)師、MQ、分布式)和 13 篇非技術(shù)文章(主要是程序員職場(chǎng)),一共十萬(wàn)多字

          如果你是新關(guān)注我的,通過(guò)《爬坡》可以快速了解我這個(gè)公眾號(hào)的精華。我相信這些精華文章一定能幫到大家,助大家早日成功。獲取方式,在公眾號(hào)后臺(tái)回復(fù):爬坡

          瀏覽 80
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日日夜夜狠狠草 | 午夜人妻无码 | 免费一区两区三区 | 青青日在线视频 | iGAO激情在线视频入口 |