Kafka是啥虽缕?用Kafka官方的話來說就是:
Kafka is used for building real-time data pipelines and streaming apps. It is horizontally scalable, fault-tolerant, wicked fast, and runs in production in thousands of companies.
大致的意思就是馋没,這是一個實時數(shù)據(jù)處理系統(tǒng),可以橫向擴(kuò)展惯殊、高可靠锌半,而且還變態(tài)快搂抒,已經(jīng)被很多公司使用扮惦。
那么什么是實時數(shù)據(jù)處理系統(tǒng)呢臀蛛?顧名思義,實時數(shù)據(jù)處理系統(tǒng)就是數(shù)據(jù)一旦產(chǎn)生崖蜜,就要能快速進(jìn)行處理的系統(tǒng)浊仆。
對于實時數(shù)據(jù)處理,我們最常見的豫领,就是消息中間件了抡柿,也叫MQ(Message Queue,消息隊列)氏堤,也有叫Message Broker的沙绝。
這篇文章,我將從消息中間件的角度鼠锈,帶大家看看Kafka的內(nèi)部結(jié)構(gòu),看看它是如何做到橫向擴(kuò)展星著、高可靠的同時购笆,還能變態(tài)快的。
為什么需要消息中間件
消息中間件的作用主要有兩點:
- 解耦消息的生產(chǎn)和消費(fèi)虚循。
- 緩沖同欠。
想象一個場景,你的一個創(chuàng)建訂單的操作横缔,在訂單創(chuàng)建完成之后铺遂,需要觸發(fā)一系列其他的操作,比如進(jìn)行用戶訂單數(shù)據(jù)的統(tǒng)計茎刚、給用戶發(fā)送短信襟锐、給用戶發(fā)送郵件等等,就像這樣:
createOrder(...){
...
statOrderData(...);
sendSMS();
sendEmail();
}
代碼這樣寫似乎沒什么問題膛锭,可是過了一段時間粮坞,你給系統(tǒng)引進(jìn)了一個用戶行為分析服務(wù),它也需要在訂單創(chuàng)建完成之后初狰,進(jìn)行一個分析用戶行為的操作莫杈,而且隨著系統(tǒng)的逐漸壯大,創(chuàng)建訂單之后要觸發(fā)的操作也就越來越多奢入,代碼也漸漸膨脹成這樣:
createOrder(...){
...
statOrderData(...);
sendSMS();
sendEmail();
// new operation
statUserBehavior(...);
doXXX(...);
doYYY(...);
// more and more operations
...
}
導(dǎo)致代碼越來越膨脹的癥結(jié)在于筝闹,消息的生產(chǎn)和消費(fèi)耦合在一起了。createOrder方法不僅僅要負(fù)責(zé)生產(chǎn)“訂單已創(chuàng)建”這條消息,還要負(fù)責(zé)處理這條消息关顷。
這就好比BBC的記者糊秆,在知道皇馬拿到歐冠冠軍之后,拿起手機(jī)解寝,翻開皇馬球迷通訊錄扩然,給球迷一個一個打電話,告訴他們聋伦,皇馬奪冠了夫偶。
事實上,BBC的記者只需要在他們官網(wǎng)發(fā)布這條消息觉增,然后球迷自行訪問BBC兵拢,去上面獲取這條新聞;又或者球迷訂閱了BBC逾礁,那么訂閱系統(tǒng)會主動把發(fā)布在官網(wǎng)的消息推送給球迷说铃。
同樣,createOrder也需要一個像BBC官網(wǎng)那樣的載體嘹履,也就是消息中間件腻扇,在訂單創(chuàng)建完成之后,把一條主題為“orderCreated”的消息砾嫉,放到消息中間件去就ok了幼苛,不必關(guān)心需要把這條消息發(fā)給誰。這就完成了消息的生產(chǎn)焕刮。
至于需要在訂單創(chuàng)建完成之后觸發(fā)操作的服務(wù)舶沿,則只需要訂閱主題為“orderCreated”的消息,在消息中間件出現(xiàn)新的“orderCreated”消息時配并,就會收到這條消息括荡,然后進(jìn)行相應(yīng)的處理。
因此溉旋,通過使用消息中間件畸冲,上面的代碼也就簡化成了:
createOrder(...){
...
sendOrderCreatedMessage(...);
}
以后如果在訂單創(chuàng)建之后有新的操作需要執(zhí)行,這串代碼也不需要修改低滩,只需要給對消息進(jìn)行訂閱即可召夹。
另外,通過這樣的解耦恕沫,消費(fèi)者在消費(fèi)數(shù)據(jù)時更加的靈活监憎,不必每次消息一產(chǎn)生就要馬上去處理(雖然通常消費(fèi)者側(cè)也會有線程池等緩沖機(jī)制),可以等自己有空了的時候婶溯,再過來消息中間件這里取數(shù)據(jù)進(jìn)行處理鲸阔。這就是消息中間件帶來的緩沖作用偷霉。
Kafka一代 - 消息隊列
從上面的描述,我們可以看出褐筛,消息中間件之所以可以解耦消息的生產(chǎn)和消費(fèi)类少,主要是它提供了一個存放消息的地方——生產(chǎn)者把消息放進(jìn)來,消費(fèi)者在從中取出消息進(jìn)行處理渔扎。
那么這個存放消息的地方硫狞,應(yīng)該采用什么數(shù)據(jù)結(jié)構(gòu)呢?
在絕大多數(shù)情況下晃痴,我們都希望先發(fā)送進(jìn)來的消息残吩,可以先被處理(FIFO),這符合大多數(shù)的業(yè)務(wù)邏輯倘核,少數(shù)情況下我們會給消息設(shè)置優(yōu)先級泣侮。不管怎樣,對于消息中間件來說紧唱,一個先進(jìn)先出的隊列活尊,是非常合適的數(shù)據(jù)結(jié)構(gòu):
那么要怎樣保證消息可以被順序消費(fèi)呢?
消費(fèi)者過來獲取消息時漏益,每次都把index=0的數(shù)據(jù)返回過去蛹锰,然后再刪除index=0的那條數(shù)據(jù)?
很明顯不行绰疤,因為訂閱了這條消息的消費(fèi)者數(shù)量宁仔,可能是0,也可能是1峦睡,還可能大于1。如果每次消費(fèi)完就刪除了权埠,那么其他訂閱了這條消息的消費(fèi)者就獲取不到這條消息了榨了。
事實上,Kafka會對數(shù)據(jù)進(jìn)行持久化存儲(至于存放多長時間攘蔽,這是可以配置的)龙屉,消費(fèi)者端會記錄一個offset,表明該消費(fèi)者當(dāng)前消費(fèi)到哪條數(shù)據(jù)满俗,所以下次消費(fèi)者想繼續(xù)消費(fèi)转捕,只需從offset+1的位置繼續(xù)消費(fèi)就好了。
消費(fèi)者甚至可以通過調(diào)整offset的值唆垃,重新消費(fèi)以前的數(shù)據(jù)五芝。
那么這就是Kafka了嗎?不辕万,這只是一條非常普通的消息隊列枢步,我們姑且叫它為Kafka一代吧沉删。
這個Kafka一代用一條消息隊列實現(xiàn)了消息中間件,這樣的簡單實現(xiàn)存在不少問題:
- Topic魚龍混雜醉途。想象一下矾瑰,一個只訂閱了topic為“A”的消費(fèi)者,卻要在一條有ABCDEFG...等各種各樣topic的隊列里頭去尋找topic為A的消息隘擎,這樣性能豈不是很慢殴穴?
- 吞吐量低。我們把全部消息都放在一條隊列了货葬,請求一多采幌,它肯定應(yīng)付不過來。
由此就引申出了Kafka二代宝惰。
Kafka二代 - Partition
要解決Kafka一代的那兩個問題植榕,很簡單——分布存儲。
二代Kafka引入了Partition的概念尼夺,也就是采用多條隊列尊残, 每條隊列里面的消息都是相同的topic:
Partition的設(shè)計解決了上面提到的兩個問題:
- 純Topic隊列。一個隊列只有一種topic淤堵,消費(fèi)者再也不用擔(dān)心會碰到不是自己想要的topic的消息了寝衫。
- 提高吞吐量。不同topic的消息交給不同隊列去存儲拐邪,再也不用以一敵十了慰毅。
一個隊列只有一種topic,但是一種topic的消息卻可以根據(jù)自定義的key值扎阶,分散到多條隊列中汹胃。也就是說,上圖的p1和p2东臀,可以都是同一種topic的隊列着饥。不過這是屬于比較高級的應(yīng)用了,以后有機(jī)會再和大家討論惰赋。
Kafka二代足夠完美了嗎宰掉?當(dāng)然不是,我們雖然通過Partition提升了性能赁濒,但是我們忽略了一個很重要的問題——高可用轨奄。
萬一機(jī)器掛掉了怎么辦?單點系統(tǒng)總是不可靠的拒炎。我們必須考慮備用節(jié)點和數(shù)據(jù)備份的問題挪拟。
Kafka三代 - Broker集群
很明顯,為了解決高可用問題枝冀,我們需要集群舞丛。
Kafka對集群的支持也是非常友好的耘子。在Kafka中,集群里的每個實例叫做Broker球切,就像這樣:
每個partition不再只有一個谷誓,而是有一個leader(紅色)和多個replica(藍(lán)色),生產(chǎn)者根據(jù)消息的topic和key值吨凑,確定了消息要發(fā)往哪個partition之后(假設(shè)是p1)捍歪,會找到partition對應(yīng)的leader(也就是broker2里的p1),然后將消息發(fā)給leader鸵钝,leader負(fù)責(zé)消息的寫入糙臼,并與其余的replica進(jìn)行同步。
一旦某一個partition的leader掛掉了恩商,那么只需提拔一個replica出來变逃,讓它成為leader就ok了,系統(tǒng)依舊可以正常運(yùn)行怠堪。
通過Broker集群的設(shè)計揽乱,我們不僅解決了系統(tǒng)高可用的問題,還進(jìn)一步提升了系統(tǒng)的吞吐量粟矿,因為replica同樣可以為消費(fèi)者提供數(shù)據(jù)查找的功能凰棉。
Kafka沒那么簡單
這篇文章只是帶大家初步認(rèn)識一下Kafka,很多細(xì)節(jié)并沒有深入討論陌粹,比如:
1撒犀、Kafka的消息結(jié)構(gòu)?
我們只知道Kafka內(nèi)部是一個消息隊列掏秩,但是隊列里的元素長什么樣或舞,包含了哪些消息呢?
2蒙幻、Zookeeper和Kafka的關(guān)系嚷那?
如果玩過Kafka的Quick Start教程,就會發(fā)現(xiàn)杆煞,我們在使用Kafka時,需要先啟動一個ZK腐泻,那么這個ZK的作用到底是什么呢决乎?
參考:What-is-the-actual-role-of-Zookeeper-in-Kafka
3、數(shù)據(jù)可靠性和重復(fù)消費(fèi)
生產(chǎn)者把消息發(fā)給Kafka派桩,發(fā)送過程中掛掉构诚、或者Kafka保存消息時發(fā)送異常怎么辦?
同理铆惑,消費(fèi)者獲取消費(fèi)時發(fā)生異常怎么辦范嘱?
甚至送膳,如果消費(fèi)者已經(jīng)消費(fèi)了數(shù)據(jù),但是修改offset時失敗了丑蛤,導(dǎo)致重復(fù)消費(fèi)怎么辦叠聋?
等等這些異常場景,都是Kafka需要考慮的受裹。
參考:Kafka - Message Delivery Semantics
4碌补、 pull or push
消費(fèi)者側(cè)在獲取消息時,是通過主動去pull消息呢棉饶?還是由Kafka給消費(fèi)者push消息厦章?
這兩種方式各自有什么優(yōu)劣?
5照藻、 如何提高消費(fèi)者處理性能
還是之前的訂單創(chuàng)建的例子袜啃,訂單創(chuàng)建后,你要給用戶發(fā)送短信幸缕,現(xiàn)在你發(fā)現(xiàn)由于你只有一個消費(fèi)者在發(fā)送短信群发,忙不過來,怎么辦冀值?這就有了Kafka里頭的消費(fèi)者組(Consumer Group)的設(shè)計也物。
參考:Understanding-kafka-consumer-groups-and-consumer
......
終極問題:一條消息從生產(chǎn),到被消費(fèi)列疗,完整流程是怎樣的滑蚯?
如果能詳盡透徹地回答這個問題,那你對Kafka的理解也就非常深入了抵栈。
總結(jié)
本文從一個演化的視角告材,帶大家在Kafka的后花園里走馬觀花,逛了一圈古劲。
很多細(xì)節(jié)并沒有深入討論斥赋,只是一個引子,希望能起到拋磚引玉的作用产艾。
參考文獻(xiàn)&學(xué)習(xí)資源
官網(wǎng):
一些不錯的博客:
- Kafka-in-a-nutshell(入門絕佳讀物)
- What every software engineer should know about real-time data's unifying abstraction(從這篇文章可以知道LinkedIn為何要開發(fā)Kafka)
- How to choose the number of topics/partitions in a Kafka cluster?(對Kafka Partition的深入講解和性能優(yōu)化指導(dǎo))
書籍(沒看過疤剑,但是感覺不錯的書):
- Kafka權(quán)威指南
- Apache Kafka源碼剖析(可以自己先看看源碼,再看看這本書)