1. 概述
Kakfa起初是由LinkedIn公司開發(fā)的一個(gè)分布式检眯、支持分區(qū)的(partition)魔眨、多副本的(replica)舌剂,基于zookeeper協(xié)調(diào)的分布式消息系統(tǒng)裕便,它的最大的特性就是可以實(shí)時(shí)的處理大量數(shù)據(jù)以滿足各種需求場景:比如基于hadoop的批處理系統(tǒng)请垛、低延遲的實(shí)時(shí)系統(tǒng)催训、storm/Spark流式處理引擎,web/nginx日志宗收、訪問日志漫拭,消息服務(wù)等等。后成為Apache的一部分混稽,它使用Scala編寫采驻,以可水平擴(kuò)展和高吞吐率而被廣泛使用。
消息隊(duì)列的性能好壞匈勋,其文件存儲(chǔ)機(jī)制設(shè)計(jì)是衡量一個(gè)消息隊(duì)列服務(wù)技術(shù)水平和最關(guān)鍵指標(biāo)之一礼旅。下面將從Kafka文件存儲(chǔ)機(jī)制和物理結(jié)構(gòu)角度,分析Kafka是如何實(shí)現(xiàn)高效文件存儲(chǔ)洽洁,及實(shí)際應(yīng)用效果痘系。
2. Kafka的特性
- 高吞吐量、低延遲:kafka每秒可以處理幾十萬條消息饿自,它的延遲最低只有幾毫秒汰翠,每個(gè)topic可以分多個(gè)partition, consumer group 對(duì)partition進(jìn)行consume操作龄坪。
- 可擴(kuò)展性:kafka集群支持熱擴(kuò)展
- 持久性、可靠性:消息被持久化到本地磁盤复唤,并且支持?jǐn)?shù)據(jù)備份防止數(shù)據(jù)丟失
- 容錯(cuò)性:允許集群中節(jié)點(diǎn)失斀√铩(若副本數(shù)量為n,則允許n-1個(gè)節(jié)點(diǎn)失敗)
- 高并發(fā):支持?jǐn)?shù)千個(gè)客戶端同時(shí)讀寫
3. Kafka體系架構(gòu)
一個(gè)典型的Kafka體系架構(gòu)包括若干Producer(可以是服務(wù)器日志佛纫,業(yè)務(wù)數(shù)據(jù)抄课,頁面前端產(chǎn)生的page view等等),若干broker(Kafka支持水平擴(kuò)展雳旅,一般broker數(shù)量越多跟磨,集群吞吐率越高),若干Consumer (Group)攒盈,以及一個(gè)Zookeeper集群抵拘。Kafka通過Zookeeper管理集群配置,選舉leader型豁,以及在consumer group發(fā)生變化時(shí)進(jìn)行rebalance僵蛛。Producer使用push(推)模式將消息發(fā)布到broker,Consumer使用pull(拉)模式從broker訂閱并消費(fèi)消息迎变。
4. Topic & Partition
一個(gè)topic可以認(rèn)為一個(gè)一類消息充尉,每個(gè)topic將被分成多個(gè)partition,每個(gè)partition在存儲(chǔ)層面是append log文件衣形。任何發(fā)布到此partition的消息都會(huì)被追加到log文件的尾部驼侠,每條消息在文件中的位置稱為offset(偏移量),offset為一個(gè)long型的數(shù)字谆吴,它唯一標(biāo)記一條消息倒源。每條消息都被append到partition中,是順序?qū)懘疟P句狼,因此效率非常高(經(jīng)驗(yàn)證笋熬,順序?qū)懘疟P效率比隨機(jī)寫內(nèi)存還要高,這是Kafka高吞吐率的一個(gè)很重要的保證)腻菇。
每一條消息被發(fā)送到broker中胳螟,會(huì)根據(jù)partition規(guī)則選擇被存儲(chǔ)到哪一個(gè)partition。如果partition規(guī)則設(shè)置的合理筹吐,所有消息可以均勻分布到不同的partition里糖耸,這樣就實(shí)現(xiàn)了水平擴(kuò)展。
在發(fā)送一條消息時(shí)骏令,可以指定這個(gè)消息的key蔬捷,producer根據(jù)這個(gè)key和partition機(jī)制來判斷這個(gè)消息發(fā)送到哪個(gè)partition。
5. 數(shù)據(jù)可靠性和持久性保證
當(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í)(前面ISR那一節(jié)講到播急,ISR中的成員由于某些情況會(huì)增加也會(huì)減少脓钾,最少就只剩一個(gè)leader),這樣就變成了acks=1的情況桩警。
6. Leader選舉
一條消息只有被ISR中的所有follower都從leader復(fù)制過去才會(huì)被認(rèn)為已提交可训。這樣就避免了部分?jǐn)?shù)據(jù)被寫進(jìn)了leader,還沒來得及被任何follower復(fù)制就宕機(jī)了捶枢,而造成數(shù)據(jù)丟失握截。而對(duì)于producer而言,它可以選擇是否等待消息commit烂叔,這可以通過request.required.acks來設(shè)置谨胞。這種機(jī)制確保了只要ISR中有一個(gè)或者以上的follower,一條被commit的消息就不會(huì)丟失蒜鸡。
有一個(gè)很重要的問題是當(dāng)leader宕機(jī)了胯努,怎樣在follower中選舉出新的leader,因?yàn)閒ollower可能落后很多或者直接crash了术瓮,所以必須確保選擇“最新”的follower作為新的leader康聂。一個(gè)基本的原則就是,如果leader不在了胞四,新的leader必須擁有原來的leader commit的所有消息。這就需要做一個(gè)折中伶椿,如果leader在表名一個(gè)消息被commit前等待更多的follower確認(rèn)辜伟,那么在它掛掉之后就有更多的follower可以成為新的leader,但這也會(huì)造成吞吐率的下降脊另。
一種非常常用的選舉leader的方式是“少數(shù)服從多數(shù)”导狡,Kafka并不是采用這種方式。這種模式下偎痛,如果我們有2f+1個(gè)副本旱捧,那么在commit之前必須保證有f+1個(gè)replica復(fù)制完消息,同時(shí)為了保證能正確選舉出新的leader,失敗的副本數(shù)不能超過f個(gè)枚赡。這種方式有個(gè)很大的優(yōu)勢氓癌,系統(tǒng)的延遲取決于最快的幾臺(tái)機(jī)器,也就是說比如副本數(shù)為3贫橙,那么延遲就取決于最快的那個(gè)follower而不是最慢的那個(gè)贪婉。“少數(shù)服從多數(shù)”的方式也有一些劣勢卢肃,為了保證leader選舉的正常進(jìn)行疲迂,它所能容忍的失敗的follower數(shù)比較少挟裂,如果要容忍1個(gè)follower掛掉糕珊,那么至少要3個(gè)以上的副本,如果要容忍2個(gè)follower掛掉萍嬉,必須要有5個(gè)以上的副本幅垮。也就是說优质,在生產(chǎn)環(huán)境下為了保證較高的容錯(cuò)率,必須要有大量的副本军洼,而大量的副本又會(huì)在大數(shù)據(jù)量下導(dǎo)致性能的急劇下降巩螃。這種算法更多用在Zookeeper這種共享集群配置的系統(tǒng)中而很少在需要大量數(shù)據(jù)的系統(tǒng)中使用的原因。HDFS的HA功能也是基于“少數(shù)服從多數(shù)”的方式匕争,但是其數(shù)據(jù)存儲(chǔ)并不是采用這樣的方式避乏。
實(shí)際上,leader選舉的算法非常多甘桑,比如Zookeeper的Zab拍皮、Raft以及Viewstamped Replication。而Kafka所使用的leader選舉算法更像是微軟的PacificA算法跑杭。
Kafka在Zookeeper中為每一個(gè)partition動(dòng)態(tài)的維護(hù)了一個(gè)ISR铆帽,這個(gè)ISR里的所有replica都跟上了leader,只有ISR里的成員才能有被選為leader的可能(unclean.leader.election.enable=false)德谅。在這種模式下爹橱,對(duì)于f+1個(gè)副本,一個(gè)Kafka topic能在保證不丟失已經(jīng)commit消息的前提下容忍f個(gè)副本的失敗窄做,在大多數(shù)使用場景下愧驱,這種模式是十分有利的。事實(shí)上椭盏,為了容忍f個(gè)副本的失敗组砚,“少數(shù)服從多數(shù)”的方式和ISR在commit前需要等待的副本的數(shù)量是一樣的,但是ISR需要的總的副本的個(gè)數(shù)幾乎是“少數(shù)服從多數(shù)”的方式的一半掏颊。
上文提到糟红,在ISR中至少有一個(gè)follower時(shí),Kafka可以確保已經(jīng)commit的數(shù)據(jù)不丟失,但如果某一個(gè)partition的所有replica都掛了盆偿,就無法保證數(shù)據(jù)不丟失了柒爸。這種情況下有兩種可行的方案:
- 等待ISR中任意一個(gè)replica“活”過來,并且選它作為leader
- 選擇第一個(gè)“活”過來的replica(并不一定是在ISR中)作為leader
這就需要在可用性和一致性當(dāng)中作出一個(gè)簡單的抉擇陈肛。如果一定要等待ISR中的replica“活”過來揍鸟,那不可用的時(shí)間就可能會(huì)相對(duì)較長。而且如果ISR中所有的replica都無法“活”過來了句旱,或者數(shù)據(jù)丟失了阳藻,這個(gè)partition將永遠(yuǎn)不可用。選擇第一個(gè)“活”過來的replica作為leader,而這個(gè)replica不是ISR中的replica,那即使它并不保障已經(jīng)包含了所有已commit的消息谈撒,它也會(huì)成為leader而作為consumer的數(shù)據(jù)源腥泥。默認(rèn)情況下,Kafka采用第二種策略啃匿,即unclean.leader.election.enable=true蛔外,也可以將此參數(shù)設(shè)置為false來啟用第一種策略