Kafka是一個知名的流處理框架,同時也可以作為消息隊列使用,但至少在官方描述中狮惜,Kafka不被承認(rèn)是消息隊列服務(wù)。
一開始我對官方這個描述是無感知的碌识,因為Kafka太優(yōu)秀了碾篡,性能優(yōu)越、低延遲筏餐、真正的分布式服務(wù)开泽,理論上是可以勝任消息隊列服務(wù)的,但是后來我發(fā)現(xiàn)我錯了魁瞪。
起源是在一個高并發(fā)項目中使用Kafka穆律,因為并發(fā)量比較大,所以使用Kafka作為削峰作用导俘。至此我仍然看不出所謂流處理跟消息隊列有什么區(qū)別峦耘,因為這部分的任務(wù)都非常簡單,純純是對數(shù)據(jù)庫的插入操作旅薄,可以在極短的時間內(nèi)完成辅髓,這部分Kafka跑得很好。
但是后面我有一些長時間的任務(wù),需要到消息隊列洛口,出于程序員慣有的技術(shù)惰性矫付,我也懶于再增加一個類似Rabbitmq的經(jīng)典消息隊列服務(wù),認(rèn)為Kafka也能勝任這個任務(wù)绍弟,但事實證明我錯了技即,我付出了比選型Rabbitmq百倍的代價,才將Kafka調(diào)教得勉強能作為消息隊列使用樟遣,而且并沒有像Rabbitmq那樣好用。
一切的悲劇在于我忽視了官方那句話:“Kafka并不是消息隊列服務(wù)”身笤。
分析
我總結(jié)了一下Kafka不適合作為長時間任務(wù)的消息隊列服務(wù)的原因:
-
超時問題
作為真正的分布式服務(wù)豹悬,Kafka不但保證服務(wù)端是高可用的,并且保證消費者也是高可用的液荸,因此就需要有一定手段來檢測客戶端是否存活瞻佛,而檢測方案是通過心跳檢測來實現(xiàn)的,一旦心跳間隔大于預(yù)設(shè)的超時時間娇钱,即可判斷消費者是失活狀態(tài)伤柄,從而將它暫時踢出消費者組。
但是讓我感到無比震驚的是文搂,Kafka居然是直接用任務(wù)本身作為心跳機制适刀。什么意思呢?就是接收任務(wù)和完成任務(wù)ACK作為心跳煤蹭,并且沒有任何的配置可以新開一個線程來做心跳存活笔喉。
這樣會給長時間任務(wù)帶來一個矛盾點:
如果超時時間設(shè)置得太短,任務(wù)還沒跑完硝皂,Kafka服務(wù)端會認(rèn)定心跳檢測不通過常挚,從而認(rèn)為消費者存在故障,于是將任務(wù)重新分配給其他消費者稽物,這將導(dǎo)致任務(wù)被重復(fù)消費奄毡,這個過程會一直輪回,最終導(dǎo)致所有消費者都卡在同一任務(wù)上不停重復(fù)執(zhí)行贝或;
如果超時時間設(shè)置得太長吼过,那任務(wù)是可以正常跑完,但是心跳的機制就完全失效傀缩,更加可怕的是那先,消費者要是故障退出,那服務(wù)端也需要等待漫長的超時時間才能認(rèn)為消費者是異常退出了赡艰,即使你把消費者重新拉起來售淡,也沒用,因為服務(wù)端會認(rèn)為之前的任務(wù)還在運行中,需要等待超時時間過去了揖闸,任務(wù)才能重新被消費揍堕。
我查閱了大量資料,也沒有找到解決辦法汤纸,因為衩茸,確實沒有任何的配置可以新開一個線程來做心跳存活,那怎么整都是白搭贮泞。
-
分片機制
不同于Rabbitmq的搶占式消費楞慈,Kafka的消費能力完全取決于topic的分片數(shù)量,體現(xiàn)有兩點:
每個分片必須對應(yīng)一個消費者啃擦,換言之囊蓝,消費者組里面的消費者數(shù)量比分片少的時候,將會有部分分片沒法被消費令蛉。
當(dāng)Kafka消費者組中的消費者數(shù)量大于topic的分片(分區(qū))數(shù)量時聚霜,多余的消費者會處于備用狀態(tài),不會消費任何消息珠叔。每個分區(qū)只能分配給一個消費者蝎宇,所以多余的消費者會空閑等待,跟CPU上的一核有難多核圍觀差不多祷安。
這就導(dǎo)致消費者無法像Rabbitmq那樣無限水平伸縮姥芥,并且Kafak的分片只能伸不能縮,要是你前期伸得太多了辆憔,那就只能哭了撇眯,祈求領(lǐng)導(dǎo)給你分配的消費者資源足夠多吧。
-
沒有優(yōu)先級
優(yōu)先級是消息隊列非常經(jīng)典的功能虱咧,而Kafka是沒有的熊榛,畢竟它并非傳統(tǒng)意義的上消息隊列。偏要在Kafka上實現(xiàn)優(yōu)先級的話腕巡,那得按優(yōu)先級設(shè)置多個topic和多組消費者玄坦,屬實不方便。
為了解決上述1和2兩個問題绘沉,我想出了一個尚且能解決問題的辦法煎楣,那就是在大循環(huán)里面,按照“消費者連接服務(wù)端->獲取消息->消費者退出->處理消息”這樣循環(huán)车伞,這樣在一定程度上能緩解癥狀择懂,但卻喪失了ACK的作用,并且顯得與正常的Kafka消費方式格格不入另玖。