雖然在以往的項(xiàng)目開發(fā)過程中已經(jīng)使用過RabbitMQ與Kafka,但還是不能準(zhǔn)確并全面的總結(jié)出它們倆之間的差異。
在這之前很長(zhǎng)一段時(shí)間一直都是把這兩種技術(shù)當(dāng)做等價(jià)的來看待,突然想到如果是我在某種特定業(yè)務(wù)下來做選型的話,我要怎么選呢?萬一選錯(cuò)了,對(duì)于軟件開發(fā)和后期的維護(hù)都會(huì)造成嚴(yán)重的影響。
所謂學(xué)而時(shí)習(xí)之,不亦說乎。溫故而知新,可以為師矣。所以通過官網(wǎng)和參考了一些博客,做了以下整理:
宏觀的差異,RabbitMQ與Kafka只是功能類似,并不是同類
RabbitMQ是消息中間件,Kafka是分布式流式系統(tǒng)。
RabbitMQ
被概括為“開源分布式消息代理”,用Erlang編寫,有助于在復(fù)雜的路由方案中有效地傳遞消息,可以通過服務(wù)器上啟用的插件進(jìn)行擴(kuò)展,高可用(隊(duì)列可以在集群中的機(jī)器上進(jìn)行鏡像)
有隊(duì)列
作為消息中間件的一種實(shí)現(xiàn),RabbitMQ支持典型的開箱即用的消息隊(duì)列。開發(fā)者定義一個(gè)命名隊(duì)列,然后發(fā)布者向這個(gè)隊(duì)列中發(fā)送消息。最后消費(fèi)者通過這個(gè)命名隊(duì)列獲取待處理的消息。
RabbitMQ的發(fā)布/訂閱模式
RabbitMQ使用消息交換器(Exchange)來實(shí)現(xiàn)發(fā)布/訂閱模式。發(fā)布者可以把消息發(fā)布到消息交換器上而不用知道這些消息都有哪些訂閱者。每一個(gè)訂閱了交換器的消費(fèi)者都會(huì)創(chuàng)建一個(gè)隊(duì)列;然后消息交換器會(huì)把生產(chǎn)的消息放入隊(duì)列以供消費(fèi)者消費(fèi)。消息交換器也可以基于各種路由規(guī)則為一些訂閱者過濾消息。
注意:RabbitMQ支持臨時(shí)和持久兩種訂閱類型。消費(fèi)者可以調(diào)用RabbitMQ的API來選擇他們想要的訂閱類型
Apache Kafka
被描述為“分布式事件流平臺(tái)”,用Scala和Java編寫,促進(jìn)了原始吞吐量,基于“分布式僅追加日志”的思想,該消息將消息寫入持久化到磁盤的日志末尾,客戶端可以選擇從該日志開始讀取的位置,高可用(Kafka群集可以在多個(gè)服務(wù)器之間分布和群集)
無隊(duì)列,按主題存儲(chǔ)
Kafka不是消息中間件的一種實(shí)現(xiàn)。它只是一種分布式流式系統(tǒng),Kafka的存儲(chǔ)層是使用分區(qū)事務(wù)日志來實(shí)現(xiàn)的。
Kafka沒有實(shí)現(xiàn)隊(duì)列。Kafka按照類別存儲(chǔ)記錄集,并且把這種類別稱為主題(topic)。
Kafka為每個(gè)主題(topic)維護(hù)一個(gè)消息分區(qū)日志。每個(gè)分區(qū)都是由有序的不可變的記錄序列組成,并且消息都是連續(xù)的被追加在尾部。默認(rèn)情況下,Kafka使用輪詢分區(qū)器(partitioner)把消息一致的分配到多個(gè)分區(qū)上。
消費(fèi)者通過維護(hù)分區(qū)的偏移量(或者說索引)來順序的讀出消息,然后消費(fèi)消息。單個(gè)消費(fèi)者可以消費(fèi)多個(gè)不同的主題,并且消費(fèi)者的數(shù)量可以伸縮到可獲取的最大分區(qū)數(shù)量。
所以在創(chuàng)建主題的時(shí)候,需要考慮一下在創(chuàng)建的主題上預(yù)期的消息吞吐量。在消費(fèi)同一個(gè)主題的多個(gè)消費(fèi)者構(gòu)成的組稱為消費(fèi)者組中,通過Kafka提供的API可以處理同一消費(fèi)者組中多個(gè)消費(fèi)者之間的分區(qū)平衡以及消費(fèi)者當(dāng)前分區(qū)偏移的存儲(chǔ)。
Kafka的發(fā)布/訂閱模式
生產(chǎn)者向一個(gè)具體的主題發(fā)送消息,然后多個(gè)消費(fèi)者組可以消費(fèi)相同的消息。每一個(gè)消費(fèi)者組都可以獨(dú)立的伸縮去處理相應(yīng)的負(fù)載。由于消費(fèi)者維護(hù)自己的分區(qū)偏移,所以他們可以選擇持久訂閱或者臨時(shí)訂閱,持久訂閱在重啟之后不會(huì)丟失偏移而臨時(shí)訂閱在重啟之后會(huì)丟失偏移并且每次重啟之后都會(huì)從分區(qū)中最新的記錄開始讀取。
但是這種實(shí)現(xiàn)方案不能完全等價(jià)的當(dāng)做典型的消息隊(duì)列模式看待。當(dāng)然,我們可以創(chuàng)建一個(gè)主題,這個(gè)主題和擁有一個(gè)消費(fèi)者的消費(fèi)組進(jìn)行關(guān)聯(lián),這樣我們就模擬出了一個(gè)典型的消息隊(duì)列。不過這會(huì)有許多缺點(diǎn),例如:消費(fèi)失敗不支持重試等,下面微觀的差異中會(huì)有說明 。
Kafka是按照預(yù)先配置好的時(shí)間保留分區(qū)中的消息,而不是根據(jù)消費(fèi)者是否消費(fèi)了這些消息。這種保留機(jī)制可以讓消費(fèi)者自由的重讀之前的消息。另外,開發(fā)者也可以利用Kafka的存儲(chǔ)層來實(shí)現(xiàn)諸如事件溯源和日志審計(jì)功能。
微觀差異,類似功能的不同特點(diǎn)
Kafka支持消息有序性,RabbitMQ不保證消息的順序
RabbitMQ
RabbitMQ文檔中關(guān)于消息順序保證的說明:
“發(fā)到一個(gè)通道(channel)上的消息,用一個(gè)交換器和一個(gè)隊(duì)列以及一個(gè)出口通道來傳遞,那么最終會(huì)按照它們發(fā)送的順序接收到。”
在RabbitMQ中只要我們是單個(gè)消費(fèi)者(并且通過限制消費(fèi)者的并發(fā)數(shù)等于1,不過,隨著系統(tǒng)規(guī)模增長(zhǎng),單線程消費(fèi)者模式會(huì)嚴(yán)重影響消息處理能力),那么接收到的消息就是有序的。然而,一旦有多個(gè)消費(fèi)者從同一個(gè)隊(duì)列中讀取消息,那么消息的處理順序就沒法保證了。
由于消費(fèi)者讀取消息之后可能會(huì)把消息放回(或者重傳)到隊(duì)列中(例如,處理失敗的情況),這樣就會(huì)導(dǎo)致消息的順序無法保證。一旦一個(gè)消息被重新放回隊(duì)列,另一個(gè)消費(fèi)者可以繼續(xù)處理它,即使這個(gè)消費(fèi)者已經(jīng)處理到了放回消息之后的消息。因此,消費(fèi)者組處理消息是無序的。
Kafka
Kafka在消息處理方面提供了可靠的順序保證。
Kafka能夠保證發(fā)送到相同主題分區(qū)的所有消息都能夠按照順序處理。
所有來自相同流的消息都會(huì)被放到相同的分區(qū)中,這樣消費(fèi)者組就可以按照順序處理它們。在同一個(gè)消費(fèi)者組中,每個(gè)分區(qū)都是由一個(gè)消費(fèi)者的一個(gè)線程來處理。結(jié)果就是我們沒法伸縮(scale)單個(gè)分區(qū)的處理能力。
不過,在Kafka中,我們可以伸縮一個(gè)主題中的分區(qū)數(shù)量,這樣可以讓每個(gè)分區(qū)分擔(dān)更少的消息,然后增加更多的消費(fèi)者來處理額外的分區(qū)。
在消息路由和過濾方面,RabbitMQ提供了更好的支持
RabbitMQ
RabbitMQ可以基于定義的訂閱者路由規(guī)則路由消息給一個(gè)消息交換器上的訂閱者。一個(gè)主題交換器可以通過routingKey的特定頭來路由消息。
或者,headers交換器可以基于任意的消息頭來路由消息。這兩種交換器都能夠有效地讓消費(fèi)者設(shè)置他們想要消息類型,因此可以給使用者提供了很好的靈活性。
Kafka
Kafka在處理消息之前是不允許消費(fèi)者過濾一個(gè)主題中的消息。一個(gè)訂閱的消費(fèi)者在沒有異常情況下會(huì)接受一個(gè)分區(qū)中的所有消息。
作為一個(gè)開發(fā)者,你可能使用Kafka流式作業(yè)(job),它會(huì)從主題中讀取消息,然后過濾,最后再把過濾的消息推送到另一個(gè)消費(fèi)者可以訂閱的主題。但是,這需要更多的工作量和維護(hù),并且還涉及到更多的移動(dòng)操作。
消息時(shí)序
分布式系統(tǒng)中,很多業(yè)務(wù)場(chǎng)景都需要考慮消息投遞的時(shí)序,例如:
(1)單聊消息投遞,保證發(fā)送方發(fā)送順序與接收方展現(xiàn)順序一致
(2)群聊消息投遞,保證所有接收方展現(xiàn)順序一致
(3)充值支付消息,保證同一個(gè)用戶發(fā)起的請(qǐng)求在服務(wù)端執(zhí)行序列一致
RabbitMQ
在保證消息時(shí)序方面,RabbitMQ提供了多種能力:
1)消息存活時(shí)間(TTL)
發(fā)送到RabbitMQ的每條消息都可以關(guān)聯(lián)一個(gè)TTL屬性。發(fā)布者可以直接設(shè)置TTL或者根據(jù)隊(duì)列的策略來設(shè)置。
系統(tǒng)可以根據(jù)設(shè)置的TTL來限制消息的有效期。如果消費(fèi)者在預(yù)期時(shí)間內(nèi)沒有處理該消息,那么這條消息會(huì)自動(dòng)的從隊(duì)列上被移除(并且會(huì)被移到死信交換器上,同時(shí)在這之后的消息都會(huì)這樣處理)。
TTL對(duì)于那些有時(shí)效性的命令特別有用,因?yàn)橐欢螘r(shí)間內(nèi)沒有處理的話,這些命令就沒有什么意義了。
2)延遲/預(yù)定的消息
RabbitMQ可以通過插件的方式來支持延遲或者預(yù)定的消息。當(dāng)這個(gè)插件在消息交換器上啟用的時(shí)候,生產(chǎn)者可以發(fā)送消息到RabbitMQ上,然后這個(gè)生產(chǎn)者可以延遲RabbitMQ路由這個(gè)消息到消費(fèi)者隊(duì)列的時(shí)間。
這個(gè)功能允許開發(fā)者調(diào)度將來(future)的命令,也就是在那之前不應(yīng)該被處理的命令。例如,當(dāng)生產(chǎn)者遇到限流規(guī)則時(shí),我們可能會(huì)把這些特定的命令延遲到之后的一個(gè)時(shí)間執(zhí)行。
Kafka
Kafka沒有提供這些功能。它在消息到達(dá)的時(shí)候就把它們寫入分區(qū)中,這樣消費(fèi)者就可以立即獲取到消息去處理。Kafka也沒有為消息提供TTL的機(jī)制,不過我們可以在應(yīng)用層實(shí)現(xiàn)。
注意:Kafka分區(qū)是一種追加模式的事務(wù)日志。所以,它是不能處理消息時(shí)間(或者分區(qū)中的位置)。
Kafka支持消息留存,RabbitMQ不支持
RabbitMQ
當(dāng)消費(fèi)者成功消費(fèi)消息之后,RabbitMQ就會(huì)把對(duì)應(yīng)的消息從存儲(chǔ)中刪除,且這種設(shè)定沒法修改。
Kafka
相反,Kafka會(huì)給每個(gè)主題配置超時(shí)時(shí)間,只要沒有達(dá)到超時(shí)時(shí)間的消息都會(huì)保留下來。在消息留存方面,Kafka僅僅把它當(dāng)做消息日志來看待,并不關(guān)心消費(fèi)者的消費(fèi)狀態(tài)。
消費(fèi)者可以不限次數(shù)的消費(fèi)每條消息,并且他們可以操作分區(qū)偏移來“及時(shí)”往返的處理這些消息。Kafka會(huì)周期的檢查分區(qū)中消息的留存時(shí)間,一旦消息超過設(shè)定保留的時(shí)長(zhǎng),就會(huì)被刪除。
Kafka的性能不依賴于存儲(chǔ)大小。所以,理論上,它存儲(chǔ)消息幾乎不會(huì)影響性能(只要你的節(jié)點(diǎn)有足夠多的空間保存這些分區(qū))。
RabbitMQ的容錯(cuò)處理優(yōu)于Kafka
消息處理存在兩種可能的故障:
1) 瞬時(shí)故障
故障產(chǎn)生是由于臨時(shí)問題導(dǎo)致,比如網(wǎng)絡(luò)連接或者服務(wù)崩潰等。我們可以通過多次測(cè)試來嘗試減輕這種故障。
2) 持久故障
故障產(chǎn)生是由于永久的問題導(dǎo)致的,并且這種問題不能通過額外的重試來解決。比如常見的原因有軟件bug或者無效的消息格式。
RabbitMQ
RabbitMQ提供了諸如交付重試和死信交換器(DLX)來處理消息處理故障。
DLX的主要思路是根據(jù)合適的配置信息自動(dòng)地把路由失敗的消息發(fā)送到DLX,并且在交換器上根據(jù)規(guī)則來進(jìn)一步的處理,比如異常重試,重試計(jì)數(shù)以及發(fā)送到“人為干預(yù)”的隊(duì)列。
在RabbitMQ中當(dāng)一個(gè)消費(fèi)者正在處理或者重試某個(gè)消息時(shí)(即使是在把它返回隊(duì)列之前),其他消費(fèi)者都可以并發(fā)的處理這個(gè)消息之后的其他消息。
當(dāng)某個(gè)消費(fèi)者在重試處理某條消息時(shí),作為一個(gè)整體的消息處理邏輯不會(huì)被阻塞。所以,一個(gè)消費(fèi)者可以同步地去重試處理一條消息,不管花費(fèi)多長(zhǎng)時(shí)間都不會(huì)影響整個(gè)系統(tǒng)的運(yùn)行。
消費(fèi)者1持續(xù)的在重試處理消息1,同時(shí)其他消費(fèi)者可以繼續(xù)處理其他消息
Kafka
Kafka沒有提供這種機(jī)制。需要我們自己在應(yīng)用層提供和實(shí)現(xiàn)消息重試機(jī)制。
注意:當(dāng)一個(gè)消費(fèi)者正在同步地處理一個(gè)特定的消息時(shí),那么同在這個(gè)分區(qū)上的其他消息是沒法被處理的。
由于消費(fèi)者不能改變消息的順序,所以我們不能夠拒絕和重試一個(gè)特定的消息以及提交一個(gè)在這個(gè)消息之后的消息。
一個(gè)應(yīng)用層解決方案:可以把失敗的消息提交到一個(gè)“重試主題”,并且從那個(gè)主題中處理重試;但是這樣的話我們就會(huì)丟失消息的順序。
如果消費(fèi)者阻塞在重試一個(gè)消息上,那么底部分區(qū)的消息就不會(huì)被處理
Kafka在伸縮方面更優(yōu)并且能夠獲得比RabbitMQ更高的吞吐量
RabbitMQ
典型的RabbitMQ部署包含3到7個(gè)節(jié)點(diǎn)的集群,并且這些集群也不需要把負(fù)載分散到不同的隊(duì)列上。
這些典型的集群通??梢灶A(yù)期每秒處理幾萬條消息。
Kafka
Kafka使用順序磁盤I / O來提高性能。
Kafka的大規(guī)模部署通常每秒可以處理數(shù)十萬條消息,甚至每秒百萬級(jí)別的消息。
Pivotal公司記錄了一個(gè)Kafka集群每秒處理一百萬條消息的例子;但是,它是在一個(gè)有著30個(gè)節(jié)點(diǎn)集群上做的,并且這些消息負(fù)載被優(yōu)化分散到多個(gè)隊(duì)列和交換器上。
注意: 大部分系統(tǒng)都還沒有達(dá)到這些極限(反正我目前從未參與開發(fā)過此類系統(tǒng))!所以,除非你正在構(gòu)建下一個(gè)非常受歡迎的百萬級(jí)用戶軟件系統(tǒng),否則你不需要太關(guān)心伸縮性問題,畢竟這兩個(gè)消息平臺(tái)都可以工作的很好。
RabbitMQ的消費(fèi)者復(fù)雜度低于Kafka
RabbitMQ
RabbitMQ使用的是智能代理和傻瓜式消費(fèi)者模式。
消費(fèi)者注冊(cè)到消費(fèi)者隊(duì)列,然后RabbitMQ把傳進(jìn)來的消息推送給消費(fèi)者。RabbitMQ也有拉?。╬ull)API;不過,一般很少被使用。
RabbitMQ管理消息的分發(fā)以及隊(duì)列上消息的移除(也可能轉(zhuǎn)移到DLX)。消費(fèi)者不需要考慮這塊。
根據(jù)RabbitMQ結(jié)構(gòu)的設(shè)計(jì),當(dāng)負(fù)載增加的時(shí)候,一個(gè)隊(duì)列上的消費(fèi)者組可以有效的從僅僅一個(gè)消費(fèi)者擴(kuò)展到多個(gè)消費(fèi)者,并且不需要對(duì)系統(tǒng)做任何的改變。
Kafka
Kafka使用的是傻瓜式代理和智能消費(fèi)者模式。
消費(fèi)者組中的消費(fèi)者需要協(xié)調(diào)他們之間的主題分區(qū)租約(以便一個(gè)具體的分區(qū)只由消費(fèi)者組中一個(gè)消費(fèi)者監(jiān)聽)。
消費(fèi)者也需要去管理和存儲(chǔ)他們分區(qū)偏移索引。不過Kafka SDK已經(jīng)為我們封裝了,所以我們不需要自己管理。
另外,當(dāng)我們有一個(gè)低負(fù)載時(shí),單個(gè)消費(fèi)者需要處理并且并行的管理多個(gè)分區(qū),這在消費(fèi)者端會(huì)消耗更多的資源。
隨著負(fù)載增加,我們只需要伸縮消費(fèi)者組使其消費(fèi)者的數(shù)量等于主題中分區(qū)的數(shù)量。這就需要我們配置Kafka增加額外的分區(qū)。
但是,隨著負(fù)載再次降低,我們不能移除我們之前增加的分區(qū),這需要給消費(fèi)者增加更多的工作量。不過Kafka SDK已經(jīng)幫我們做了這個(gè)額外的工作。
Kafka分區(qū)沒法移除,向下伸縮后消費(fèi)者會(huì)做更多的工作
結(jié)論
首先是在不考慮一些非功能性限制(如運(yùn)營(yíng)成本,開發(fā)人員對(duì)兩個(gè)平臺(tái)的了解等)的情況下:
優(yōu)先選擇RabbitMQ的條件
- 高級(jí)靈活的路由規(guī)則。
- 消息時(shí)序控制(控制消息過期或者消息延遲)。
- 高級(jí)的容錯(cuò)處理能力,在消費(fèi)者更有可能處理消息不成功的情景中(瞬時(shí)或者持久)。
- 更簡(jiǎn)單的消費(fèi)者實(shí)現(xiàn)。
優(yōu)先選擇Kafka的條件
- 嚴(yán)格的消息順序。
- 延長(zhǎng)消息留存時(shí)間,包括過去消息重放的可能。
- 傳統(tǒng)解決方案無法滿足的高伸縮能力。