阿里大牛實(shí)戰(zhàn)歸納——Kafka架構(gòu)原理
對(duì)于kafka的架構(gòu)原理我們先提出幾個(gè)問題?
1.Kafka的topic和分區(qū)內(nèi)部是如何存儲(chǔ)的,有什么特點(diǎn)?
2.與傳統(tǒng)的消息系統(tǒng)相比,Kafka的消費(fèi)模型有什么優(yōu)點(diǎn)?
3.Kafka如何實(shí)現(xiàn)分布式的數(shù)據(jù)存儲(chǔ)與數(shù)據(jù)讀取?
一、Kafka架構(gòu)圖
1.kafka名詞解釋
在一套kafka架構(gòu)中有多個(gè)Producer,多個(gè)Broker,多個(gè)Consumer,每個(gè)Producer可以對(duì)應(yīng)多個(gè)Topic,每個(gè)Consumer只能對(duì)應(yīng)一個(gè)ConsumerGroup。
整個(gè)Kafka架構(gòu)對(duì)應(yīng)一個(gè)ZK集群,通過ZK管理集群配置,選舉Leader,以及在consumer group發(fā)生變化時(shí)進(jìn)行rebalance。
名稱
解釋
Broker
消息中間件處理節(jié)點(diǎn),一個(gè)Kafka節(jié)點(diǎn)就是一個(gè)broker,一個(gè)或者多個(gè)Broker可以組成一個(gè)Kafka集群
Topic
主題,Kafka根據(jù)topic對(duì)消息進(jìn)行歸類,發(fā)布到Kafka集群的每條消息都需要指定一個(gè)topic
Producer
消息生產(chǎn)者,向Broker發(fā)送消息的客戶端
Consumer
消息消費(fèi)者,從Broker讀取消息的客戶端
ConsumerGroup
每個(gè)Consumer屬于一個(gè)特定的Consumer Group,一條消息可以發(fā)送到多個(gè)不同的Consumer Group,但是一個(gè)Consumer Group中只能有一個(gè)Consumer能夠消費(fèi)該消息
Partition
物理上的概念,一個(gè)topic可以分為多個(gè)partition,每個(gè)partition內(nèi)部是有序的
2.Topic和Partition
在Kafka中的每一條消息都有一個(gè)topic。一般來說在我們應(yīng)用中產(chǎn)生不同類型的數(shù)據(jù),都可以設(shè)置不同的主題。一個(gè)主題一般會(huì)有多個(gè)消息的訂閱者,當(dāng)生產(chǎn)者發(fā)布消息到某個(gè)主題時(shí),訂閱了這個(gè)主題的消費(fèi)者都可以接收到生產(chǎn)者寫入的新消息。
kafka為每個(gè)主題維護(hù)了分布式的分區(qū)(partition)日志文件,每個(gè)partition在kafka存儲(chǔ)層面是append log。任何發(fā)布到此partition的消息都會(huì)被追加到log文件的尾部,在分區(qū)中的每條消息都會(huì)按照時(shí)間順序分配到一個(gè)單調(diào)遞增的順序編號(hào),也就是我們的offset,offset是一個(gè)long型的數(shù)字,我們通過這個(gè)offset可以確定一條在該partition下的唯一消息。在partition下面是保證了有序性,但是在topic下面沒有保證有序性。
在上圖中在我們的生產(chǎn)者會(huì)決定發(fā)送到哪個(gè)Partition。
- 如果沒有Key值則進(jìn)行輪詢發(fā)送。
- 如果有Key值,對(duì)Key值進(jìn)行Hash,然后對(duì)分區(qū)數(shù)量取余,保證了同一個(gè)Key值的會(huì)被路由到同一個(gè)分區(qū),如果想隊(duì)列的強(qiáng)順序一致性,可以讓所有的消息都設(shè)置為同一個(gè)Key。
3.消費(fèi)模型
消息由生產(chǎn)者發(fā)送到kafka集群后,會(huì)被消費(fèi)者消費(fèi)。一般來說我們的消費(fèi)模型有兩種:推送模型(psuh)和拉取模型(pull)
基于推送模型的消息系統(tǒng),由消息代理記錄消費(fèi)狀態(tài)。消息代理將消息推送到消費(fèi)者后,標(biāo)記這條消息為已經(jīng)被消費(fèi),但是這種方式無法很好地保證消費(fèi)的處理語義。比如當(dāng)我們把已經(jīng)把消息發(fā)送給消費(fèi)者之后,由于消費(fèi)進(jìn)程掛掉或者由于網(wǎng)絡(luò)原因沒有收到這條消息,如果我們?cè)谙M(fèi)代理將其標(biāo)記為已消費(fèi),這個(gè)消息就***丟失了。如果我們利用生產(chǎn)者收到消息后回復(fù)這種方法,消息代理需要記錄消費(fèi)狀態(tài),這種不可取。如果采用push,消息消費(fèi)的速率就完全由消費(fèi)代理控制,一旦消費(fèi)者發(fā)生阻塞,就會(huì)出現(xiàn)問題。
Kafka采取拉取模型(poll),由自己控制消費(fèi)速度,以及消費(fèi)的進(jìn)度,消費(fèi)者可以按照任意的偏移量進(jìn)行消費(fèi)。比如消費(fèi)者可以消費(fèi)已經(jīng)消費(fèi)過的消息進(jìn)行重新處理,或者消費(fèi)最近的消息等等。
4.網(wǎng)絡(luò)模型
4.1 KafkaClient --單線程Selector
單線程模式適用于并發(fā)鏈接數(shù)小,邏輯簡(jiǎn)單,數(shù)據(jù)量小。
在kafka中,consumer和producer都是使用的上面的單線程模式。這種模式不適合kafka的服務(wù)端,在服務(wù)端中請(qǐng)求處理過程比較復(fù)雜,會(huì)造成線程阻塞,一旦出現(xiàn)后續(xù)請(qǐng)求就會(huì)無法處理,會(huì)造成大量請(qǐng)求超時(shí),引起雪崩。而在服務(wù)器中應(yīng)該充分利用多線程來處理執(zhí)行邏輯。
4.2 Kafka--server -- 多線程Selector
在kafka服務(wù)端采用的是多線程的Selector模型,Acceptor運(yùn)行在一個(gè)單獨(dú)的線程中,對(duì)于讀取操作的線程池中的線程都會(huì)在selector注冊(cè)read事件,負(fù)責(zé)服務(wù)端讀取請(qǐng)求的邏輯。成功讀取后,將請(qǐng)求放入message queue共享隊(duì)列中。然后在寫線程池中,取出這個(gè)請(qǐng)求,對(duì)其進(jìn)行邏輯處理,即使某個(gè)請(qǐng)求線程阻塞了,還有后續(xù)的縣城從消息隊(duì)列中獲取請(qǐng)求并進(jìn)行處理,在寫線程中處理完邏輯處理,由于注冊(cè)了OP_WIRTE事件,所以還需要對(duì)其發(fā)送響應(yīng)。
5.高可靠分布式存儲(chǔ)模型
在Kafka中保證高可靠模型的依靠的是副本機(jī)制,有了副本機(jī)制之后,就算機(jī)器宕機(jī)也不會(huì)發(fā)生數(shù)據(jù)丟失。
5.1高性能的日志存儲(chǔ)
kafka一個(gè)topic下面的所有消息都是以partition的方式分布式的存儲(chǔ)在多個(gè)節(jié)點(diǎn)上。同時(shí)在kafka的機(jī)器上,每個(gè)Partition其實(shí)都會(huì)對(duì)應(yīng)一個(gè)日志目錄,在目錄下面會(huì)對(duì)應(yīng)多個(gè)日志分段(LogSegment)。LogSegment文件由兩部分組成,分別為“.index”文件和“.log”文件,分別表示為segment索引文件和數(shù)據(jù)文件。這兩個(gè)文件的命令規(guī)則為:partition全局的***個(gè)segment從0開始,后續(xù)每個(gè)segment文件名為上一個(gè)segment文件***一條消息的offset值,數(shù)值大小為64位,20位數(shù)字字符長(zhǎng)度,沒有數(shù)字用0填充,如下,假設(shè)有1000條消息,每個(gè)LogSegment大小為100,下面展現(xiàn)了900-1000的索引和Log:
由于kafka消息數(shù)據(jù)太大,如果全部建立索引,即占了空間又增加了耗時(shí),所以kafka選擇了稀疏索引的方式,這樣的話索引可以直接進(jìn)入內(nèi)存,加快偏查詢速度。
簡(jiǎn)單介紹一下如何讀取數(shù)據(jù),如果我們要讀取第911條數(shù)據(jù)首先***步,找到他是屬于哪一段的,根據(jù)二分法查找到他屬于的文件,找到0000900.index和00000900.log之后,然后去index中去查找 (911-900) =11這個(gè)索引或者小于11最近的索引,在這里通過二分法我們找到了索引是[10,1367]然后我們通過這條索引的物理位置1367,開始往后找,直到找到911條數(shù)據(jù)。
上面講的是如果要找某個(gè)offset的流程,但是我們大多數(shù)時(shí)候并不需要查找某個(gè)offset,只需要按照順序讀即可,而在順序讀中,操作系統(tǒng)會(huì)對(duì)內(nèi)存和磁盤之間添加page cahe,也就是我們平常見到的預(yù)讀操作,所以我們的順序讀操作時(shí)速度很快。但是kafka有個(gè)問題,如果分區(qū)過多,那么日志分段也會(huì)很多,寫的時(shí)候由于是批量寫,其實(shí)就會(huì)變成隨機(jī)寫了,隨機(jī)I/O這個(gè)時(shí)候?qū)π阅苡绊懞艽蟆K砸话銇碚fKafka不能有太多的partition。針對(duì)這一點(diǎn),RocketMQ把所有的日志都寫在一個(gè)文件里面,就能變成順序?qū)?,通過一定優(yōu)化,讀也能接近于順序讀。
可以思考一下:1.為什么需要分區(qū),也就是說主題只有一個(gè)分區(qū),難道不行嗎?2.日志為什么需要分段
5.2副本機(jī)制
Kafka的副本機(jī)制是多個(gè)服務(wù)端節(jié)點(diǎn)對(duì)其他節(jié)點(diǎn)的主題分區(qū)的日志進(jìn)行復(fù)制。當(dāng)集群中的某個(gè)節(jié)點(diǎn)出現(xiàn)故障,訪問故障節(jié)點(diǎn)的請(qǐng)求會(huì)被轉(zhuǎn)移到其他正常節(jié)點(diǎn)(這一過程通常叫Reblance),kafka每個(gè)主題的每個(gè)分區(qū)都有一個(gè)主副本以及0個(gè)或者多個(gè)副本,副本保持和主副本的數(shù)據(jù)同步,當(dāng)主副本出故障時(shí)就會(huì)被替代。
在Kafka中并不是所有的副本都能被拿來替代主副本,所以在kafka的leader節(jié)點(diǎn)中維護(hù)著一個(gè)ISR(In sync Replicas)集合,翻譯過來也叫正在同步中集合,在這個(gè)集合中的需要滿足兩個(gè)條件:
- 節(jié)點(diǎn)必須和ZK保持連接
- 在同步的過程中這個(gè)副本不能落后主副本太多
另外還有個(gè)AR(Assigned Replicas)用來標(biāo)識(shí)副本的全集,OSR用來表示由于落后被剔除的副本集合,所以公式如下:ISR = leader + 沒有落后太多的副本; AR = OSR+ ISR;
這里先要說下兩個(gè)名詞:HW(高水位)是consumer能夠看到的此partition的位置,LEO是每個(gè)partition的log***一條Message的位置。HW能保證leader所在的broker失效,該消息仍然可以從新選舉的leader中獲取,不會(huì)造成消息丟失。
當(dāng)producer向leader發(fā)送數(shù)據(jù)時(shí),可以通過request.required.acks參數(shù)來設(shè)置數(shù)據(jù)可靠性的級(jí)別:
- 1(默認(rèn)):這意味著producer在ISR中的leader已成功收到的數(shù)據(jù)并得到確認(rèn)后發(fā)送下一條message。如果leader宕機(jī)了,則會(huì)丟失數(shù)據(jù)。
- 0:這意味著producer無需等待來自broker的確認(rèn)而繼續(xù)發(fā)送下一批消息。這種情況下數(shù)據(jù)傳輸效率***,但是數(shù)據(jù)可靠性確是***的。
- -1:producer需要等待ISR中的所有follower都確認(rèn)接收到數(shù)據(jù)后才算一次發(fā)送完成,可靠性***。但是這樣也不能保證數(shù)據(jù)不丟失,比如當(dāng)ISR中只有l(wèi)eader時(shí)(其他節(jié)點(diǎn)都和zk斷開連接,或者都沒追上),這樣就變成了acks=1的情況。