一文讀懂Apache Kafka服務(wù)端設(shè)計(jì)

前言

本文簡單介紹了Apache Kafka服務(wù)端的一些設(shè)計(jì)嚼蚀,因?yàn)闆]有詳細(xì)的介紹一些基礎(chǔ)概念和適合有對(duì)Kafka有一定了解的同學(xué)們閱讀链快。如果想深入了解Kafka凉敲,推薦閱讀Kafka官方文檔源代碼着倾。

Kafka被設(shè)計(jì)出來的目標(biāo)是做為海量實(shí)時(shí)數(shù)據(jù)傳輸?shù)姆植际綌?shù)據(jù)流平臺(tái)拧簸,主要用來傳輸和聚合日志數(shù)據(jù)劲绪、追蹤網(wǎng)站活動(dòng)、傳輸監(jiān)控?cái)?shù)據(jù)和作為消息隊(duì)列等盆赤。為了滿足這些功能珠叔,Kafka需要具有如下特性:

  1. 高吞吐
    作為海量數(shù)據(jù)的傳輸平臺(tái),Kafka需要極大的吞吐量來保證海量數(shù)據(jù)的傳輸弟劲。
  2. 低延遲
    實(shí)時(shí)數(shù)據(jù)處理最重要的指標(biāo)之一就是延遲,需要低延遲來保證它作為消息隊(duì)列時(shí)的性能姥芥。
  3. 高可用
    為了避免服務(wù)器故障兔乞、JVM崩潰等問題帶來的數(shù)據(jù)丟失情況,需要具備較好的容錯(cuò)性凉唐。
  4. 多次消費(fèi)
    據(jù)統(tǒng)計(jì)庸追,在LinkedIn公司內(nèi)Kafka中的每條消息平均要被消費(fèi)5次以上。為了支持海量數(shù)據(jù)的可重復(fù)消費(fèi)台囱,Kafka需要很大的容量淡溯。

為了實(shí)現(xiàn)這些特性,Kafka使用了相當(dāng)多的”黑科技“簿训。下面讓我們一一解讀一下咱娶。

一、順序?qū)懭?/h2>

可以說强品,Kafka是重度依賴文件系統(tǒng)的膘侮,它會(huì)把所有的數(shù)據(jù)寫入到硬盤上〉拈唬可是按照我們平時(shí)的理解琼了,對(duì)硬盤的讀寫不是很慢么?其實(shí)還真不一定夫晌。要理解影響硬盤讀寫速度的因素雕薪,首先我們要了解硬盤的結(jié)構(gòu)昧诱。

硬盤結(jié)構(gòu)

首先,我們來看一張硬盤的結(jié)構(gòu)圖:


磁盤結(jié)構(gòu)圖1所袁,圖片來源于網(wǎng)絡(luò)

如上圖所示盏档,磁盤主要由磁盤盤片、傳動(dòng)手臂纲熏、讀寫磁頭和主軸組成妆丘。為了更好的利用盤片資源,每張盤片的兩面都可以記錄信息局劲,所以每張盤片會(huì)對(duì)應(yīng)上下兩個(gè)磁頭讀寫數(shù)據(jù)勺拣。由于單張盤片能存儲(chǔ)的數(shù)據(jù)量有限,所以一般磁盤都有多個(gè)盤片鱼填。盤面被分為許多扇形區(qū)域药有,稱為扇區(qū)。圍繞著盤面中心的不同半徑的同心圓被稱為磁道苹丸。不同盤片間相同半徑的磁道組成的圓柱體稱為柱面愤惰。如下圖所示:

磁盤結(jié)構(gòu)圖2,圖片來源于網(wǎng)絡(luò)

硬盤的讀寫過程

磁盤中的數(shù)據(jù)全部存儲(chǔ)在磁盤的盤片上面赘理,讀取數(shù)據(jù)時(shí)轉(zhuǎn)動(dòng)主軸到指定位置宦言,傳動(dòng)手臂進(jìn)行伸展,最后由讀寫磁頭完成實(shí)際的讀寫操作商模。那么為什么大家會(huì)覺得硬盤的讀寫很慢呢奠旺?因?yàn)橐淮斡脖PIO需要以下三個(gè)步驟:

  1. 尋道
    磁盤要想讀寫數(shù)據(jù),首先要找到正確的磁道施流。讀寫磁頭移動(dòng)到需要被讀寫的磁道上的時(shí)間被稱為尋道時(shí)間响疚。
  2. 旋轉(zhuǎn)
    旋轉(zhuǎn)、跳躍瞪醋,磁盤閉著眼忿晕。要想讀寫數(shù)據(jù),光找到正確的磁道還不夠银受,硬盤要通過主軸的旋轉(zhuǎn)找到正確的扇區(qū)践盼。磁盤通過旋轉(zhuǎn)找到正確扇區(qū)的時(shí)間被稱為旋轉(zhuǎn)延遲。我們平時(shí)經(jīng)常聽到的這種磁盤7200轉(zhuǎn)宾巍,那種磁盤15000轉(zhuǎn)宏侍,指的就是磁盤的轉(zhuǎn)速(每分鐘能轉(zhuǎn)多少圈)。轉(zhuǎn)的越快蜀漆,旋轉(zhuǎn)延遲越短谅河,IO速度越快。
  3. 數(shù)據(jù)傳輸
    到這里才是數(shù)據(jù)才能真正進(jìn)行讀寫。數(shù)據(jù)傳輸?shù)乃俣群芸毂了#靡恍┑拇疟P通常能達(dá)到百兆甚至幾百兆每秒吐限。

看到這里我們知道了,一次完整的磁盤IO時(shí)間實(shí)際上為:

尋道時(shí)間 + 旋轉(zhuǎn)延遲 + 數(shù)據(jù)傳輸時(shí)間

一般的磁盤操作褂始,絕大部分的時(shí)間花在了前兩個(gè)步驟上诸典。也就是說,對(duì)磁盤進(jìn)行順序讀寫很快(因?yàn)榛静挥眠M(jìn)行前兩個(gè)步驟)崎苗,而隨機(jī)讀寫就很慢了狐粱。根據(jù)Kafka官方給出的數(shù)據(jù),在7200rpm/s的SATA RAID-5磁盤陣列上進(jìn)行順序?qū)懭胨俣冗_(dá)到600MB/sec胆数,而隨機(jī)寫入大概只有100KB/sec肌蜻,相差了6000倍!而Kafka正是使用了順序讀寫必尼,才能獲得如此高的性能蒋搜。

二、Page Cache與Memory Map

如果僅僅使用順序讀寫判莉,那么Kafka也不會(huì)有現(xiàn)在這么好的性能豆挽。事實(shí)上,Kafka充分利用了現(xiàn)代操作系統(tǒng)中的文件緩存系統(tǒng)券盅。

在現(xiàn)代操作系統(tǒng)中帮哈,為了彌補(bǔ)硬盤寫入的速度的不足,系統(tǒng)越來越激進(jìn)的使用內(nèi)存作為文件系統(tǒng)的緩存锰镀,甚至?xí)褂盟锌臻e的內(nèi)存作為磁盤緩存(即page cache)但汞。Page cache提供了預(yù)讀和回寫功能。簡單來說互站,預(yù)讀就是當(dāng)順序讀取文件內(nèi)容時(shí),page cache會(huì)提前將當(dāng)前讀取頁面之后的幾個(gè)頁面也加載到page cache當(dāng)中僵缺,這樣程序相當(dāng)于直接讀取cache中的內(nèi)容胡桃,而不必直接與磁盤交互】某保回寫就是當(dāng)磁盤進(jìn)行寫入時(shí)翠胰,會(huì)寫入到page cache當(dāng)中,由操作系統(tǒng)在恰當(dāng)?shù)臅r(shí)候再寫入磁盤自脯。很多人不知道的是之景,所有我們的常規(guī)IO操作全部都要經(jīng)過page cache,這個(gè)特性是在操作系統(tǒng)層面決定的膏潮,很難取消掉锻狗。

有了page cache,一切看起來都很美好∏峒停可實(shí)際情況是油额,這里面仍然存在一些問題。首先刻帚,當(dāng)我們使用常規(guī)方式讀取文件內(nèi)容時(shí)潦嘶,系統(tǒng)內(nèi)核必須將page cache中的文件內(nèi)容復(fù)制到user buffer中。這不僅浪費(fèi)了CPU時(shí)間崇众,而且還將導(dǎo)致系統(tǒng)的物理內(nèi)存中出現(xiàn)兩份數(shù)據(jù)掂僵,浪費(fèi)了物理內(nèi)存空間。另外顷歌,由于Kafka是構(gòu)建在JVM上的锰蓬,對(duì)于JVM比較了解的同學(xué)都會(huì)知道這樣兩條規(guī)律:

  1. JVM中對(duì)象消耗的內(nèi)存非常大,經(jīng)常會(huì)達(dá)到實(shí)際數(shù)據(jù)的兩倍甚至更多衙吩。
  2. 隨著數(shù)據(jù)量的增長互妓,JVM的垃圾回收將會(huì)越來越慢,甚至不可忍受坤塞。

所以基于以上考慮冯勉,Kafka并沒有使用常規(guī)的磁盤操作,而是使用了Memory-mapped files摹芙。當(dāng)使用Memory-mapped files時(shí)灼狰,系統(tǒng)內(nèi)核會(huì)將程序的virtual memory直接映射到page cache,使我們可以把文件數(shù)據(jù)當(dāng)做內(nèi)存數(shù)據(jù)一樣操作浮禾。這樣不僅避免了數(shù)據(jù)在內(nèi)核空間和用戶空間之間復(fù)制交胚,也避免了使用java對(duì)象帶來的一些問題,從而極大提高了Kafka讀寫效率盈电。在java的NIO中提供了使用memory-mapped files的api蝴簇,即MappedByteBuffer(繼承自ByteBuffer),感興趣的同學(xué)可以去深入研究匆帚。關(guān)于page cache和memory-mapped files熬词,可以閱讀這篇博客:Page Cache, the Affair Between Memory and Files

三吸重、Zero-Copy

按照前兩節(jié)所講述的互拾,我們使用順序讀寫最大化磁盤性能;使用page cache和高效的memory-mapped files嚎幸,避免對(duì)磁盤進(jìn)行直接操作颜矿。按道理來講,性能上應(yīng)該非常出色了嫉晶。但是盡管如此骑疆,還是有兩個(gè)問題影響著系統(tǒng)的性能:頻繁的小數(shù)據(jù)量網(wǎng)絡(luò)IO操作和過多的字節(jié)拷貝田篇。

為了避免頻繁的網(wǎng)絡(luò)往返帶來的性能開銷,Kafka將消息組合在一起形成一個(gè)“消息集”封断。使用這種方式可以將消息分批發(fā)送斯辰,而不是單條發(fā)送,從而分?jǐn)偭司W(wǎng)絡(luò)往返的開銷坡疼。當(dāng)數(shù)據(jù)量巨大的時(shí)候彬呻,這種方式可以極大的提升網(wǎng)絡(luò)IO的性能。Kafka的生產(chǎn)者和消費(fèi)者都是采用這種方式向Kafka發(fā)送數(shù)據(jù)和從Kafka拉取數(shù)據(jù)的柄瑰。

接下來我們來介紹一下zero-copy闸氮。Kafka使用了Linux的系統(tǒng)調(diào)用sendfile來發(fā)送系統(tǒng)中的消息,為了了解sendfile系統(tǒng)調(diào)用帶來的優(yōu)勢(shì)教沾,我們先來了解一下通過socket發(fā)送數(shù)據(jù)的傳統(tǒng)方式:

傳統(tǒng)數(shù)據(jù)發(fā)送方式蒲跨,圖片來源于網(wǎng)絡(luò)

由上圖我們可以看到,如果要將磁盤上的數(shù)據(jù)發(fā)送出去授翻,需要經(jīng)過以下四個(gè)步驟:

  1. 操作系統(tǒng)從磁盤讀取數(shù)據(jù)或悲,并寫入到內(nèi)核空間的page cache中。
  2. 應(yīng)用程序從內(nèi)核空間讀取數(shù)據(jù)堪唐,并復(fù)制到用戶空間中巡语。
  3. 應(yīng)用程序?qū)⒂脩艨臻g中的數(shù)據(jù)寫回到內(nèi)核空間的socket緩沖中去。
  4. 操作系統(tǒng)將socket緩沖中的數(shù)據(jù)復(fù)制到網(wǎng)卡緩沖中淮菠,并經(jīng)過網(wǎng)卡發(fā)送出去男公。

可以看到這種傳統(tǒng)發(fā)送數(shù)據(jù)的方式經(jīng)過了四次數(shù)據(jù)復(fù)制和兩次系統(tǒng)調(diào)用,效率很差合陵。那么使用sendfile系統(tǒng)調(diào)用后是什么情況呢枢赔?

sendfile數(shù)據(jù)發(fā)送方式,圖片來源于網(wǎng)絡(luò)

從圖中我們可以看到拥知,使用sendfile可以直接從page cache復(fù)制數(shù)據(jù)到網(wǎng)卡緩沖踏拜,避免了不必要的系統(tǒng)調(diào)用和數(shù)據(jù)復(fù)制,非常高效低剔。

由于Kafka的一個(gè)topic往往有多個(gè)消費(fèi)者組在消費(fèi)速梗,所以采用zero-copy的方式,讓數(shù)據(jù)只從磁盤讀取到page cache一次户侥,就可以服務(wù)所有的消費(fèi)了。通過使用page cache和sendfile峦嗤,在消費(fèi)者消費(fèi)Kafka中數(shù)據(jù)的時(shí)候蕊唐,磁盤幾乎沒有任何讀取活動(dòng),全部的數(shù)據(jù)都來自于page cache中烁设。

在java中替梨,java.nio.channels.FileChannel類提供了transferTo()方法來實(shí)現(xiàn)zero copy(當(dāng)然還取決與操作系統(tǒng)钓试,在Unix和多數(shù)Linux上transferTo()方法會(huì)進(jìn)行sendfile系統(tǒng)調(diào)用)。

四副瀑、端到端批量壓縮

很多時(shí)候弓熏,數(shù)據(jù)傳輸?shù)男阅芷款i不在于CPU或硬盤,而在于網(wǎng)絡(luò)帶寬糠睡。這種情況在遠(yuǎn)距離的公網(wǎng)傳輸中最為常見挽鞠。為了解決這個(gè)問題,Kafka提供了端到端的批量壓縮功能狈孔。雖然用戶也可以對(duì)每條消息自行壓縮信认,但是一些數(shù)據(jù)格式可能導(dǎo)致單條壓縮的壓縮比較低。舉例來說均抽,在一批JSON數(shù)據(jù)中嫁赏,字段名稱其實(shí)是重復(fù)的,單條壓縮會(huì)造成很多冗余油挥。

而Kafka把一批消息抽象為“消息集”(上節(jié)講到過)潦蝇,producer對(duì)數(shù)據(jù)集進(jìn)行壓縮,這些數(shù)據(jù)將會(huì)以被壓縮的格式傳輸?shù)椒?wù)器并寫入到數(shù)據(jù)日志中深寥,只有當(dāng)消費(fèi)者讀取這些數(shù)據(jù)后它們才會(huì)被解壓縮攘乒。Kafka目前支持GZIP,Snappy和LZ4壓縮方式翩迈。

五持灰、ISR

Kafka使用ISR機(jī)制來保證系統(tǒng)的高可用。在創(chuàng)建topic時(shí)负饲,我們可以通過設(shè)置replication-factor參數(shù)來控制topic的復(fù)制因子(之后通過Kafka提供的工具也可以動(dòng)態(tài)改變這個(gè)參數(shù))堤魁。比如在下面創(chuàng)建topic的語句中:

bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 5 --topic test

我們?cè)O(shè)置了 replication-factor 為3,即有三個(gè)副本返十。副本的作用就是當(dāng)集群中的某個(gè)服務(wù)器發(fā)生故障時(shí)妥泉,系統(tǒng)可以自動(dòng)使用其他服務(wù)器上的副本提供服務(wù),不會(huì)影響到消息的生產(chǎn)和消費(fèi)洞坑。

副本的單位是partition盲链。每個(gè)partition會(huì)有一個(gè)leader,零或多個(gè)follower迟杂。所有l(wèi)eader和follower的數(shù)量加在一起就是replication-factor參數(shù)的值刽沾。比如上面設(shè)置了replication-factor為3,那么這個(gè)topic中的每個(gè)partition就有1個(gè)leader和2個(gè)follower排拷。在對(duì)topic的partition進(jìn)行讀寫時(shí)侧漓,所有的讀寫操作都會(huì)去直接請(qǐng)求leader,follower只是被動(dòng)的去同步leader中的消息监氢。而follower中的消息布蔗,不論是消息的順序還是offset全部與leader相同藤违。當(dāng)然,由于消息先被寫入leader纵揍,follower再去拉取數(shù)據(jù)顿乒,所以同步上會(huì)存在很小一段時(shí)間的延遲。

與一般的分布式系統(tǒng)不同泽谨,Kafka沒有使用“alive”或者“failed”來標(biāo)志副本的存活情況璧榄,而是使用了一個(gè)新的概念:“in-sync”。所有在“in-sync”狀態(tài)的replication(副本)構(gòu)成了這個(gè)partition的“同步副本隊(duì)列”隔盛,即ISR犹菱。那么Kafka如何判斷一個(gè)replication是否在“in-sync”狀態(tài)下呢?

  1. 副本所在的節(jié)點(diǎn)必須持有 Zookeeper的session吮炕。
  2. 副本復(fù)制leader上寫入消息的位置不能“落后太多”腊脱。

如果違反了其中任意一條,那么這個(gè)副本會(huì)被暫時(shí)移出ISR隊(duì)列龙亲,當(dāng)它重新滿足這兩條要求時(shí)陕凹,又會(huì)被加入進(jìn)來。當(dāng)然鳄炉,如果leader掛掉杜耙,那么會(huì)有一個(gè)follower被選舉成為新的leader,為partition的讀寫提供服務(wù)拂盯。使用命令:

bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic my-topic

可以看到此topic下每個(gè)partition的replicas和ISR情況佑女。

總結(jié)

作為目前最流行的分布式消息系統(tǒng),Apache Kafka的很多設(shè)計(jì)都非常精妙谈竿,值得我們學(xué)習(xí)和借鑒团驱。由于篇幅所限,本文只是簡單的列舉了一些Kafka服務(wù)端設(shè)計(jì)中的主要內(nèi)容空凸,還有許多其他的內(nèi)容沒有寫出來嚎花,而這些設(shè)計(jì)的具體代碼實(shí)現(xiàn)也遠(yuǎn)比本文中這些三言兩句的復(fù)述要復(fù)雜的多。學(xué)習(xí)任何開源項(xiàng)目最好的途徑就是官方文檔與源代碼呀洲,歡迎各位感興趣的同學(xué)去深入挖掘和研究紊选。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市道逗,隨后出現(xiàn)的幾起案子兵罢,更是在濱河造成了極大的恐慌,老刑警劉巖滓窍,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卖词,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡贰您,警方通過查閱死者的電腦和手機(jī)坏平,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锦亦,“玉大人舶替,你說我怎么就攤上這事「茉埃” “怎么了顾瞪?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抛蚁。 經(jīng)常有香客問我陈醒,道長,這世上最難降的妖魔是什么瞧甩? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任钉跷,我火速辦了婚禮,結(jié)果婚禮上肚逸,老公的妹妹穿的比我還像新娘爷辙。我一直安慰自己,他們只是感情好朦促,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布膝晾。 她就那樣靜靜地躺著,像睡著了一般务冕。 火紅的嫁衣襯著肌膚如雪血当。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天禀忆,我揣著相機(jī)與錄音臊旭,去河邊找鬼。 笑死油湖,一個(gè)胖子當(dāng)著我的面吹牛巍扛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乏德,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼撤奸,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了喊括?” 一聲冷哼從身側(cè)響起胧瓜,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎郑什,沒想到半個(gè)月后府喳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蘑拯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年钝满,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了兜粘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弯蚜,死狀恐怖孔轴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情碎捺,我是刑警寧澤路鹰,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站收厨,受9級(jí)特大地震影響晋柱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诵叁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一雁竞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拧额,春花似錦浓领、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捎拯,卻和暖如春泪幌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背署照。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來泰國打工祸泪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人建芙。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓凶朗,卻偏偏與公主長得像堰酿,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容