官术网_书友最值得收藏!

6.1 自主內(nèi)存管理

Flink從一開始就選擇了使用自主的內(nèi)存管理,避開了JVM內(nèi)存管理在大數(shù)據(jù)場景下的問題,提升了計算效率。

1. JVM內(nèi)存管理的不足

基于JVM的數(shù)據(jù)分析引擎需要將大量數(shù)據(jù)存到內(nèi)存中,這就不得不面對JVM存在的幾個問題。

(1)有效數(shù)據(jù)密度低

Java的對象在內(nèi)存中的存儲包含3個主要部分:對象頭、實例數(shù)據(jù)、對齊填充部分。32位和64位的虛擬機(jī)中對象頭分別需要占用32bit和64bit。實例數(shù)據(jù)是實際的數(shù)據(jù)存儲。為了提高效率,內(nèi)存中數(shù)據(jù)存儲不是連續(xù)的,而是按照8byte的整數(shù)倍進(jìn)行存儲。例如,只有一個boolean字段的類實例占16byte:頭信息占8byte,boolean占1byte,為了對齊達(dá)到8的倍數(shù)會額外占用7byte。這就導(dǎo)致在JVM中有效信息的存儲密度很低。

(2)垃圾回收

JVM的內(nèi)存回收機(jī)制的優(yōu)點和缺點同樣明顯,優(yōu)點是開發(fā)者無須資源回收,可以提高開發(fā)效率,減少了內(nèi)存泄漏的可能。但是內(nèi)存回收是不可控的,在大數(shù)據(jù)計算的場景中,這個缺點被放大,TB、PB級的數(shù)據(jù)計算需要消耗大量的內(nèi)存,在內(nèi)存中產(chǎn)生海量的Java對象,一旦出現(xiàn)Full GC,GC會達(dá)到秒級甚至分鐘級,直接影響執(zhí)行效率。

GC帶來的中斷會使集群中的心跳信息超時,導(dǎo)致節(jié)點被踢出集群,整個集群進(jìn)入不穩(wěn)定的狀態(tài)。雖然通過JVM參數(shù)的調(diào)優(yōu)可以提升回收效率,盡量減少Full GC,但是仍然不能避免這個問題,精確的調(diào)優(yōu)也非常困難。

(3)OOM問題影響穩(wěn)定性

OutOfMemoryError是分布式計算框架經(jīng)常會遇到的問題,當(dāng)JVM中所有對象大小超過分配給JVM的內(nèi)存大小時,就會發(fā)生OutOfMemoryError錯誤,導(dǎo)致JVM崩潰,分布式框架的健壯性和性能都會受到影響。

(4)緩存未命中問題

CPU進(jìn)行計算的時候,是從CPU緩存中獲取數(shù)據(jù),而不是直接從內(nèi)存獲取數(shù)據(jù)。CPU有分L1和L2/3級緩存。L1小,一般為32KB,L3大,能達(dá)到32MB。緩存的理論基礎(chǔ)是程序局部性原理,包括時間局部性和空間局部性:最近被CPU訪問的數(shù)據(jù),短期內(nèi)CPU還要訪問(時間);被CPU訪問的數(shù)據(jù)附近的數(shù)據(jù),CPU短期內(nèi)還要訪問(空間)。Java對象在堆上存儲的時候并不是連續(xù)的,所以從內(nèi)存中讀取Java對象時,緩存的鄰近的內(nèi)存區(qū)域的數(shù)據(jù)往往不是CPU下一步計算所需要的,這就是緩存未命中。此時CPU需要空轉(zhuǎn)等待從內(nèi)存中重新讀取數(shù)據(jù),CPU的速度和內(nèi)存的速度之間差好幾個數(shù)量級,導(dǎo)致CPU沒有充分利用起來。如果數(shù)據(jù)沒有在內(nèi)存中,而是需要從磁盤上加載,那么執(zhí)行效率就會變得慘不忍睹。

不同硬件的訪問延遲如圖6-1所示。

圖6-1 不同硬件的訪問延遲

從圖中可以看到,L1級緩存的訪問效率最高可以到普通磁盤的108倍(1億倍)。

2. 自主內(nèi)存管理

因為JVM存在諸多問題,所以越來越多的大數(shù)據(jù)計算引擎選擇自行管理JVM內(nèi)存,如Spark、Flink、HBase,盡量達(dá)到C/C++ 一樣的性能,同時避免OOM的發(fā)生。本章主要介紹Flink是如何解決上面的問題的,主要內(nèi)容包括內(nèi)存管理、定制的序列化工具、緩存友好的數(shù)據(jù)結(jié)構(gòu)和算法、堆外內(nèi)存等。

在Flink中,Java對象的有效信息被序列化為二進(jìn)制數(shù)據(jù)流,在內(nèi)存中連續(xù)存儲,保存在預(yù)分配的內(nèi)存塊上,內(nèi)存塊叫作MemorySegment。MemorySegment是內(nèi)存分配的最小單元,是一段固定長度的內(nèi)存(默認(rèn)大小為32KB),并且提供了非常高效的讀寫方法,很多運算可以直接操作二進(jìn)制數(shù)據(jù),不需要反序列化即可執(zhí)行。

MemorySegment可以保存在堆上,其內(nèi)部存儲為一個Java byte數(shù)組,也可以保存在堆外的ByteBuffer中。每條記錄都會以序列化的形式存儲在一個或多個MemorySegment中。

Flink早期版本使用的是堆上內(nèi)存,在堆內(nèi)存上管理序列化之后的數(shù)據(jù)。如果需要處理的數(shù)據(jù)超出了內(nèi)存限制,則會將部分?jǐn)?shù)據(jù)存儲到硬盤上。操作多塊MemorySegment就像操作一塊大的連續(xù)內(nèi)存一樣,F(xiàn)link會使用邏輯視圖(AbstractPagedInputView)以方便操作。

但使用堆上內(nèi)存,仍然不是完全自主的內(nèi)存管理,還存在以下問題。

1)超大內(nèi)存(上百GB)JVM的啟動需要很長時間,F(xiàn)ull GC可以達(dá)到分鐘級。使用堆外內(nèi)存,可以將大量的數(shù)據(jù)保存在堆外,極大地減小堆內(nèi)存,避免GC和內(nèi)存溢出的問題。

2)高效的IO操作。堆外內(nèi)存在寫磁盤或網(wǎng)絡(luò)傳輸時是zero-copy,而堆上內(nèi)存則至少需要1次內(nèi)存復(fù)制。

3)堆外內(nèi)存是進(jìn)程間共享的。也就是說,即使JVM進(jìn)程崩潰也不會丟失數(shù)據(jù)。這可以用來做故障恢復(fù)(Flink暫時沒有利用這項功能,不過未來很可能會去做)。

3. 堆外內(nèi)存的不足之處

堆外內(nèi)存提供了更好的性能和更可控的內(nèi)存管理,但是也存在幾個問題:

1)堆上內(nèi)存的使用、監(jiān)控、調(diào)試簡單,堆外內(nèi)存出現(xiàn)問題后的診斷則較為復(fù)雜。

2)Flink有時需要分配短生命周期的MemorySegment,在堆外內(nèi)存上分配比在堆上內(nèi)存開銷更高。

3)在Flink的測試中,部分操作在堆外內(nèi)存上會比堆上內(nèi)存慢。

同時為了提高效率,F(xiàn)link在計算中采用了DBMS的Sort和Join算法,直接操作二進(jìn)制數(shù)據(jù),避免數(shù)據(jù)反復(fù)序列化帶來的開銷。Flink的內(nèi)部實現(xiàn)更像C/C++ 而非Java。

主站蜘蛛池模板: 商城县| 巨鹿县| 蓬溪县| 嘉兴市| 上杭县| 桂东县| 常德市| 稻城县| 平陆县| 长兴县| 盘锦市| 内黄县| 临海市| 宁武县| 固阳县| 巴青县| 承德县| 蚌埠市| 灵武市| 江北区| 叙永县| 五寨县| 浠水县| 临沭县| 武安市| 吴旗县| 平南县| 同江市| 石阡县| 绥中县| 阳城县| 双桥区| 多伦县| 连平县| 比如县| 黄浦区| 定州市| 磐安县| 宜阳县| 荔波县| 右玉县|