Kafka設(shè)計(jì)解析(六)- Kafka高性能架構(gòu)之道

Kafka設(shè)計(jì)解析(六)- Kafka高性能架構(gòu)之道

原創(chuàng)文章,轉(zhuǎn)載請(qǐng)務(wù)必將下面這段話置于文章開頭處。

本文轉(zhuǎn)發(fā)自技術(shù)世界原文鏈接 http://www.jasongj.com/kafka/high_throughput/

簡(jiǎn)介

? 本文從宏觀架構(gòu)層面和微觀實(shí)現(xiàn)層面分析了Kafka如何實(shí)現(xiàn)高性能。包含Kafka如何利用Partition實(shí)現(xiàn)并行處理和提供水平擴(kuò)展能力,如何通過(guò)ISR實(shí)現(xiàn)可用性和數(shù)據(jù)一致性的動(dòng)態(tài)平衡,如何使用NIO和Linux的sendfile實(shí)現(xiàn)零拷貝以及如何通過(guò)順序讀寫和數(shù)據(jù)壓縮實(shí)現(xiàn)磁盤的高效利用。

宏觀架構(gòu)層面

利用Partition實(shí)現(xiàn)并行處理

Partition提供并行處理的能力

Kafka是一個(gè)Pub-Sub的消息系統(tǒng),無(wú)論是發(fā)布還是訂閱,都須指定Topic。如《Kafka設(shè)計(jì)解析(一)- Kafka背景及架構(gòu)介紹》一文所述,Topic只是一個(gè)邏輯的概念。每個(gè)Topic都包含一個(gè)或多個(gè)Partition,不同Partition可位于不同節(jié)點(diǎn)。同時(shí)Partition在物理上對(duì)應(yīng)一個(gè)本地文件夾,每個(gè)Partition包含一個(gè)或多個(gè)Segment,每個(gè)Segment包含一個(gè)數(shù)據(jù)文件和一個(gè)與之對(duì)應(yīng)的索引文件。在邏輯上,可以把一個(gè)Partition當(dāng)作一個(gè)非常長(zhǎng)的數(shù)組,可通過(guò)這個(gè)“數(shù)組”的索引(offset)去訪問(wèn)其數(shù)據(jù)。

Topic是邏輯概念
Partition在物理上對(duì)應(yīng)一個(gè)本地文件夾
Partition = Segment + Segment + Segment + .....
Segment = 數(shù)據(jù)文件 + 索引文件(offset)

一方面,由于不同Partition可位于不同機(jī)器,因此可以充分利用集群優(yōu)勢(shì),實(shí)現(xiàn)機(jī)器間的并行處理。另一方面,由于Partition在物理上對(duì)應(yīng)一個(gè)文件夾,即使多個(gè)Partition位于同一個(gè)節(jié)點(diǎn),也可通過(guò)配置讓同一節(jié)點(diǎn)上的不同Partition置于不同的disk drive上,從而實(shí)現(xiàn)磁盤間的并行處理,充分發(fā)揮多磁盤的優(yōu)勢(shì)。

不同的Partition可以位于不同的機(jī)器-------實(shí)現(xiàn)機(jī)器間的并行處理
Partition在物理上對(duì)應(yīng)一個(gè)文件夾-------實(shí)現(xiàn)磁盤間的并行處理

利用多磁盤的具體方法是,將不同磁盤mount到不同目錄,然后在server.properties中,將log.dirs設(shè)置為多目錄(用逗號(hào)分隔)。Kafka會(huì)自動(dòng)將所有Partition盡可能均勻分配到不同目錄也即不同目錄(也即不同disk)上。

Kafka自動(dòng)將所有Partition盡可能均勻分配到不同的disk上

注:雖然物理上最小單位是Segment,但Kafka并不提供同一Partition內(nèi)不同Segment間的并行處理。因?yàn)閷?duì)于寫而言,每次只會(huì)寫Partition內(nèi)的一個(gè)Segment,而對(duì)于讀而言,也只會(huì)順序讀取同一Partition內(nèi)的不同Segment。

Partition是最小并發(fā)粒度

如同《Kafka設(shè)計(jì)解析(四)- Kafka Consumer設(shè)計(jì)解析》一文所述,多Consumer消費(fèi)同一個(gè)Topic時(shí),同一條消息只會(huì)被同一Consumer Group內(nèi)的一個(gè)Consumer所消費(fèi)。而數(shù)據(jù)并非按消息為單位分配,而是以Partition為單位分配,也即同一個(gè)Partition的數(shù)據(jù)只會(huì)被一個(gè)Consumer所消費(fèi)(在不考慮Rebalance的前提下)。

同一個(gè)Partition只會(huì)被一個(gè)Consumer消費(fèi)
Partition個(gè)數(shù)決定了可能的最大并行度

如果Consumer的個(gè)數(shù)多于Partition的個(gè)數(shù),那么會(huì)有部分Consumer無(wú)法消費(fèi)該Topic的任何數(shù)據(jù),也即當(dāng)Consumer個(gè)數(shù)超過(guò)Partition后,增加Consumer并不能增加并行度。

簡(jiǎn)而言之,Partition個(gè)數(shù)決定了可能的最大并行度。如下圖所示,由于Topic 2只包含3個(gè)Partition,故group2中的Consumer 3、Consumer 4、Consumer 5 可分別消費(fèi)1個(gè)Partition的數(shù)據(jù),而Consumer 6消費(fèi)不到Topic 2的任何數(shù)據(jù)。


Kafka Consumer

以Spark消費(fèi)Kafka數(shù)據(jù)為例,如果所消費(fèi)的Topic的Partition數(shù)為N,則有效的Spark最大并行度也為N。即使將Spark的Executor數(shù)設(shè)置為N+M,最多也只有N個(gè)Executor可同時(shí)處理該Topic的數(shù)據(jù)。

ISR實(shí)現(xiàn)可用性與數(shù)據(jù)一致性的動(dòng)態(tài)平衡

CAP理論

CAP理論是指,分布式系統(tǒng)中,一致性、可用性和分區(qū)容忍性最多只能同時(shí)滿足兩個(gè)。

一致性:consistency

  • 通過(guò)某個(gè)節(jié)點(diǎn)的寫操作結(jié)果對(duì)后面通過(guò)其它節(jié)點(diǎn)的讀操作可見(jiàn)
  • 如果更新數(shù)據(jù)后,并發(fā)訪問(wèn)情況下后續(xù)讀操作可立即感知該更新,稱為強(qiáng)一致性
  • 如果允許之后部分或者全部感知不到該更新,稱為弱一致性
  • 若在之后的一段時(shí)間(通常該時(shí)間不固定)后,一定可以感知到該更新,稱為最終一致性

可用性:availability

  • 任何一個(gè)沒(méi)有發(fā)生故障的節(jié)點(diǎn)必須在有限的時(shí)間內(nèi)返回合理的結(jié)果

分區(qū)容忍性:Patience

  • 部分節(jié)點(diǎn)宕機(jī)或者無(wú)法與其它節(jié)點(diǎn)通信時(shí),各分區(qū)間還可保持分布式系統(tǒng)的功能

常用數(shù)據(jù)復(fù)制及一致性方案

Master-Slave

  • RDBMS的讀寫分離即為典型的Master-Slave方案
  • 同步復(fù)制可保證強(qiáng)一致性但會(huì)影響可用性
  • 異步復(fù)制可提供高可用性但會(huì)降低一致性

WNR

  • 主要用于去中心化的分布式系統(tǒng)中。DynamoDB與Cassandra即采用此方案或其變種
  • N代表總副本數(shù),W代表每次寫操作要保證的最少寫成功的副本數(shù),R代表每次讀至少要讀取的副本數(shù)
  • 當(dāng)W+R>N時(shí),可保證每次讀取的數(shù)據(jù)至少有一個(gè)副本擁有最新的數(shù)據(jù)
  • 多個(gè)寫操作的順序難以保證,可能導(dǎo)致多副本間的寫操作順序不一致。Dynamo通過(guò)向量時(shí)鐘保證最終一致性

Paxos及其變種

  • Google的Chubby,Zookeeper的原子廣播協(xié)議(Zab),RAFT等

基于ISR的數(shù)據(jù)復(fù)制方案

如《 Kafka High Availability(上)》一文所述,Kafka的數(shù)據(jù)復(fù)制是以Partition為單位的。而多個(gè)備份間的數(shù)據(jù)復(fù)制,通過(guò)Follower向Leader拉取數(shù)據(jù)完成。從一這點(diǎn)來(lái)講,Kafka的數(shù)據(jù)復(fù)制方案接近于上文所講的Master-Slave方案。不同的是,Kafka既不是完全的同步復(fù)制,也不是完全的異步復(fù)制,而是基于ISR的動(dòng)態(tài)復(fù)制方案。

給予ISR的動(dòng)態(tài)復(fù)制方案(接近于Master-Slave方案) 

ISR,也即In-sync Replica。每個(gè)Partition的Leader都會(huì)維護(hù)這樣一個(gè)列表,該列表中,包含了所有與之同步的Replica(包含Leader自己)。每次數(shù)據(jù)寫入時(shí),只有ISR中的所有Replica都復(fù)制完,Leader才會(huì)將其置為Commit,它才能被Consumer所消費(fèi)。

ISR由Leader維護(hù)

這種方案,與同步復(fù)制非常接近。但不同的是,這個(gè)ISR是由Leader動(dòng)態(tài)維護(hù)的。如果Follower不能緊“跟上”Leader,它將被Leader從ISR中移除,待它又重新“跟上”Leader后,會(huì)被Leader再次加加ISR中。每次改變ISR后,Leader都會(huì)將最新的ISR持久化到Zookeeper中。

至于如何判斷某個(gè)Follower是否“跟上”Leader,不同版本的Kafka的策略稍微有些區(qū)別。

  • 對(duì)于0.8.*版本,如果Follower在replica.lag.time.max.ms時(shí)間內(nèi)未向Leader發(fā)送Fetch請(qǐng)求(也即數(shù)據(jù)復(fù)制請(qǐng)求),則Leader會(huì)將其從ISR中移除。如果某Follower持續(xù)向Leader發(fā)送Fetch請(qǐng)求,但是它與Leader的數(shù)據(jù)差距在replica.lag.max.messages以上,也會(huì)被Leader從ISR中移除。
  • 從0.9.0.0版本開始,replica.lag.max.messages被移除,故Leader不再考慮Follower落后的消息條數(shù)。另外,Leader不僅會(huì)判斷Follower是否在replica.lag.time.max.ms時(shí)間內(nèi)向其發(fā)送Fetch請(qǐng)求,同時(shí)還會(huì)考慮Follower是否在該時(shí)間內(nèi)與之保持同步。
  • 0.10.* 版本的策略與0.9.*版一致

對(duì)于0.8.版本的replica.lag.max.messages參數(shù),很多讀者曾留言提問(wèn),既然只有ISR中的所有Replica復(fù)制完后的消息才被認(rèn)為Commit,那為何會(huì)出現(xiàn)Follower與Leader差距過(guò)大的情況。原因在于,Leader并不需要等到前一條消息被Commit才接收后一條消息。事實(shí)上,Leader可以按順序接收大量消息,最新的一條消息的Offset被記為High Watermark。而只有被ISR中所有Follower都復(fù)制過(guò)去的消息才會(huì)被Commit,Consumer只能消費(fèi)被Commit的消息。由于Follower的復(fù)制是嚴(yán)格按順序的,所以被Commit的消息之前的消息肯定也已經(jīng)被Commit過(guò)。換句話說(shuō),High Watermark標(biāo)記的是Leader所保存的最新消息的offset,而Commit Offset標(biāo)記的是最新的可被消費(fèi)的(已同步到ISR中的Follower)消息。而Leader對(duì)數(shù)據(jù)的接收與Follower對(duì)數(shù)據(jù)的復(fù)制是異步進(jìn)行的,因此會(huì)出現(xiàn)Commit Offset與High Watermark存在一定差距的情況。0.8.版本中replica.lag.max.messages限定了Leader允許的該差距的最大值。

Leader接受數(shù)據(jù)和復(fù)制副本數(shù)據(jù)是異步進(jìn)行的,Leader按順序接受完消息,將最新的一條消息的offset(偏移)記為High Watermark,而Follower復(fù)制結(jié)束后,commit后標(biāo)記commit offset,所以當(dāng)這兩者的差距拉大,就會(huì)出現(xiàn)Follower未能跟上Leader。

Kafka基于ISR的數(shù)據(jù)復(fù)制方案原理如下圖所示。


Kafka Replication

如上圖所示,在第一步中,Leader A總共收到3條消息,故其high watermark為3,但由于ISR中的Follower只同步了第1條消息(m1),故只有m1被Commit,也即只有m1可被Consumer消費(fèi)。此時(shí)Follower B與Leader A的差距是1,而Follower C與Leader A的差距是2,均未超過(guò)默認(rèn)的replica.lag.max.messages,故得以保留在ISR中。在第二步中,由于舊的Leader A宕機(jī),新的Leader B在replica.lag.time.max.ms時(shí)間內(nèi)未收到來(lái)自A的Fetch請(qǐng)求,故將A從ISR中移除,此時(shí)ISR={B,C}。同時(shí),由于此時(shí)新的Leader B中只有2條消息,并未包含m3(m3從未被任何Leader所Commit),所以m3無(wú)法被Consumer消費(fèi)。第四步中,F(xiàn)ollower A恢復(fù)正常,它先將宕機(jī)前未Commit的所有消息全部刪除,然后從最后Commit過(guò)的消息的下一條消息開始追趕新的Leader B,直到它“趕上”新的Leader,才被重新加入新的ISR中。

藍(lán)色線 = commit offset

使用ISR方案的原因

  • 由于Leader可移除不能及時(shí)與之同步的Follower,故與同步復(fù)制相比可避免最慢的Follower拖慢整體速度,也即ISR提高了系統(tǒng)可用性。
  • ISR中的所有Follower都包含了所有Commit過(guò)的消息,而只有Commit過(guò)的消息才會(huì)被Consumer消費(fèi),故從Consumer的角度而言,ISR中的所有Replica都始終處于同步狀態(tài),從而與異步復(fù)制方案相比提高了數(shù)據(jù)一致性。
  • ISR可動(dòng)態(tài)調(diào)整,極限情況下,可以只包含Leader,極大提高了可容忍的宕機(jī)的Follower的數(shù)量。與Majority Quorum方案相比,容忍相同個(gè)數(shù)的節(jié)點(diǎn)失敗,所要求的總節(jié)點(diǎn)數(shù)少了近一半。
避免最慢的Follower拖慢系統(tǒng)的速度
ISR中的所有Replica都處于同步狀態(tài)
極限狀態(tài)下,ISRz中可以只包含Leader  

ISR相關(guān)配置說(shuō)明

  • Broker的min.insync.replicas參數(shù)指定了Broker所要求的ISR最小長(zhǎng)度,默認(rèn)值為1。也即極限情況下ISR可以只包含Leader。但此時(shí)如果Leader宕機(jī),則該P(yáng)artition不可用,可用性得不到保證。
  • 只有被ISR中所有Replica同步的消息才被Commit,但Producer發(fā)布數(shù)據(jù)時(shí),Leader并不需要ISR中的所有Replica同步該數(shù)據(jù)才確認(rèn)收到數(shù)據(jù)。Producer可以通過(guò)acks參數(shù)指定最少需要多少個(gè)Replica確認(rèn)收到該消息才視為該消息發(fā)送成功。acks的默認(rèn)值是1,即Leader收到該消息后立即告訴Producer收到該消息,此時(shí)如果在ISR中的消息復(fù)制完該消息前Leader宕機(jī),那該條消息會(huì)丟失。而如果將該值設(shè)置為0,則Producer發(fā)送完數(shù)據(jù)后,立即認(rèn)為該數(shù)據(jù)發(fā)送成功,不作任何等待,而實(shí)際上該數(shù)據(jù)可能發(fā)送失敗,并且Producer的Retry機(jī)制將不生效。更推薦的做法是,將acks設(shè)置為all或者-1,此時(shí)只有ISR中的所有Replica都收到該數(shù)據(jù)(也即該消息被Commit),Leader才會(huì)告訴Producer該消息發(fā)送成功,從而保證不會(huì)有未知的數(shù)據(jù)丟失。
min.insync.replicas: 1      指定Broker所要求的ISR最小的長(zhǎng)度
acks                        指定最少需要多少個(gè)Replica確認(rèn)收到該消息才視為成功

具體實(shí)現(xiàn)層面

高效使用磁盤

順序?qū)懘疟P

根據(jù)《一些場(chǎng)景下順序?qū)懘疟P快于隨機(jī)寫內(nèi)存》所述,將寫磁盤的過(guò)程變?yōu)轫樞驅(qū)懀蓸O大提高對(duì)磁盤的利用率。

Kafka的整個(gè)設(shè)計(jì)中,Partition相當(dāng)于一個(gè)非常長(zhǎng)的數(shù)組,而Broker接收到的所有消息順序?qū)懭脒@個(gè)大數(shù)組中。同時(shí)Consumer通過(guò)Offset順序消費(fèi)這些數(shù)據(jù),并且不刪除已經(jīng)消費(fèi)的數(shù)據(jù),從而避免了隨機(jī)寫磁盤的過(guò)程。

由于磁盤有限,不可能保存所有數(shù)據(jù),實(shí)際上作為消息系統(tǒng)Kafka也沒(méi)必要保存所有數(shù)據(jù),需要?jiǎng)h除舊的數(shù)據(jù)。而這個(gè)刪除過(guò)程,并非通過(guò)使用“讀-寫”模式去修改文件,而是將Partition分為多個(gè)Segment,每個(gè)Segment對(duì)應(yīng)一個(gè)物理文件,通過(guò)刪除整個(gè)文件的方式去刪除Partition內(nèi)的數(shù)據(jù)。這種方式清除舊數(shù)據(jù)的方式,也避免了對(duì)文件的隨機(jī)寫操作。

Kafka順序存寫數(shù)據(jù),故刪除時(shí)刪除對(duì)應(yīng)的Segment(物理文件,disk),避免對(duì)文件的隨機(jī)寫操作。

通過(guò)如下代碼可知,Kafka刪除Segment的方式,是直接刪除Segment對(duì)應(yīng)的整個(gè)log文件和整個(gè)index文件而非刪除文件中的部分內(nèi)容。

/**
 * Delete this log segment from the filesystem.
 *
 * @throws KafkaStorageException if the delete fails.
 */
def delete() {
  val deletedLog = log.delete()
  val deletedIndex = index.delete()
  val deletedTimeIndex = timeIndex.delete()
  if(!deletedLog && log.file.exists)
    throw new KafkaStorageException("Delete of log " + log.file.getName + " failed.")
  if(!deletedIndex && index.file.exists)
    throw new KafkaStorageException("Delete of index " + index.file.getName + " failed.")
  if(!deletedTimeIndex && timeIndex.file.exists)
    throw new KafkaStorageException("Delete of time index " + timeIndex.file.getName + " failed.")
}

充分利用Page Cache(分頁(yè)緩存)

使用Page Cache的好處如下

  • I/O Scheduler會(huì)將連續(xù)的小塊寫組裝成大塊的物理寫從而提高性能
  • I/O Scheduler會(huì)嘗試將一些寫操作重新按順序排好,從而減少磁盤頭的移動(dòng)時(shí)間
  • 充分利用所有空閑內(nèi)存(非JVM內(nèi)存)。如果使用應(yīng)用層Cache(即JVM堆內(nèi)存),會(huì)增加GC負(fù)擔(dān)
  • 讀操作可直接在Page Cache內(nèi)進(jìn)行。如果消費(fèi)和生產(chǎn)速度相當(dāng),甚至不需要通過(guò)物理磁盤(直接通過(guò)Page Cache)交換數(shù)據(jù)
  • 如果進(jìn)程重啟,JVM內(nèi)的Cache會(huì)失效,但Page Cache仍然可用
小塊寫--->大塊寫
寫操作按順序排好
利用所有空閑內(nèi)存
讀操作可直接在Page Cache內(nèi)進(jìn)行(不是JVM內(nèi)存)

Broker收到數(shù)據(jù)后,寫磁盤時(shí)只是將數(shù)據(jù)寫入Page Cache,并不保證數(shù)據(jù)一定完全寫入磁盤。從這一點(diǎn)看,可能會(huì)造成機(jī)器宕機(jī)時(shí),Page Cache內(nèi)的數(shù)據(jù)未寫入磁盤從而造成數(shù)據(jù)丟失。但是這種丟失只發(fā)生在機(jī)器斷電等造成操作系統(tǒng)不工作的場(chǎng)景,而這種場(chǎng)景完全可以由Kafka層面的Replication機(jī)制去解決。如果為了保證這種情況下數(shù)據(jù)不丟失而強(qiáng)制將Page Cache中的數(shù)據(jù)Flush到磁盤,反而會(huì)降低性能。也正因如此,Kafka雖然提供了flush.messagesflush.ms兩個(gè)參數(shù)將Page Cache中的數(shù)據(jù)強(qiáng)制Flush到磁盤,但是Kafka并不建議使用。

斷電會(huì)導(dǎo)致Page Cache數(shù)據(jù)丟失
可設(shè)置采用Flush到磁盤,但影響性能

如果數(shù)據(jù)消費(fèi)速度與生產(chǎn)速度相當(dāng),甚至不需要通過(guò)物理磁盤交換數(shù)據(jù),而是直接通過(guò)Page Cache交換數(shù)據(jù)。同時(shí),F(xiàn)ollower從Leader Fetch數(shù)據(jù)時(shí),也可通過(guò)Page Cache完成。下圖為某Partition的Leader節(jié)點(diǎn)的網(wǎng)絡(luò)/磁盤讀寫信息。

Kafka I/O page cache

從上圖可以看到,該Broker每秒通過(guò)網(wǎng)絡(luò)從Producer接收約35MB數(shù)據(jù),雖然有Follower從該Broker Fetch數(shù)據(jù),但是該Broker基本無(wú)讀磁盤。這是因?yàn)樵揃roker直接從Page Cache中將數(shù)據(jù)取出返回給了Follower。

支持多Disk Drive

Broker的log.dirs配置項(xiàng),允許配置多個(gè)文件夾。如果機(jī)器上有多個(gè)Disk Drive,可將不同的Disk掛載到不同的目錄,然后將這些目錄都配置到log.dirs里。Kafka會(huì)盡可能將不同的Partition分配到不同的目錄,也即不同的Disk上,從而充分利用了多Disk的優(yōu)勢(shì)

零拷貝

Kafka中存在大量的網(wǎng)絡(luò)數(shù)據(jù)持久化到磁盤(Producer到Broker)和磁盤文件通過(guò)網(wǎng)絡(luò)發(fā)送(Broker到Consumer)的過(guò)程。這一過(guò)程的性能直接影響Kafka的整體吞吐量。對(duì)比傳統(tǒng)模式的拷貝來(lái)看看kafka如何實(shí)現(xiàn)零拷貝

傳統(tǒng)模式下的四次拷貝與四次上下文切換

以將磁盤文件通過(guò)網(wǎng)絡(luò)發(fā)送為例。傳統(tǒng)模式下,一般使用如下偽代碼所示的方法先將文件數(shù)據(jù)讀入內(nèi)存,然后通過(guò)Socket將內(nèi)存中的數(shù)據(jù)發(fā)送出去。

buffer = File.read
Socket.send(buffer)

這一過(guò)程實(shí)際上發(fā)生了四次數(shù)據(jù)拷貝。首先通過(guò)系統(tǒng)調(diào)用將文件數(shù)據(jù)讀入到內(nèi)核態(tài)Buffer(DMA拷貝),然后應(yīng)用程序將內(nèi)存態(tài)Buffer數(shù)據(jù)讀入到用戶態(tài)Buffer(CPU拷貝),接著用戶程序通過(guò)Socket發(fā)送數(shù)據(jù)時(shí)將用戶態(tài)Buffer數(shù)據(jù)拷貝到內(nèi)核態(tài)Buffer(CPU拷貝),最后通過(guò)DMA拷貝將數(shù)據(jù)拷貝到NIC Buffer(網(wǎng)卡緩沖)。同時(shí),還伴隨著四次上下文切換,如下圖所示。

BIO 四次拷貝 四次上下文切換

sendfile和transferTo實(shí)現(xiàn)零拷貝

Linux 2.4+內(nèi)核通過(guò)sendfile系統(tǒng)調(diào)用,提供了零拷貝。數(shù)據(jù)通過(guò)DMA拷貝到內(nèi)核態(tài)Buffer后,直接通過(guò)DMA(Direct Memory Access,直接內(nèi)存存取)拷貝到NIC Buffer,無(wú)需CPU拷貝。這也是零拷貝這一說(shuō)法的來(lái)源。除了減少數(shù)據(jù)拷貝外,因?yàn)檎麄€(gè)讀文件-網(wǎng)絡(luò)發(fā)送由一個(gè)sendfile調(diào)用完成,整個(gè)過(guò)程只有兩次上下文切換,因此大大提高了性能。零拷貝過(guò)程如下圖所示。

BIO 零拷貝 兩次上下文切換

從具體實(shí)現(xiàn)來(lái)看,Kafka的數(shù)據(jù)傳輸通過(guò)TransportLayer來(lái)完成,其子類PlaintextTransportLayer通過(guò)Java NIO的FileChannel的transferTotransferFrom方法實(shí)現(xiàn)零拷貝,如下所示。

@Override
public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
    return fileChannel.transferTo(position, count, socketChannel);
}

注: transferTotransferFrom并不保證一定能使用零拷貝。實(shí)際上是否能使用零拷貝與操作系統(tǒng)相關(guān),如果操作系統(tǒng)提供sendfile這樣的零拷貝系統(tǒng)調(diào)用,則這兩個(gè)方法會(huì)通過(guò)這樣的系統(tǒng)調(diào)用充分利用零拷貝的優(yōu)勢(shì),否則并不能通過(guò)這兩個(gè)方法本身實(shí)現(xiàn)零拷貝。

減少網(wǎng)絡(luò)開銷

批處理

批處理是一種常用的用于提高I/O性能的方式。對(duì)Kafka而言,批處理既減少了網(wǎng)絡(luò)傳輸?shù)腛verhead(天花板),又提高了寫磁盤的效率。

Kafka 0.8.1及以前的Producer區(qū)分同步Producer和異步Producer。同步Producer的send方法主要分兩種形式。一種是接受一個(gè)KeyedMessage作為參數(shù),一次發(fā)送一條消息。另一種是接受一批KeyedMessage作為參數(shù),一次性發(fā)送多條消息。而對(duì)于異步發(fā)送而言,無(wú)論是使用哪個(gè)send方法,實(shí)現(xiàn)上都不會(huì)立即將消息發(fā)送給Broker,而是先存到內(nèi)部的隊(duì)列中,直到消息條數(shù)達(dá)到閾值或者達(dá)到指定的Timeout才真正的將消息發(fā)送出去,從而實(shí)現(xiàn)了消息的批量發(fā)送。

Kafka 0.8.2開始支持新的Producer API,將同步Producer和異步Producer結(jié)合。雖然從send接口來(lái)看,一次只能發(fā)送一個(gè)ProducerRecord,而不能像之前版本的send方法一樣接受消息列表,但是send方法并非立即將消息發(fā)送出去,而是通過(guò)batch.sizelinger.ms控制實(shí)際發(fā)送頻率,從而實(shí)現(xiàn)批量發(fā)送。

由于每次網(wǎng)絡(luò)傳輸,除了傳輸消息本身以外,還要傳輸非常多的網(wǎng)絡(luò)協(xié)議本身的一些內(nèi)容(稱為Overhead),所以將多條消息合并到一起傳輸,可有效減少網(wǎng)絡(luò)傳輸?shù)腛verhead,進(jìn)而提高了傳輸效率。

從零拷貝章節(jié)的圖中可以看到,雖然Broker持續(xù)從網(wǎng)絡(luò)接收數(shù)據(jù),但是寫磁盤并非每秒都在發(fā)生,而是間隔一段時(shí)間寫一次磁盤,并且每次寫磁盤的數(shù)據(jù)量都非常大(最高達(dá)到718MB/S)。

數(shù)據(jù)壓縮降低網(wǎng)絡(luò)負(fù)載

Kafka從0.7開始,即支持將數(shù)據(jù)壓縮后再傳輸給Broker。除了可以將每條消息單獨(dú)壓縮然后傳輸外,Kafka還支持在批量發(fā)送時(shí),將整個(gè)Batch的消息一起壓縮后傳輸。數(shù)據(jù)壓縮的一個(gè)基本原理是,重復(fù)數(shù)據(jù)越多壓縮效果越好。因此將整個(gè)Batch的數(shù)據(jù)一起壓縮能更大幅度減小數(shù)據(jù)量,從而更大程度提高網(wǎng)絡(luò)傳輸效率。

Broker接收消息后,并不直接解壓縮,而是直接將消息以壓縮后的形式持久化到磁盤。Consumer Fetch到數(shù)據(jù)后再解壓縮。因此Kafka的壓縮不僅減少了Producer到Broker的網(wǎng)絡(luò)傳輸負(fù)載,同時(shí)也降低了Broker磁盤操作的負(fù)載,也降低了Consumer與Broker間的網(wǎng)絡(luò)傳輸量,從而極大得提高了傳輸效率,提高了吞吐量。

1 單條/Batch壓縮---傳輸----持久化到磁盤---Consumer Fetch到數(shù)據(jù)后再解壓縮
2 降低了Broker磁盤操作的負(fù)擔(dān),降低了Consumer與Broker間的網(wǎng)絡(luò)傳輸量 提高了傳輸效率 提高了   吞吐率

高效的序列化方式

Kafka消息的Key和Payload(或者說(shuō)Value)的類型可自定義,只需同時(shí)提供相應(yīng)的序列化器和反序列化器即可。因此用戶可以通過(guò)使用快速且緊湊的序列化-反序列化方式(如Avro,Protocal Buffer)來(lái)減少實(shí)際網(wǎng)絡(luò)傳輸和磁盤存儲(chǔ)的數(shù)據(jù)規(guī)模,從而提高吞吐率。這里要注意,如果使用的序列化方法太慢,即使壓縮比非常高,最終的效率也不一定高。

Kafka系列文章

Kafka設(shè)計(jì)解析(一)- Kafka簡(jiǎn)介及架構(gòu)介紹
Kafka設(shè)計(jì)解析(二)- Kafka High Availability (上)
Kafka設(shè)計(jì)解析(三)- Kafka High Availability (下)
Kafka設(shè)計(jì)解析(四)- Kafka Consumer設(shè)計(jì)解析
Kafka設(shè)計(jì)解析(五)- Kafka性能測(cè)試方法及Benchmark報(bào)告
Kafka設(shè)計(jì)解析(六)- Kafka高性能架構(gòu)之道
Kafka設(shè)計(jì)解析(七)- Kafka Stream

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評(píng)論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,727評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,193評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,411評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,777評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,978評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,216評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,657評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,960評(píng)論 2 373

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