日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

面试官问:上亿数据量下,Kafka是如何优化JVM GC问题的?

發(fā)布時(shí)間:2025/3/21 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 面试官问:上亿数据量下,Kafka是如何优化JVM GC问题的? 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

大家都知道Kafka是一個(gè)高吞吐的消息隊(duì)列,是大數(shù)據(jù)場(chǎng)景首選的消息隊(duì)列,這種場(chǎng)景就意味著發(fā)送單位時(shí)間消息的量會(huì)特別的大,那既然如此巨大的數(shù)據(jù)量,kafka是如何支撐起如此龐大的數(shù)據(jù)量的分發(fā)的呢?

今天我們從kafka架構(gòu)以如何優(yōu)化GC兩個(gè)方面講解

kafka架構(gòu)

既然要說kafka是如何通過內(nèi)存緩沖池設(shè)計(jì)來優(yōu)化JVM的GC問題,那么,如果不清楚kafka 的架構(gòu)設(shè)計(jì),又怎么更好的調(diào)優(yōu)呢?起碼的我們要知道基礎(chǔ)的才能往更好的出發(fā)呀,對(duì)把

先來看技術(shù)名詞

  • Topic:用于劃分Message的邏輯概念,一個(gè)Topic可以分布在多個(gè)Broker上。

  • Partition:是Kafka中橫向擴(kuò)展和一切并行化的基礎(chǔ),每個(gè)Topic都至少被切分為1個(gè)Partition。

  • Offset:消息在Partition中的編號(hào),編號(hào)順序不跨Partition。

  • Consumer:用于從Broker中取出/消費(fèi)Message。

  • Producer:用于往Broker中發(fā)送/生產(chǎn)Message。

  • Replication:Kafka支持以Partition為單位對(duì)Message進(jìn)行冗余備份,每個(gè)Partition都可以配置至少1個(gè)Replication(當(dāng)僅1個(gè)Replication時(shí)即僅該P(yáng)artition本身)。

  • Leader:每個(gè)Replication集合中的Partition都會(huì)選出一個(gè)唯一的Leader,所有的讀寫請(qǐng)求都由Leader處理。其他Replicas從Leader處把數(shù)據(jù)更新同步到本地,過程類似大家熟悉的MySQL中的Binlog同步。

  • Broker:Kafka中使用Broker來接受Producer和Consumer的請(qǐng)求,并把Message持久化到本地磁盤。每個(gè)Cluster當(dāng)中會(huì)選舉出一個(gè)Broker來擔(dān)任Controller,負(fù)責(zé)處理Partition的Leader選舉,協(xié)調(diào)Partition遷移等工作。

  • ISR(In-Sync Replica):是Replicas的一個(gè)子集,表示目前Alive且與Leader能夠“Catch-up”的Replicas集合。由于讀寫都是首先落到Leader上,所以一般來說通過同步機(jī)制從Leader上拉取數(shù)據(jù)的Replica都會(huì)和Leader有一些延遲(包括了延遲時(shí)間和延遲條數(shù)兩個(gè)維度),任意一個(gè)超過閾值都會(huì)把該Replica踢出ISR。每個(gè)Partition都有它自己獨(dú)立的ISR。

  • 以上幾乎是我們?cè)谑褂肒afka的過程中可能遇到的所有名詞,同時(shí)也無一不是最核心的概念或組件,感覺到從設(shè)計(jì)本身來說,Kafka還是足夠簡(jiǎn)潔的。這次本文圍繞Kafka優(yōu)異的吞吐性能,逐個(gè)介紹一下其設(shè)計(jì)與實(shí)現(xiàn)當(dāng)中所使用的各項(xiàng)“黑科技”。

    Broker

    不同于Redis和MemcacheQ等內(nèi)存消息隊(duì)列,Kafka的設(shè)計(jì)是把所有的Message都要寫入速度低容量大的硬盤,以此來換取更強(qiáng)的存儲(chǔ)能力。實(shí)際上,Kafka使用硬盤并沒有帶來過多的性能損失,“規(guī)規(guī)矩矩”的抄了一條“近道”。

    首先,說“規(guī)規(guī)矩矩”是因?yàn)镵afka在磁盤上只做Sequence I/O,由于消息系統(tǒng)讀寫的特殊性,這并不存在什么問題。關(guān)于磁盤I/O的性能,引用一組Kafka官方給出的測(cè)試數(shù)據(jù)(Raid-5,7200rpm):

    Sequence I/O: 600MB/s

    Random I/O: 100KB/s

    所以通過只做Sequence I/O的限制,規(guī)避了磁盤訪問速度低下對(duì)性能可能造成的影響。

    接下來我們?cè)倭囊涣腒afka是如何“抄近道的”。

    首先,Kafka重度依賴底層操作系統(tǒng)提供的PageCache功能。當(dāng)上層有寫操作時(shí),操作系統(tǒng)只是將數(shù)據(jù)寫入PageCache,同時(shí)標(biāo)記Page屬性為Dirty。當(dāng)讀操作發(fā)生時(shí),先從PageCache中查找,如果發(fā)生缺頁才進(jìn)行磁盤調(diào)度,最終返回需要的數(shù)據(jù)。實(shí)際上PageCache是把盡可能多的空閑內(nèi)存都當(dāng)做了磁盤緩存來使用。同時(shí)如果有其他進(jìn)程申請(qǐng)內(nèi)存,回收PageCache的代價(jià)又很小,所以現(xiàn)代的OS都支持PageCache。

    使用PageCache功能同時(shí)可以避免在JVM內(nèi)部緩存數(shù)據(jù),JVM為我們提供了強(qiáng)大的GC能力,同時(shí)也引入了一些問題不適用與Kafka的設(shè)計(jì)。

    ? 如果在Heap內(nèi)管理緩存,JVM的GC線程會(huì)頻繁掃描Heap空間,帶來不必要的開銷。如果Heap過大,執(zhí)行一次Full GC對(duì)系統(tǒng)的可用性來說將是極大的挑戰(zhàn)。

    ? 所有在在JVM內(nèi)的對(duì)象都不免帶有一個(gè)Object Overhead(千萬不可小視),內(nèi)存的有效空間利用率會(huì)因此降低。

    ? 所有的In-Process Cache在OS中都有一份同樣的PageCache。所以通過將緩存只放在PageCache,可以至少讓可用緩存空間翻倍。

    ? 如果Kafka重啟,所有的In-Process Cache都會(huì)失效,而OS管理的PageCache依然可以繼續(xù)使用。

    PageCache還只是第一步,Kafka為了進(jìn)一步的優(yōu)化性能還采用了Sendfile技術(shù)。在解釋Sendfile之前,首先介紹一下傳統(tǒng)的網(wǎng)絡(luò)I/O操作流程,大體上分為以下4步。

  • OS 從硬盤把數(shù)據(jù)讀到內(nèi)核區(qū)的PageCache。

  • 用戶進(jìn)程把數(shù)據(jù)從內(nèi)核區(qū)Copy到用戶區(qū)。

  • 然后用戶進(jìn)程再把數(shù)據(jù)寫入到Socket,數(shù)據(jù)流入內(nèi)核區(qū)的Socket Buffer上。

  • OS 再把數(shù)據(jù)從Buffer中Copy到網(wǎng)卡的Buffer上,這樣完成一次發(fā)送。

  • 整個(gè)過程共經(jīng)歷兩次Context Switch,四次System Call。同一份數(shù)據(jù)在內(nèi)核Buffer與用戶Buffer之間重復(fù)拷貝,效率低下。其中2、3兩步?jīng)]有必要,完全可以直接在內(nèi)核區(qū)完成數(shù)據(jù)拷貝。這也正是Sendfile所解決的問題,經(jīng)過Sendfile優(yōu)化后,整個(gè)I/O過程就變成了下面這個(gè)樣子。

    通過以上的介紹不難看出,Kafka的設(shè)計(jì)初衷是盡一切努力在內(nèi)存中完成數(shù)據(jù)交換,無論是對(duì)外作為一整個(gè)消息系統(tǒng),或是內(nèi)部同底層操作系統(tǒng)的交互。如果Producer和Consumer之間生產(chǎn)和消費(fèi)進(jìn)度上配合得當(dāng),完全可以實(shí)現(xiàn)數(shù)據(jù)交換零I/O。這也就是我為什么說Kafka使用“硬盤”并沒有帶來過多性能損失的原因。下面是我在生產(chǎn)環(huán)境中采到的一些指標(biāo)。

    (20 Brokers, 75 Partitions per Broker, 110k msg/s)

    此時(shí)的集群只有寫,沒有讀操作。10M/s左右的Send的流量是Partition之間進(jìn)行Replicate而產(chǎn)生的。從recv和writ的速率比較可以看出,寫盤是使用Asynchronous+Batch的方式,底層OS可能還會(huì)進(jìn)行磁盤寫順序優(yōu)化。而在有Read Request進(jìn)來的時(shí)候分為兩種情況,第一種是內(nèi)存中完成數(shù)據(jù)交換。

    Send流量從平均10M/s增加到了到平均60M/s,而磁盤Read只有不超過50KB/s。PageCache降低磁盤I/O效果非常明顯。

    接下來是讀一些收到了一段時(shí)間,已經(jīng)從內(nèi)存中被換出刷寫到磁盤上的老數(shù)據(jù)。

    Kafka如何通過經(jīng)典的內(nèi)存緩沖池設(shè)計(jì)來優(yōu)化JVM GC問題?

    其他指標(biāo)還是老樣子,而磁盤Read已經(jīng)飚高到40+MB/s。此時(shí)全部的數(shù)據(jù)都已經(jīng)是走硬盤了(對(duì)硬盤的順序讀取OS層會(huì)進(jìn)行Prefill PageCache的優(yōu)化)。依然沒有任何性能問題。

    Tips

  • Kafka官方并不建議通過Broker端的log.flush.interval.messages和log.flush.interval.ms來強(qiáng)制寫盤,認(rèn)為數(shù)據(jù)的可靠性應(yīng)該通過Replica來保證,而強(qiáng)制Flush數(shù)據(jù)到磁盤會(huì)對(duì)整體性能產(chǎn)生影響。

  • 可以通過調(diào)整/proc/sys/vm/dirty_background_ratio和/proc/sys/vm/dirty_ratio來調(diào)優(yōu)性能。

  • 臟頁率超過第一個(gè)指標(biāo)會(huì)啟動(dòng)pdflush開始Flush Dirty PageCache。

  • 臟頁率超過第二個(gè)指標(biāo)會(huì)阻塞所有的寫操作來進(jìn)行Flush。

  • 根據(jù)不同的業(yè)務(wù)需求可以適當(dāng)?shù)慕档蚫irty_background_ratio和提高dirty_ratio。

  • Partition

    Partition是Kafka可以很好的橫向擴(kuò)展和提供高并發(fā)處理以及實(shí)現(xiàn)Replication的基礎(chǔ)。

    擴(kuò)展性方面。首先,Kafka允許Partition在集群內(nèi)的Broker之間任意移動(dòng),以此來均衡可能存在的數(shù)據(jù)傾斜問題。其次,Partition支持自定義的分區(qū)算法,例如可以將同一個(gè)Key的所有消息都路由到同一個(gè)Partition上去。同時(shí)Leader也可以在In-Sync的Replica中遷移。由于針對(duì)某一個(gè)Partition的所有讀寫請(qǐng)求都是只由Leader來處理,所以Kafka會(huì)盡量把Leader均勻的分散到集群的各個(gè)節(jié)點(diǎn)上,以免造成網(wǎng)絡(luò)流量過于集中。

    并發(fā)方面。任意Partition在某一個(gè)時(shí)刻只能被一個(gè)Consumer Group內(nèi)的一個(gè)Consumer消費(fèi)(反過來一個(gè)Consumer則可以同時(shí)消費(fèi)多個(gè)Partition),Kafka非常簡(jiǎn)潔的Offset機(jī)制最小化了Broker和Consumer之間的交互,這使Kafka并不會(huì)像同類其他消息隊(duì)列一樣,隨著下游Consumer數(shù)目的增加而成比例的降低性能。此外,如果多個(gè)Consumer恰巧都是消費(fèi)時(shí)間序上很相近的數(shù)據(jù),可以達(dá)到很高的PageCache命中率,因而Kafka可以非常高效的支持高并發(fā)讀操作,實(shí)踐中基本可以達(dá)到單機(jī)網(wǎng)卡上限。

    不過,Partition的數(shù)量并不是越多越好,Partition的數(shù)量越多,平均到每一個(gè)Broker上的數(shù)量也就越多。考慮到Broker宕機(jī)(Network Failure, Full GC)的情況下,需要由Controller來為所有宕機(jī)的Broker上的所有Partition重新選舉Leader,假設(shè)每個(gè)Partition的選舉消耗10ms,如果Broker上有500個(gè)Partition,那么在進(jìn)行選舉的5s的時(shí)間里,對(duì)上述Partition的讀寫操作都會(huì)觸發(fā)LeaderNotAvailableException。

    再進(jìn)一步,如果掛掉的Broker是整個(gè)集群的Controller,那么首先要進(jìn)行的是重新任命一個(gè)Broker作為Controller。新任命的Controller要從Zookeeper上獲取所有Partition的Meta信息,獲取每個(gè)信息大概3-5ms,那么如果有10000個(gè)Partition這個(gè)時(shí)間就會(huì)達(dá)到30s-50s。而且不要忘記這只是重新啟動(dòng)一個(gè)Controller花費(fèi)的時(shí)間,在這基礎(chǔ)上還要再加上前面說的選舉Leader的時(shí)間 -_-!!!!!!

    此外,在Broker端,對(duì)Producer和Consumer都使用了Buffer機(jī)制。其中Buffer的大小是統(tǒng)一配置的,數(shù)量則與Partition個(gè)數(shù)相同。如果Partition個(gè)數(shù)過多,會(huì)導(dǎo)致Producer和Consumer的Buffer內(nèi)存占用過大。

    Tips

  • Partition的數(shù)量盡量提前預(yù)分配,雖然可以在后期動(dòng)態(tài)增加Partition,但是會(huì)冒著可能破壞Message Key和Partition之間對(duì)應(yīng)關(guān)系的風(fēng)險(xiǎn)。

  • Replica的數(shù)量不要過多,如果條件允許盡量把Replica集合內(nèi)的Partition分別調(diào)整到不同的Rack。

  • 盡一切努力保證每次停Broker時(shí)都可以Clean Shutdown,否則問題就不僅僅是恢復(fù)服務(wù)所需時(shí)間長(zhǎng),還可能出現(xiàn)數(shù)據(jù)損壞或其他很詭異的問題。

  • Producer

    Kafka的研發(fā)團(tuán)隊(duì)表示在0.8版本里用Java重寫了整個(gè)Producer,據(jù)說性能有了很大提升。我還沒有親自對(duì)比試用過,這里就不做數(shù)據(jù)對(duì)比了。本文結(jié)尾的擴(kuò)展閱讀里提到了一套我認(rèn)為比較好的對(duì)照組,有興趣的同學(xué)可以嘗試一下。

    其實(shí)在Producer端的優(yōu)化大部分消息系統(tǒng)采取的方式都比較單一,無非也就化零為整、同步變異步這么幾種。

    Kafka系統(tǒng)默認(rèn)支持MessageSet,把多條Message自動(dòng)地打成一個(gè)Group后發(fā)送出去,均攤后拉低了每次通信的RTT。而且在組織MessageSet的同時(shí),還可以把數(shù)據(jù)重新排序,從爆發(fā)流式的隨機(jī)寫入優(yōu)化成較為平穩(wěn)的線性寫入。

    此外,還要著重介紹的一點(diǎn)是,Producer支持End-to-End的壓縮。數(shù)據(jù)在本地壓縮后放到網(wǎng)絡(luò)上傳輸,在Broker一般不解壓(除非指定要Deep-Iteration),直至消息被Consume之后在客戶端解壓。

    當(dāng)然用戶也可以選擇自己在應(yīng)用層上做壓縮和解壓的工作(畢竟Kafka目前支持的壓縮算法有限,只有GZIP和Snappy),不過這樣做反而會(huì)意外的降低效率!!!!Kafka的End-to-End壓縮與MessageSet配合在一起工作效果最佳,上面的做法直接割裂了兩者間聯(lián)系。至于道理其實(shí)很簡(jiǎn)單,壓縮算法中一條基本的原理“重復(fù)的數(shù)據(jù)量越多,壓縮比越高”。無關(guān)于消息體的內(nèi)容,無關(guān)于消息體的數(shù)量,大多數(shù)情況下輸入數(shù)據(jù)量大一些會(huì)取得更好的壓縮比。

    不過Kafka采用MessageSet也導(dǎo)致在可用性上一定程度的妥協(xié)。每次發(fā)送數(shù)據(jù)時(shí),Producer都是send()之后就認(rèn)為已經(jīng)發(fā)送出去了,但其實(shí)大多數(shù)情況下消息還在內(nèi)存的MessageSet當(dāng)中,尚未發(fā)送到網(wǎng)絡(luò),這時(shí)候如果Producer掛掉,那就會(huì)出現(xiàn)丟數(shù)據(jù)的情況。

    為了解決這個(gè)問題,Kafka在0.8版本的設(shè)計(jì)借鑒了網(wǎng)絡(luò)當(dāng)中的ack機(jī)制。如果對(duì)性能要求較高,又能在一定程度上允許Message的丟失,那就可以設(shè)置request.required.acks=0 來關(guān)閉ack,以全速發(fā)送。如果需要對(duì)發(fā)送的消息進(jìn)行確認(rèn),就需要設(shè)置request.required.acks為1或-1,那么1和-1又有什么區(qū)別呢?這里又要提到前面聊的有關(guān)Replica數(shù)量問題。如果配置為1,表示消息只需要被Leader接收并確認(rèn)即可,其他的Replica可以進(jìn)行異步拉取無需立即進(jìn)行確認(rèn),在保證可靠性的同時(shí)又不會(huì)把效率拉得很低。如果設(shè)置為-1,表示消息要Commit到該P(yáng)artition的ISR集合中的所有Replica后,才可以返回ack,消息的發(fā)送會(huì)更安全,而整個(gè)過程的延遲會(huì)隨著Replica的數(shù)量正比增長(zhǎng),這里就需要根據(jù)不同的需求做相應(yīng)的優(yōu)化。

    Tips

  • Producer的線程不要配置過多,尤其是在Mirror或者M(jìn)igration中使用的時(shí)候,會(huì)加劇目標(biāo)集群Partition消息亂序的情況(如果你的應(yīng)用場(chǎng)景對(duì)消息順序很敏感的話)。

  • 0.8版本的request.required.acks默認(rèn)是0(同0.7)。

  • Consumer

    Consumer端的設(shè)計(jì)大體上還算是比較常規(guī)的。

    ? 通過Consumer Group,可以支持生產(chǎn)者消費(fèi)者和隊(duì)列訪問兩種模式。

    ? Consumer API分為High level和Low level兩種。前一種重度依賴Zookeeper,所以性能差一些且不自由,但是超省心。第二種不依賴Zookeeper服務(wù),無論從自由度和性能上都有更好的表現(xiàn),但是所有的異常(Leader遷移、Offset越界、Broker宕機(jī)等)和Offset的維護(hù)都需要自行處理。

    ? 大家可以關(guān)注下不日發(fā)布的0.9 Release。開發(fā)人員又用Java重寫了一套Consumer。把兩套API合并在一起,同時(shí)去掉了對(duì)Zookeeper的依賴。據(jù)說性能有大幅度提升哦~~

    Tips

    強(qiáng)烈推薦使用Low level API,雖然繁瑣一些,但是目前只有這個(gè)API可以對(duì)Error數(shù)據(jù)進(jìn)行自定義處理,尤其是處理Broker異常或由于Unclean Shutdown導(dǎo)致的Corrupted Data時(shí),否則無法Skip只能等著“壞消息”在Broker上被Rotate掉,在此期間該Replica將會(huì)一直處于不可用狀態(tài)。

    那么Kafka如何做到能支持能同時(shí)發(fā)送大量消息的呢?

    答案是Kafka通過批量壓縮和發(fā)送做到的。

    我們知道消息肯定是放在內(nèi)存中的,大數(shù)據(jù)場(chǎng)景消息的不斷發(fā)送,內(nèi)存中不斷存在大量的消息,很容易引起GC。

    頻繁的GC特別是full gc是會(huì)造成“stop the world”,也就是其他線程停止工作等待垃圾回收線程執(zhí)行,繼而進(jìn)一步影響發(fā)送的速度影響吞吐量,那么Kafka是如何做到優(yōu)化JVM的GC問題的呢?看完本篇文章你會(huì)get到。

    Kafka的內(nèi)存池

    下面介紹下Kafka客戶端發(fā)送的大致過程,如下圖:

    Kafka的kafkaProducer對(duì)象是線程安全的,每個(gè)發(fā)送線程在發(fā)送消息時(shí)候共用一個(gè)kafkaProducer對(duì)象來調(diào)用發(fā)送方法,最后發(fā)送的數(shù)據(jù)根據(jù)Topic和分區(qū)的不同被組裝進(jìn)某一個(gè)RecordBatch中。

    發(fā)送的數(shù)據(jù)放入RecordBatch后會(huì)被發(fā)送線程批量取出組裝成ProduceRequest對(duì)象發(fā)送給Kafka服務(wù)端。

    可以看到發(fā)送數(shù)據(jù)線程和取數(shù)據(jù)線程都要跟內(nèi)存中的RecordBatch打交道,RecordBatch是存儲(chǔ)數(shù)據(jù)的對(duì)象,那么RecordBatch是怎么分配的呢?

    下面我們看下Kafka的緩沖池結(jié)構(gòu),如下圖所示:

    名詞解釋:

    緩沖池:BufferPool(緩沖池)對(duì)象,整個(gè)KafkaProducer實(shí)例中只有一個(gè)BufferPool對(duì)象。內(nèi)存池總大小,它是已使用空間和可使用空間的總和,用totalMemory表示(由buffer.memory配置,默認(rèn)32M)。

    可使用的空間:它包含包括兩個(gè)部分,綠色部分代表未申請(qǐng)未使用的部分,用availableMemory表示

    黃色部分代表已經(jīng)申請(qǐng)但沒有使用的部分,用一個(gè)ByteBuffer雙端隊(duì)列(Deque)表示,在BufferPool中這個(gè)隊(duì)列叫free,隊(duì)列中的每個(gè)ByteBuffer的大小用poolableSize表示(由batch.size配置,默認(rèn)16k),因?yàn)槊看蝔ree申請(qǐng)內(nèi)存都是以poolableSize為單位申請(qǐng)的,申請(qǐng)poolableSize大小的bytebuffer后用RecordBatch來包裝起來。

    已使用空間:代表緩沖池中已經(jīng)裝了數(shù)據(jù)的部分。

    根據(jù)以上介紹,我們可以知道,總的BufferPool大小=已使用空間+可使用空間;free的大小=free.size * poolableSize(poolsize就是單位batch的size)。

    數(shù)據(jù)的分配過程??

    總的來說是判斷需要存儲(chǔ)的數(shù)據(jù)的大小是否free里有合適的recordBatch裝得下。如果裝得下則用recordBatch來存儲(chǔ)數(shù)據(jù),如果free里沒有空間但是availableMemory+free的大小比需要存儲(chǔ)的數(shù)據(jù)大(也就是說可使用空間比實(shí)際需要申請(qǐng)的空間大),說明可使用空間大小足夠,則會(huì)用讓free一直釋放byteBuffer空間直到有空間裝得下要存儲(chǔ)的數(shù)據(jù)位置,如果需要申請(qǐng)的空間比實(shí)際可使用空間大,則內(nèi)存申請(qǐng)會(huì)阻塞直到申請(qǐng)到足夠的內(nèi)存為止。

    整個(gè)申請(qǐng)過程如下圖:

    數(shù)據(jù)的釋放過程?

    總的來說有2個(gè)入口,釋放過程如下圖:

    再來看段申請(qǐng)空間代碼:

    //判斷需要申請(qǐng)空間大小,如果需要申請(qǐng)空間大小比batchSize小,那么申請(qǐng)大小就是batchsize,如果比batchSize大,那么大小以實(shí)際申請(qǐng)大小為準(zhǔn) int?size?=?Math.max(this.batchSize,?Records.LOG_OVERHEAD?+?Record.recordSize(key,?value)); log.trace("Allocating?a?new?{}?byte?message?buffer?for?topic?{}?partition?{}",?size,?tp.topic(),?tp.partition()); //這個(gè)過程可以參考圖3 ByteBuffer?buffer?=?free.allocate(size,?maxTimeToBlock);

    再來段回收的核心代碼:

    public?void?deallocate(ByteBuffer?buffer,?int?size)?{????lock.lock();????try?{????????//只有標(biāo)準(zhǔn)規(guī)格(bytebuffer空間大小和poolableSize大小一致的才放入free)???????if?(size?==?this.poolableSize?&&?size?==?buffer.capacity())?{???????????//注意這里的buffer是直接reset了,重新reset后可以重復(fù)利用,沒有g(shù)c問題????????????buffer.clear();????????????//添加進(jìn)free循環(huán)利用???????????this.free.add(buffer);????????}?else?{????????????//規(guī)格不是poolableSize大小的那么沒有進(jìn)行重制,但是會(huì)把a(bǔ)vailableMemory增加,代表整個(gè)可用內(nèi)存空間增加了,這個(gè)時(shí)候buffer的回收依賴jvm的gc????????????this.availableMemory?+=?size;????????}???????//喚醒排在前面的等待線程???????Condition?moreMem?=?this.waiters.peekFirst();???????if?(moreMem?!=?null)?moreMem.signal();????}?finally?{???????lock.unlock();????} }

    通過申請(qǐng)和釋放過程流程圖以及釋放空間代碼,我們可以得到一個(gè)結(jié)論:就是如果用戶申請(qǐng)的數(shù)據(jù)(發(fā)送的消息)大小都是在poolableSize(由batch.size配置,默認(rèn)16k)以內(nèi),并且申請(qǐng)時(shí)候free里有空間,那么用戶申請(qǐng)的空間是可以循環(huán)利用的空間,可以減少gc,但是其他情況也可能存在直接用堆內(nèi)存申請(qǐng)空間的情況,存在gc的情況。

    如何盡量避免呢,如果批量消息里面單個(gè)消息都是超過16k,可以考慮調(diào)整batchSize大小。

    如果沒有使用緩沖池,那么用戶發(fā)送的模型是下圖5,由于GC特別是Full GC的存在,如果大量發(fā)送,就可能會(huì)發(fā)生頻繁的垃圾回收,導(dǎo)致的工作線程的停頓,會(huì)對(duì)整個(gè)發(fā)送性能,吞吐量延遲等都有影響。

    使用緩沖池后,整個(gè)使用過程可以縮略為下圖:

    總結(jié)

    Kafka通過使用內(nèi)存緩沖池的設(shè)計(jì),讓整個(gè)發(fā)送過程中的存儲(chǔ)空間循環(huán)利用,有效減少JVM GC造成的影響,從而提高發(fā)送性能,提升吞吐量。

    來源 |?https://urlify.cn/ZrERri

    總結(jié)

    以上是生活随笔為你收集整理的面试官问:上亿数据量下,Kafka是如何优化JVM GC问题的?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。