Flink 原理與實(shí)現(xiàn):內(nèi)存管理

如今,大數(shù)據(jù)領(lǐng)域的開源框架(Hadoop,Spark,Storm)都使用的 JVM,當(dāng)然也包括 Flink?;?JVM 的數(shù)據(jù)分析引擎都需要面對(duì)將大量數(shù)據(jù)存到內(nèi)存中,這就不得不面對(duì) JVM 存在的幾個(gè)問題:

  1. Java 對(duì)象存儲(chǔ)密度低。一個(gè)只包含 boolean 屬性的對(duì)象占用了16個(gè)字節(jié)內(nèi)存:對(duì)象頭占了8個(gè),boolean 屬性占了1個(gè),對(duì)齊填充占了7個(gè)。而實(shí)際上只需要一個(gè)bit(1/8字節(jié))就夠了。
  2. Full GC 會(huì)極大地影響性能,尤其是為了處理更大數(shù)據(jù)而開了很大內(nèi)存空間的JVM來說,GC 會(huì)達(dá)到秒級(jí)甚至分鐘級(jí)。
  3. OOM 問題影響穩(wěn)定性。OutOfMemoryError是分布式計(jì)算框架經(jīng)常會(huì)遇到的問題,當(dāng)JVM中所有對(duì)象大小超過分配給JVM的內(nèi)存大小時(shí),就會(huì)發(fā)生OutOfMemoryError錯(cuò)誤,導(dǎo)致JVM崩潰,分布式框架的健壯性和性能都會(huì)受到影響。

所以目前,越來越多的大數(shù)據(jù)項(xiàng)目開始自己管理JVM內(nèi)存了,像 Spark、Flink、HBase,為的就是獲得像 C 一樣的性能以及避免 OOM 的發(fā)生。本文將會(huì)討論 Flink 是如何解決上面的問題的,主要內(nèi)容包括內(nèi)存管理、定制的序列化工具、緩存友好的數(shù)據(jù)結(jié)構(gòu)和算法、堆外內(nèi)存、JIT編譯優(yōu)化等。

積極的內(nèi)存管理

Flink 并不是將大量對(duì)象存在堆上,而是將對(duì)象都序列化到一個(gè)預(yù)分配的內(nèi)存塊上,這個(gè)內(nèi)存塊叫做 MemorySegment,它代表了一段固定長(zhǎng)度的內(nèi)存(默認(rèn)大小為 32KB),也是 Flink 中最小的內(nèi)存分配單元,并且提供了非常高效的讀寫方法。你可以把 MemorySegment 想象成是為 Flink 定制的 java.nio.ByteBuffer。它的底層可以是一個(gè)普通的 Java 字節(jié)數(shù)組(byte[]),也可以是一個(gè)申請(qǐng)?jiān)诙淹獾?ByteBuffer。每條記錄都會(huì)以序列化的形式存儲(chǔ)在一個(gè)或多個(gè)MemorySegment中。

Flink 中的 Worker 名叫 TaskManager,是用來運(yùn)行用戶代碼的 JVM 進(jìn)程。TaskManager 的堆內(nèi)存主要被分成了三個(gè)部分:


  • Remaining (Free) Heap:這部分的內(nèi)存是留給用戶代碼以及 TaskManager 的數(shù)據(jù)結(jié)構(gòu)使用的。因?yàn)檫@些數(shù)據(jù)結(jié)構(gòu)一般都很小,所以基本上這些內(nèi)存都是給用戶代碼使用的。從GC的角度來看,可以把這里看成的新生代,也就是說這里主要都是由用戶代碼生成的短期對(duì)象。
  • Memory Manager Pool:這是一個(gè)由 MemoryManager 管理的,由眾多MemorySegment組成的超大集合。Flink 中的算法(如 sort/shuffle/join)會(huì)向這個(gè)內(nèi)存池申請(qǐng) MemorySegment,將序列化后的數(shù)據(jù)存于其中,使用完后釋放回內(nèi)存池。默認(rèn)情況下,池子占了堆內(nèi)存的 70% 的大小。
  • Network Buffers: 一定數(shù)量的32KB大小的 buffer,主要用于數(shù)據(jù)的網(wǎng)絡(luò)傳輸。在 TaskManager 啟動(dòng)的時(shí)候就會(huì)分配。默認(rèn)數(shù)量是 2048 個(gè),可以通過 taskmanager.network.numberOfBuffers 來配置。

注意:Memory Manager Pool 主要在Batch模式下使用。在Steaming模式下,該池子不會(huì)預(yù)分配內(nèi)存,也不會(huì)向該池子請(qǐng)求內(nèi)存塊。也就是說該部分的內(nèi)存都是可以給用戶代碼使用的。不過社區(qū)是打算在 Streaming 模式下也能將該池子利用起來。

Flink 采用類似 DBMS 的 sort 和 join 算法,直接操作二進(jìn)制數(shù)據(jù),從而使序列化/反序列化帶來的開銷達(dá)到最小。所以 Flink 的內(nèi)部實(shí)現(xiàn)更像 C/C++ 而非 Java。如果需要處理的數(shù)據(jù)超出了內(nèi)存限制,則會(huì)將部分?jǐn)?shù)據(jù)存儲(chǔ)到硬盤上。如果要操作多塊MemorySegment就像操作一塊大的連續(xù)內(nèi)存一樣,F(xiàn)link會(huì)使用邏輯視圖(AbstractPagedInputView)來方便操作。下圖描述了 Flink 如何存儲(chǔ)序列化后的數(shù)據(jù)到內(nèi)存塊中,以及在需要的時(shí)候如何將數(shù)據(jù)存儲(chǔ)到磁盤上。

從上面我們能夠得出 Flink 積極的內(nèi)存管理以及直接操作二進(jìn)制數(shù)據(jù)有以下幾點(diǎn)好處:

  1. 減少GC壓力。顯而易見,因?yàn)樗谐qv型數(shù)據(jù)都以二進(jìn)制的形式存在 Flink 的MemoryManager中,這些MemorySegment一直呆在老年代而不會(huì)被GC回收。其他的數(shù)據(jù)對(duì)象基本上是由用戶代碼生成的短生命周期對(duì)象,這部分對(duì)象可以被 Minor GC 快速回收。只要用戶不去創(chuàng)建大量類似緩存的常駐型對(duì)象,那么老年代的大小是不會(huì)變的,Major GC也就永遠(yuǎn)不會(huì)發(fā)生。從而有效地降低了垃圾回收的壓力。另外,這里的內(nèi)存塊還可以是堆外內(nèi)存,這可以使得 JVM 內(nèi)存更小,從而加速垃圾回收。

  2. 避免了OOM。所有的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)和算法只能通過內(nèi)存池申請(qǐng)內(nèi)存,保證了其使用的內(nèi)存大小是固定的,不會(huì)因?yàn)檫\(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)和算法而發(fā)生OOM。在內(nèi)存吃緊的情況下,算法(sort/join等)會(huì)高效地將一大批內(nèi)存塊寫到磁盤,之后再讀回來。因此,OutOfMemoryErrors可以有效地被避免。

  3. 節(jié)省內(nèi)存空間。Java 對(duì)象在存儲(chǔ)上有很多額外的消耗(如上一節(jié)所談)。如果只存儲(chǔ)實(shí)際數(shù)據(jù)的二進(jìn)制內(nèi)容,就可以避免這部分消耗。

  4. 高效的二進(jìn)制操作 & 緩存友好的計(jì)算。二進(jìn)制數(shù)據(jù)以定義好的格式存儲(chǔ),可以高效地比較與操作。另外,該二進(jìn)制形式可以把相關(guān)的值,以及hash值,鍵值和指針等相鄰地放進(jìn)內(nèi)存中。這使得數(shù)據(jù)結(jié)構(gòu)可以對(duì)高速緩存更友好,可以從 L1/L2/L3 緩存獲得性能的提升(下文會(huì)詳細(xì)解釋)。

為 Flink 量身定制的序列化框架

目前 Java 生態(tài)圈提供了眾多的序列化框架:Java serialization, Kryo, Apache Avro 等等。但是 Flink 實(shí)現(xiàn)了自己的序列化框架。因?yàn)樵?Flink 中處理的數(shù)據(jù)流通常是同一類型,由于數(shù)據(jù)集對(duì)象的類型固定,對(duì)于數(shù)據(jù)集可以只保存一份對(duì)象Schema信息,節(jié)省大量的存儲(chǔ)空間。同時(shí),對(duì)于固定大小的類型,也可通過固定的偏移位置存取。當(dāng)我們需要訪問某個(gè)對(duì)象成員變量的時(shí)候,通過定制的序列化工具,并不需要反序列化整個(gè)Java對(duì)象,而是可以直接通過偏移量,只是反序列化特定的對(duì)象成員變量。如果對(duì)象的成員變量較多時(shí),能夠大大減少Java對(duì)象的創(chuàng)建開銷,以及內(nèi)存數(shù)據(jù)的拷貝大小。

Flink支持任意的Java或是Scala類型。Flink 在數(shù)據(jù)類型上有很大的進(jìn)步,不需要實(shí)現(xiàn)一個(gè)特定的接口(像Hadoop中的org.apache.hadoop.io.Writable),F(xiàn)link 能夠自動(dòng)識(shí)別數(shù)據(jù)類型。Flink 通過 Java Reflection 框架分析基于 Java 的 Flink 程序 UDF (User Define Function)的返回類型的類型信息,通過 Scala Compiler分析基于 Scala 的 Flink 程序 UDF 的返回類型的類型信息。類型信息由 TypeInformation 類表示,TypeInformation 支持以下幾種類型:

  • BasicTypeInfo: 任意Java 基本類型(裝箱的)或 String 類型。
  • BasicArrayTypeInfo: 任意Java基本類型數(shù)組(裝箱的)或 String 數(shù)組。
  • WritableTypeInfo: 任意 Hadoop Writable 接口的實(shí)現(xiàn)類。
  • TupleTypeInfo: 任意的 Flink Tuple 類型(支持Tuple1 to Tuple25)。Flink tuples 是固定長(zhǎng)度固定類型的Java Tuple實(shí)現(xiàn)。
  • CaseClassTypeInfo: 任意的 Scala CaseClass(包括 Scala tuples)。
  • PojoTypeInfo: 任意的 POJO (Java or Scala),例如,Java對(duì)象的所有成員變量,要么是 public 修飾符定義,要么有 getter/setter 方法。
  • GenericTypeInfo: 任意無法匹配之前幾種類型的類。

前六種數(shù)據(jù)類型基本上可以滿足絕大部分的Flink程序,針對(duì)前六種類型數(shù)據(jù)集,F(xiàn)link皆可以自動(dòng)生成對(duì)應(yīng)的 TypeSerializer,能非常高效地對(duì)數(shù)據(jù)集進(jìn)行序列化和反序列化。對(duì)于最后一種數(shù)據(jù)類型,F(xiàn)link會(huì)使用Kryo進(jìn)行序列化和反序列化。每個(gè)TypeInformation中,都包含了serializer,類型會(huì)自動(dòng)通過serializer進(jìn)行序列化,然后用Java Unsafe接口寫入MemorySegments。對(duì)于可以用作key的數(shù)據(jù)類型,F(xiàn)link還同時(shí)自動(dòng)生成TypeComparator,用來輔助直接對(duì)序列化后的二進(jìn)制數(shù)據(jù)進(jìn)行compare、hash等操作。對(duì)于 Tuple、CaseClass、POJO 等組合類型,其TypeSerializerTypeComparator也是組合的,序列化和比較時(shí)會(huì)委托給對(duì)應(yīng)的serializerscomparators。如下圖展示 一個(gè)內(nèi)嵌型的Tuple3 對(duì)象的序列化過程。


可以看出這種序列化方式存儲(chǔ)密度是相當(dāng)緊湊的。其中 int 占4字節(jié),double 占8字節(jié),POJO多個(gè)一個(gè)字節(jié)的header,PojoSerializer只負(fù)責(zé)將header序列化進(jìn)去,并委托每個(gè)字段對(duì)應(yīng)的serializer對(duì)字段進(jìn)行序列化。

Flink 的類型系統(tǒng)可以很輕松地?cái)U(kuò)展出自定義的TypeInformation、Serializer以及Comparator,來提升數(shù)據(jù)類型在序列化和比較時(shí)的性能。

Flink 如何直接操作二進(jìn)制數(shù)據(jù)

Flink 提供了如 group、sort、join 等操作,這些操作都需要訪問海量數(shù)據(jù)。這里,我們以sort為例,這是一個(gè)在 Flink 中使用非常頻繁的操作。

首先,F(xiàn)link 會(huì)從MemoryManager 中申請(qǐng)一批 MemorySegment,我們把這批 MemorySegment 稱作 sort buffer,用來存放排序的數(shù)據(jù)。

我們會(huì)把 sort buffer 分成兩塊區(qū)域。一個(gè)區(qū)域是用來存放所有對(duì)象完整的二進(jìn)制數(shù)據(jù)。另一個(gè)區(qū)域用來存放指向完整二進(jìn)制數(shù)據(jù)的指針以及定長(zhǎng)的序列化后的key(key+pointer)。如果需要序列化的key是個(gè)變長(zhǎng)類型,如String,則會(huì)取其前綴序列化。如上圖所示,當(dāng)一個(gè)對(duì)象要加到 sort buffer 中時(shí),它的二進(jìn)制數(shù)據(jù)會(huì)被加到第一個(gè)區(qū)域,指針(可能還有key)會(huì)被加到第二個(gè)區(qū)域。

將實(shí)際的數(shù)據(jù)和指針加定長(zhǎng)key分開存放有兩個(gè)目的。第一,交換定長(zhǎng)塊(key+pointer)更高效,不用交換真實(shí)的數(shù)據(jù)也不用移動(dòng)其他key和pointer。第二,這樣做是緩存友好的,因?yàn)閗ey都是連續(xù)存儲(chǔ)在內(nèi)存中的,可以大大減少 cache miss(后面會(huì)詳細(xì)解釋)。

排序的關(guān)鍵是比大小和交換。Flink 中,會(huì)先用 key 比大小,這樣就可以直接用二進(jìn)制的key比較而不需要反序列化出整個(gè)對(duì)象。因?yàn)閗ey是定長(zhǎng)的,所以如果key相同(或者沒有提供二進(jìn)制key),那就必須將真實(shí)的二進(jìn)制數(shù)據(jù)反序列化出來,然后再做比較。之后,只需要交換key+pointer就可以達(dá)到排序的效果,真實(shí)的數(shù)據(jù)不用移動(dòng)。

最后,訪問排序后的數(shù)據(jù),可以沿著排好序的key+pointer區(qū)域順序訪問,通過pointer找到對(duì)應(yīng)的真實(shí)數(shù)據(jù),并寫到內(nèi)存或外部(更多細(xì)節(jié)可以看這篇文章 Joins in Flink)。

緩存友好的數(shù)據(jù)結(jié)構(gòu)和算法

隨著磁盤IO和網(wǎng)絡(luò)IO越來越快,CPU逐漸成為了大數(shù)據(jù)領(lǐng)域的瓶頸。從 L1/L2/L3 緩存讀取數(shù)據(jù)的速度比從主內(nèi)存讀取數(shù)據(jù)的速度快好幾個(gè)量級(jí)。通過性能分析可以發(fā)現(xiàn),CPU時(shí)間中的很大一部分都是浪費(fèi)在等待數(shù)據(jù)從主內(nèi)存過來上。如果這些數(shù)據(jù)可以從 L1/L2/L3 緩存過來,那么這些等待時(shí)間可以極大地降低,并且所有的算法會(huì)因此而受益。

在上面討論中我們談到的,F(xiàn)link 通過定制的序列化框架將算法中需要操作的數(shù)據(jù)(如sort中的key)連續(xù)存儲(chǔ),而完整數(shù)據(jù)存儲(chǔ)在其他地方。因?yàn)閷?duì)于完整的數(shù)據(jù)來說,key+pointer更容易裝進(jìn)緩存,這大大提高了緩存命中率,從而提高了基礎(chǔ)算法的效率。這對(duì)于上層應(yīng)用是完全透明的,可以充分享受緩存友好帶來的性能提升。

走向堆外內(nèi)存

Flink 基于堆內(nèi)存的內(nèi)存管理機(jī)制已經(jīng)可以解決很多JVM現(xiàn)存問題了,為什么還要引入堆外內(nèi)存?

  1. 啟動(dòng)超大內(nèi)存(上百GB)的JVM需要很長(zhǎng)時(shí)間,GC停留時(shí)間也會(huì)很長(zhǎng)(分鐘級(jí))。使用堆外內(nèi)存的話,可以極大地減小堆內(nèi)存(只需要分配Remaining Heap那一塊),使得 TaskManager 擴(kuò)展到上百GB內(nèi)存不是問題。
  2. 高效的 IO 操作。堆外內(nèi)存在寫磁盤或網(wǎng)絡(luò)傳輸時(shí)是 zero-copy,而堆內(nèi)存的話,至少需要 copy 一次。
  3. 堆外內(nèi)存是進(jìn)程間共享的。也就是說,即使JVM進(jìn)程崩潰也不會(huì)丟失數(shù)據(jù)。這可以用來做故障恢復(fù)(Flink暫時(shí)沒有利用起這個(gè),不過未來很可能會(huì)去做)。

但是強(qiáng)大的東西總是會(huì)有其負(fù)面的一面,不然為何大家不都用堆外內(nèi)存呢。

  1. 堆內(nèi)存的使用、監(jiān)控、調(diào)試都要簡(jiǎn)單很多。堆外內(nèi)存意味著更復(fù)雜更麻煩。
  2. Flink 有時(shí)需要分配短生命周期的 MemorySegment,這個(gè)申請(qǐng)?jiān)诙焉蠒?huì)更廉價(jià)。
  3. 有些操作在堆內(nèi)存上會(huì)快一點(diǎn)點(diǎn)。

Flink用通過ByteBuffer.allocateDirect(numBytes)來申請(qǐng)堆外內(nèi)存,用 sun.misc.Unsafe 來操作堆外內(nèi)存。

基于 Flink 優(yōu)秀的設(shè)計(jì),實(shí)現(xiàn)堆外內(nèi)存是很方便的。Flink 將原來的 MemorySegment 變成了抽象類,并生成了兩個(gè)子類。HeapMemorySegmentHybridMemorySegment。從字面意思上也很容易理解,前者是用來分配堆內(nèi)存的,后者是用來分配堆外內(nèi)存和堆內(nèi)存的。是的,你沒有看錯(cuò),后者既可以分配堆外內(nèi)存又可以分配堆內(nèi)存。為什么要這樣設(shè)計(jì)呢?

首先假設(shè)HybridMemorySegment只提供分配堆外內(nèi)存。在上述堆外內(nèi)存的不足中的第二點(diǎn)談到,Flink 有時(shí)需要分配短生命周期的 buffer,這些buffer用HeapMemorySegment會(huì)更高效。那么當(dāng)使用堆外內(nèi)存時(shí),為了也滿足堆內(nèi)存的需求,我們需要同時(shí)加載兩個(gè)子類。這就涉及到了 JIT 編譯優(yōu)化的問題。因?yàn)橐郧?MemorySegment 是一個(gè)單獨(dú)的 final 類,沒有子類。JIT 編譯時(shí),所有要調(diào)用的方法都是確定的,所有的方法調(diào)用都可以被去虛化(de-virtualized)和內(nèi)聯(lián)(inlined),這可以極大地提高性能(MemroySegment的使用相當(dāng)頻繁)。然而如果同時(shí)加載兩個(gè)子類,那么 JIT 編譯器就只能在真正運(yùn)行到的時(shí)候才知道是哪個(gè)子類,這樣就無法提前做優(yōu)化。實(shí)際測(cè)試的性能差距在 2.7 被左右。

Flink 使用了兩種方案:

方案1:只能有一種 MemorySegment 實(shí)現(xiàn)被加載

代碼中所有的短生命周期和長(zhǎng)生命周期的MemorySegment都實(shí)例化其中一個(gè)子類,另一個(gè)子類根本沒有實(shí)例化過(使用工廠模式來控制)。那么運(yùn)行一段時(shí)間后,JIT 會(huì)意識(shí)到所有調(diào)用的方法都是確定的,然后會(huì)做優(yōu)化。

方案2:提供一種實(shí)現(xiàn)能同時(shí)處理堆內(nèi)存和堆外內(nèi)存

這就是 HybridMemorySegment 了,能同時(shí)處理堆與堆外內(nèi)存,這樣就不需要子類了。這里 Flink 優(yōu)雅地實(shí)現(xiàn)了一份代碼能同時(shí)操作堆和堆外內(nèi)存。這主要?dú)w功于 sun.misc.Unsafe提供的一系列方法,如getLong方法:

sun.misc.Unsafe.getLong(Object reference, long offset)
  • 如果reference不為空,則會(huì)取該對(duì)象的地址,加上后面的offset,從相對(duì)地址處取出8字節(jié)并得到 long。這對(duì)應(yīng)了堆內(nèi)存的場(chǎng)景。
  • 如果reference為空,則offset就是要操作的絕對(duì)地址,從該地址處取出數(shù)據(jù)。這對(duì)應(yīng)了堆外內(nèi)存的場(chǎng)景。

這里我們看下 MemorySegment 及其子類的實(shí)現(xiàn)。

public abstract class MemorySegment {
  // 堆內(nèi)存引用
  protected final byte[] heapMemory;
  // 堆外內(nèi)存地址
  protected long address;
  
  //堆內(nèi)存的初始化
  MemorySegment(byte[] buffer, Object owner) {
    //一些先驗(yàn)檢查
    ...
    this.heapMemory = buffer;
    this.address = BYTE_ARRAY_BASE_OFFSET;
    ...
  }
  //堆外內(nèi)存的初始化
  MemorySegment(long offHeapAddress, int size, Object owner) {
    //一些先驗(yàn)檢查
    ...
    this.heapMemory = null;
    this.address = offHeapAddress;
    ...
  }
  
  public final long getLong(int index) {
    final long pos = address + index;
    if (index >= 0 && pos <= addressLimit - 8) {
      // 這是我們關(guān)注的地方,使用 Unsafe 來操作 on-heap & off-heap
      return UNSAFE.getLong(heapMemory, pos);
    }
    else if (address > addressLimit) {
      throw new IllegalStateException("segment has been freed");
    }
    else {
      // index is in fact invalid
      throw new IndexOutOfBoundsException();
    }
  }
  ...
}
public final class HeapMemorySegment extends MemorySegment {
  // 指向heapMemory的額外引用,用來如數(shù)組越界的檢查
  private byte[] memory;
  // 只能初始化堆內(nèi)存
  HeapMemorySegment(byte[] memory, Object owner) {
    super(Objects.requireNonNull(memory), owner);
    this.memory = memory;
  }
  ...
}
public final class HybridMemorySegment extends MemorySegment {
  private final ByteBuffer offHeapBuffer;
  
  // 堆外內(nèi)存初始化
  HybridMemorySegment(ByteBuffer buffer, Object owner) {
    super(checkBufferAndGetAddress(buffer), buffer.capacity(), owner);
    this.offHeapBuffer = buffer;
  }
  
  // 堆內(nèi)存初始化
  HybridMemorySegment(byte[] buffer, Object owner) {
    super(buffer, owner);
    this.offHeapBuffer = null;
  }
  ...
}

可以發(fā)現(xiàn),HybridMemorySegment 中的很多方法其實(shí)都下沉到了父類去實(shí)現(xiàn)。包括堆內(nèi)堆外內(nèi)存的初始化。MemorySegment 中的 getXXX/putXXX 方法都是調(diào)用了 unsafe 方法,可以說MemorySegment已經(jīng)具有了些 Hybrid 的意思了。HeapMemorySegment只調(diào)用了父類的MemorySegment(byte[] buffer, Object owner)方法,也就只能申請(qǐng)堆內(nèi)存。另外,閱讀代碼你會(huì)發(fā)現(xiàn),許多方法(大量的 getXXX/putXXX)都被標(biāo)記成了 final,兩個(gè)子類也是 final 類型,為的也是優(yōu)化 JIT 編譯器,會(huì)提醒 JIT 這個(gè)方法是可以被去虛化和內(nèi)聯(lián)的。

對(duì)于堆外內(nèi)存,使用 HybridMemorySegment 能同時(shí)用來代表堆和堆外內(nèi)存。這樣只需要一個(gè)類就能代表長(zhǎng)生命周期的堆外內(nèi)存和短生命周期的堆內(nèi)存。既然HybridMemorySegment已經(jīng)這么全能,為什么還要方案1呢?因?yàn)槲覀冃枰S模式來保證只有一個(gè)子類被加載(為了更高的性能),而且HeapMemorySegmentheap模式的HybridMemorySegment要快。

下方是一些性能測(cè)試數(shù)據(jù),更詳細(xì)的數(shù)據(jù)請(qǐng)參考這篇文章。

總結(jié)

本文主要總結(jié)了 Flink 面對(duì) JVM 存在的問題,而在內(nèi)存管理的道路上越走越深。從自己管理內(nèi)存,到序列化框架,再到堆外內(nèi)存。其實(shí)縱觀大數(shù)據(jù)生態(tài)圈,其實(shí)會(huì)發(fā)現(xiàn)各個(gè)開源項(xiàng)目都有同樣的趨勢(shì)。比如最近炒的很火熱的 Spark Tungsten 項(xiàng)目,與 Flink 在內(nèi)存管理上的思想是及其相似的。

來源:http://wuchong.me/blog/2016/04/29/flink-internals-memory-manage/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Apache Flink(下簡(jiǎn)稱Flink)項(xiàng)目是大數(shù)據(jù)處理領(lǐng)域最近冉冉升起的一顆新星,其不同于其他大數(shù)據(jù)項(xiàng)目的諸...
    尼小摩閱讀 34,440評(píng)論 0 85
  • Apache Flink(下簡(jiǎn)稱Flink)項(xiàng)目是大數(shù)據(jù)處理領(lǐng)域最近冉冉升起的一顆新星,其不同于其他大數(shù)據(jù)項(xiàng)目的諸...
    Alukar閱讀 27,286評(píng)論 1 48
  • 今天說說我自己的身體感受。高三的時(shí)候,有一陣子,學(xué)業(yè)壓力很大,也沒有目標(biāo)。有一晚蹲在窗臺(tái)上向外望去,我在想如果從這...
    云中看花閱讀 112評(píng)論 0 0
  • 席慕蓉有一句名言: “是時(shí)候了。好好地做個(gè)凡人。不和別人吵架。不需任何解釋。不論是否愛過。不說臟話……一日三餐一個(gè)...
    莫愁小窩Janies閱讀 264評(píng)論 0 0

友情鏈接更多精彩內(nèi)容