- Flink內(nèi)核原理與實現(xiàn)
- 馮飛 崔鵬云 陳冠華編著
- 1800字
- 2021-04-14 11:30:43
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。
- 32位嵌入式系統(tǒng)與SoC設(shè)計導(dǎo)論
- VMware Performance and Capacity Management(Second Edition)
- 大數(shù)據(jù)挑戰(zhàn)與NoSQL數(shù)據(jù)庫技術(shù)
- CentOS 8 Essentials
- 變頻器、軟啟動器及PLC實用技術(shù)260問
- Implementing Splunk 7(Third Edition)
- Linux服務(wù)與安全管理
- 筆記本電腦維修90個精選實例
- 格蠹匯編
- 從零開始學(xué)SQL Server
- Learning Linux Shell Scripting
- 深度學(xué)習(xí)原理與 TensorFlow實踐
- Access 2007數(shù)據(jù)庫入門與實例應(yīng)用金典
- 工業(yè)機(jī)器人技術(shù)
- R Data Visualization Recipes