Apache Arrow 和 Java:大數據傳輸快如閃電

本文要點

  • Arrow 為分析應用程序提供了零複製數據傳輸
  • Arrow 支持內存中、列式格式和數據處理
  • Arrow 是跨平臺、跨語言可互操作的數據交換方式
  • Arrow 是大數據系統的骨幹

大數據天生就太大了,無法裝進單獨的一臺機器裡。數據集需要在多臺計算機上分區存儲。每個分區都分配給一臺主機,還可以選擇分配給備份機器。這樣,每臺機器都會有多個分區。多數大數據框架使用隨機策略為計算機分配分區。如果每個計算作業都使用一個分區,那麼這種策略會將計算負載很好地分散在整個群集上。但是,如果一個作業需要多個分區,則它很有可能需要從其他計算機獲取分區。而傳輸數據的過程總會帶來性能損失。

Apache Arrow 提出了一種跨語言、跨平臺,列式(columnar)內存中(in-memory)的數據格式。由於各個平臺和編程語言上的數據都由相同的字節表示,它就不再需要序列化了。這種通用格式支持在大數據系統中進行零複製數據傳輸,以最大程度地降低數據傳輸帶來的性能影響。

本文的目的是介紹 Apache Arrow,並帶你熟悉Apache Arrow Java 庫的基本概念。隨本文附帶的源代碼在這裡。

通常,一次數據傳輸操作包括:

  • 以某種格式序列化數據
  • 通過網絡連接發送序列化數據
  • 在接收端反序列化數據

以 Web 應用程序中前端與後端之間的通信為例。人們通常使用 JavaScript 對象表示(JSON)格式序列化數據。數據量不大時它很好用。序列化和反序列化的開銷可以忽略不計,並且 JSON 可由人類閱讀理解,從而簡化了調試工作。但當數據量增加時,序列化成本可能成為主要的性能瓶頸。如果沒有適當的應對,系統最後可能會花費大部分時間來序列化數據。顯然,CPU 週期應該用到很多更有價值的事情上。

Apache Arrow 和 Java:大數據傳輸快如閃電

在此過程中,我們在軟件中控制一個要素:(反)序列化。自然,市面上有很多序列化框架。可選項有 ProtoBuf、Thrift 和 MessagePack 等等。其中許多框架將盡量降低序列化成本作為主要目標。

儘管它們在努力降低序列化成本,但(反)序列化的步驟依舊是不可避免的。你的代碼所作用的對象不是通過網絡發送的數據。另一側的代碼處理的對象也不是通過網絡接收到的字節。到頭來,最快的序列化就是沒有序列化

Apache Arrow 適合我嗎?

從概念上講,Apache Arrow 被設計為大數據系統(例如 Ballista 或 Dremio )或大數據系統集成的骨幹。如果你的用例不在大數據系統領域,可能就無需關心 Apache Arrow 的開銷。對你來說,行業流行的序列化框架(如 ProtoBuf、FlatBuffers、Thrift、MessagePack 或其他選項)可能更合適。

使用 Apache Arrow 編程與使用普通的舊 Java 對象編程的體驗有很大區別,因為前者並沒有 Java 對象。代碼一路都在緩衝區上操作。現有的實用程序庫(如 Apache Commons 和 Guava 等)不能再用了。你可能必須重新實現某些算法才能使用字節緩衝區。最後一點也很重要,你必須一直以列而非對象的思維來編程。

在 Apache Arrow 之上構建系統,需要你讀取、寫入、呼吸和消耗 Arrow 緩衝區。如果你要構建一個可以處理數據對象集合(如某種數據庫)的系統,想要計算對列友好的內容,並計劃在一個群集中運行它,那麼 Arrow 絕對值得投資。

與 Parquet 的集成(稍後討論)讓持久性更容易實現。跨平臺、跨語言方面,Arrow 支持多種語言的微服務架構,並能與現有的大數據環境輕鬆集成。內置的稱為 Arrow Flight 的 RPC 框架使 Arrow 可以輕鬆用標準化且高效的方式共享 / 提供數據集。

零複製數據傳輸

首先,為什麼我們需要序列化呢?在 Java 應用程序中,我們通常要使用對象和原始值。這些對象以某種方式映射到計算機內存中的字節上。JDK 知道如何將對象映射到計算機上的字節。但是這種映射在另一臺計算機上可能會有所不同。字節順序(又稱字節序)就是一個例子。而且,並非所有的編程語言都具有相同的原始類型集,甚至不會以相同的方式存儲相似的類型。

序列化將對象使用的內存轉換為一種通用格式。這種格式有一個規範,並且為每種編程語言和平臺提供了一個庫,該庫能將對象轉換為序列化的格式或轉換回來。換句話說,序列化就是用來共享數據的,而且不會破壞每種編程語言和平臺的特有行為。序列化可消除不同平臺和編程語言中的所有差異,從而讓每位程序員都能按自己喜歡的方式工作。這就像翻譯員可以消除說不同語言的人們之間的語言障礙一樣。

在大多數情況下,序列化是非常好用的。但當我們傳輸大量數據時,它將成為一個很大的瓶頸。那麼在這種情況下,我們可以消除序列化過程嗎?這實際上就是零複製序列化框架(如 Apache Arrow 和 Flat Buffers)的目標。你可以將其視為處理序列化數據本身,而非處理對象,以避免序列化步驟。零複製是指你的應用程序所處理的字節可以無需任何修改就通過網絡傳輸。同樣,在接收端,應用程序可以按原樣處理收到的字節,而無需反序列化步驟。

這裡的最大優勢在於,數據可以無需任何轉換,

按原樣從一個環境傳輸到另一環境,因為連接兩側對數據都是按原樣理解的。

這裡的主要缺陷是失去了編程中的特性(idiosyncrasies)。所有操作都在字節緩衝區上執行。沒有整數,有字節序列。沒有數組,有字節序列。沒有對象,有字節序列的集合。當然,你仍然可以將通用格式的數據轉換為整數、數組和對象。但這樣以來你就要進行反序列化,零複製就失去了意義。一旦傳輸到 Java 對象,則只有 Java 才能處理數據。

Apache Arrow 和 Java:大數據傳輸快如閃電

在實踐中這是如何工作的呢?我們來簡單看一下兩個零複製序列化框架:Apache Arrow 和來自谷歌的 FlatBuffers 。儘管兩者都是零複製框架,但是它們是針對不同用例的不同設計取向。

FlatBuffers 最初是為了支持移動遊戲而開發的。它的重點在於以最小的開銷將數據從服務器快速傳輸到客戶端。你可以發送單個對象或對象集合。數據存儲在(堆上)ByteBuffer 中,格式為 FlatBuffers 通用數據佈局。FlatBuffers 編譯器將根據數據規範生成代碼,從而簡化你與 ByteBuffers 的交互。你可以像處理數組、對象或原語一樣處理數據。在後臺,每個訪問器(accessor)方法都獲取相應的字節並將字節轉換為 JVM 和你的代碼可理解的構造。如果出於某種原因需要訪問字節,你也可以這樣做。

Arrow 與 FlatBuffers 的不同之處在於它們在內存中佈局列表 / 數組 / 表的方式。FlatBuffers 對錶使用一種面向的格式,而 Arrow 使用一種列式格式存儲表格化數據。這就在對大數據集的分析化(OLAP)查詢方面帶來了很大的不同。

Arrow 針對的是大數據系統,在系統中你通常不傳輸單個對象,而是傳輸大量對象。另一方面,FlatBuffers 宣傳自己是序列化框架(也是這麼用的)。換句話說,你的應用程序代碼處理的是 Java 對象和原語,並且只在發送數據時將數據轉換為 FlatBuffers 的內存佈局。如果接收側是隻讀的,則不必將數據反序列化為 Java 對象,可以直接從 FlatBuffers 的 ByteBuffers 中讀取數據。

Apache Arrow 和 Java:大數據傳輸快如閃電

在大型數據集中,行數通常可以從數千行到數萬億行不等。這樣的數據集可能有幾列到數千列。

對此類數據集的典型分析查詢只會引用少量列。以電子商務交易的數據集為例,一位銷售經理想要一個特定區域的銷售概覽,按項目類別分組。他不想看到具體的交易,平均銷售價格就足夠了。這樣的查詢可以通過三個步驟來完成:

  • 遍歷區域列中的所有值,並跟蹤請求區域中所有銷售的行 / 對象 ID
  • 根據項目類別列中的對應值分組過濾後的 ID
  • 計算每個組的彙總

基本上,查詢處理器在任何給定時間只需在內存中有一列即可。通過以列式格式存儲集合,我們可以分別訪問單個字段 / 列的所有值。精心設計的格式可以針對 CPU 的 SIMD 指令優化佈局,進而完成這一操作。對於此類分析負載,Apache Arrow 列式佈局比 FlatBuffers 面向行的佈局更合適。

Apache Arrow

Apache Arrow 的核心是內存中數據佈局格式。除了該格式外,Apache Arrow 還提供了一組庫(包括 C、C++、C#、Go、Java、JavaScript、MATLAB、Python、R、Ruby 和 Rust),以使用 Apache Arrow 格式的數據。本文的剩餘部分會介紹 Arrow 的基本概念,以及如何使用 Apache Arrow 編寫 Java 應用程序。

基本概念

向量 Schema 根

假設我們正在建模一個連鎖店的銷售記錄。一般來說,你會遇到一個代表某次銷售過程的對象。這樣的對象將具有各種屬性,例如

  • 一個 ID
  • 關於這次銷售所在商店的信息,例如地區、城市,也許還有商店的類型
  • 一些客戶信息
  • 所售商品的編號
  • 所售商品的類別(可能是子類別)
  • 賣了多少商品
  • 等等…

在 Java 中,銷售由 Sale 類建模。這個類包含單次銷售的所有信息。所有銷售都通過 Sale 對象的集合(在內存中)表示。從數據庫角度來看,Sale 對象的集合等效於面向行的關係型數據庫。實際上,往往在這樣的應用程序中,對象的集合被映射到數據庫中的 yi ge 4 關係表以實現持久性。

在面向列的數據庫中,對象的集合分解為列的集合。所有 ID 都存儲在單個列中。在內存中,所有 ID 都按順序存儲。同樣,有一列用於存儲各次銷售所在商店的全部所屬城市。從概念上講,這種列式格式可被認為是將一組對象分解為一組等長數組。對象中每個字段對應一個數組。

為了重建某個對象,可在給定索引處選擇每個列 / 數組的值來重組被分解的數組。例如,可以獲取 id 數組的第 10 個值、商店城市數組的第 10 個值等來重組第 10 次銷售。

Apache Arrow 的工作機制類似於面向列的關係型數據庫。Java 對象的集合被分解為列的集合,這些列在 Arrow 中稱為向量。向量是 Arrow 列式格式的基本單位。

所有向量的母親都是 FieldVector。有用於原始類型的向量類型,如 Int4Vector 和 Float8Vector。字符串有一個向量類型:VarCharVector。任意二進制數據都有一個向量類型:VarBinaryVector。有幾種類型的向量是用來建模時間的,如 TimeStampVector、TimeStampSecVector、TimeStampTZVector 和 TimeMicroVector。

這裡還可以組成更復雜的結構。StructVector 用於將一組向量分組為一個字段。例如,考慮上面的銷售示例中的商店信息。所有商店信息(地區、城市和類型)都可以分組在一個 StructVector 中。ListVector 允許在一個字段中存儲一個可變長度的元素列表。MapVector 在一個向量中存儲一個鍵值映射。

繼續拿數據庫做類比,用表表示對象的集合。為了標識表中的值,表有一個 schema:名稱到類型的一個映射。在面向行的數據庫中,每行將一個名稱映射到一個預定義類型的值。在 Java 中,Schema 對應於類定義的成員變量集。面向列的數據庫同樣具有 schema。在表中,schema 中的每個名稱都映射到一個預定義類型的列。

在 Apache Arrow 術語中,向量的集合由VectorSchemaRoot(向量 Schema 根)表示。VectorSchemaRoot 還包含一個 Schema,將名稱(也稱為

字段)映射到列(也稱為向量)。

緩衝區分配器

我們添加到向量中的值存儲在哪裡?Arrow 向量是由緩衝區支持的。一般來說這是一個 java.nio.ByteBuffer。緩衝區在緩衝區分配器中池化。你可以要求緩衝區分配器創建一個特定大小的緩衝區,也可以讓分配器負責緩衝區的創建和自動擴展以存儲新值。分配器跟蹤所有已分配的緩衝區。

一個向量由一個分配器管理。我們說支持向量的緩衝區是分配器所有的。向量所有權可以從一個分配器轉移到另一個上。

例如,你正在實現一個數據流。這個流由一系列處理階段組成。每個階段都會對數據進行一些操作,然後將其傳送到下一階段。每個階段都有自己的緩衝區分配器,用於管理當前正在處理的緩衝區。處理完成後,數據將進入下一個階段。

換句話說,支持向量的緩衝區的所有權被轉移到下一級的緩衝區分配器。現在,這個緩衝區分配器負責管理內存並在不再需要內存時將其釋放。

分配器創建的緩衝區是 DirectByteBuffers(直接字節緩衝),因此它們是堆外存儲的。這意味著數據使用完後必須釋放內存。對 Java 程序員來說,這種感覺一開始會很奇怪。但這是使用 Apache Arrow 的一個要點。向量實現了 AutoCloseable(可自動關閉)接口,因此建議將向量創建包裝在 try-with-resources 塊中,該塊將自動關閉向量,也就是釋放內存。

示例:寫,讀和處理

介紹的最後一部分,我們將具體介紹一個使用 Apache Arrow 的示例應用程序。它的機制是從磁盤上的一個文件中讀取一個人員的“數據庫”,然後過濾和彙總數據,最後打印出結果。

務必注意,Apache Arrow 是內存中格式。在實際應用中,最好使用針對持久存儲優化的其他(列式)格式,例如 Parquet 。Parquet 會對寫入磁盤的數據做壓縮處理並添加中間摘要。因此,從磁盤讀取和寫入 Parquet 文件應該比讀取和寫入 Apache Arrow 文件更快。在此示例中,Arrow 僅用於教學目的。

假設我們有一個 Person 類和一個 Address 類(僅顯示相關部分):

public Person(String firstName, String lastName, int age, Address address) {

this.firstName = firstName;

this.lastName = lastName;

this.age = age;

this.address = address;

}

public Address(String street, int streetNumber, String city, int postalCode) {

this.street = street;

this.streetNumber = streetNumber;

this.city = city;

this.postalCode = postalCode;

}

我們將編寫兩個應用程序。第一個程序將生成一個隨機生成的人員集合,並將其以 Arrow 格式寫入磁盤。接下來我們將編寫一個應用程序,其將“人員數據庫”以 Arrow 格式從磁盤讀取到內存中。選出所有符合以下特徵的人員:

  • 姓氏以“P”開頭
  • 年齡在 18 至 35 歲之間
  • 住在名稱以“way”結尾的街道上

對於選定的人員,我們按每個城市分組並計算各組的平均年齡。看過這個示例後,你應該能對如何使用 Apache Arrow 實施內存中數據分析有一些概念了。

可以在此 Git 存儲庫中找到此示例的代碼。

寫數據

在我們開始寫數據之前。請注意 Arrow 格式針對的是內存中數據。它尚未針對磁盤數據存儲進行優化。在實際的應用程序中,你應該考慮諸如 Parquet 之類的格式,該格式支持壓縮和其他一些技巧以加快磁盤上列式數據的存儲,進而持久保存數據。在這裡,我們將以 Arrow 格式寫出數據,以簡化討論並強調重點。

給定一個 Person 對象數組,我們開始將數據寫到一個名為 people.arrow 的文件中。第一步是將 Person 對象的數組轉換為 Arrow VectorSchemaRoot。如果你確實想充分利用 Arrow,可以讓整個應用程序都使用 Arrow 向量。但是出於教學目的,在此處進行轉換更好些。

<code>private void vectorizePerson(int index, Person person, VectorSchemaRoot schemaRoot) {
    // Using setSafe: it increases the buffer capacity if needed
    ((VarCharVector) schemaRoot.getVector("firstName")).setSafe(index, person.getFirstName().getBytes());
    ((VarCharVector) schemaRoot.getVector("lastName")).setSafe(index, person.getLastName().getBytes());
    ((UInt4Vector) schemaRoot.getVector("age")).setSafe(index, person.getAge());
    List childrenFromFields = schemaRoot.getVector("address").getChildrenFromFields();
    Address address = person.getAddress();
    ((VarCharVector) childrenFromFields.get(0)).setSafe(index, address.getStreet().getBytes());
    ((UInt4Vector) childrenFromFields.get(1)).setSafe(index, address.getStreetNumber());
    ((VarCharVector) childrenFromFields.get(2)).setSafe(index, address.getCity().getBytes());
    ((UInt4Vector) childrenFromFields.get(3)).setSafe(index, address.getPostalCode());
}/<code>

在 vectorizePerson 中,Person 對象通過 person schema 映射到 schemaRoot 中的向量。setSafe 方法確保後備緩衝區足夠大,以容納下一個值。如果後備緩衝區不夠大,則將擴展緩衝區。VectorSchemaRoot 是 schema 和向量集合的容器。這樣,VectorSchemaRoot 類可以被視為一個無模式數據庫,因此只有在對象實例化階段在構造器中傳遞 schema 時,才能知道這個 schema。因此,所有方法(如 getVector)都有非常通用的返回類型,這裡是 FieldVector。結果,需要基於 schema 或數據集的知識進行大量 casting。

在此示例中,我們可以選擇預分配 UInt4Vectors 和 UInt2Vector(因為我們預先知道在一批中有多少人)。然後我們可以使用 set 方法來避免緩衝區大小檢查和重新分配,以擴展緩衝區。

vectorizePerson 函數可以傳遞給一個 ChunkedWriter,ChunkedWriter 是處理分塊並寫入 Arrow 格式化的二進制文件的抽象。

<code>void writeToArrowFile(Person[] people) throws IOException {
   new ChunkedWriter<>(CHUNK_SIZE, this::vectorizePerson).write(new File("people.arrow"), people);
}
The ChunkedWriter has a write method that looks like this:
public void write(File file, Person[] values) throws IOException {
   DictionaryProvider.MapDictionaryProvider dictProvider = new DictionaryProvider.MapDictionaryProvider();
   try (RootAllocator allocator = new RootAllocator();
        VectorSchemaRoot schemaRoot = VectorSchemaRoot.create(personSchema(), allocator);
        FileOutputStream fd = new FileOutputStream(file);
        ArrowFileWriter fileWriter = new ArrowFileWriter(schemaRoot, dictProvider, fd.getChannel())) {
       fileWriter.start();
       int index = 0;
       while (index < values.length) {
           schemaRoot.allocateNew();
           int chunkIndex = 0;
           while (chunkIndex < chunkSize && index + chunkIndex < values.length) {
               vectorizer.vectorize(values[index + chunkIndex], chunkIndex, schemaRoot);
               chunkIndex++;
           }
           schemaRoot.setRowCount(chunkIndex);
           fileWriter.writeBatch();
           index += chunkIndex;
           schemaRoot.clear();
       }
       fileWriter.end();
   }
}
/<code>

分別解釋一下。首先,我們創建一個(i)分配器、(ii)schemaRoot 和(iii)dictProvider。我們需要它們(i)分配內存緩衝區,(ii)是向量的容器(由緩衝區支持),以及(iii)進行字典壓縮(你現在可以忽略它)。接下來,在(2)中創建一個 ArrowFileWriter。它基於一個 VectorSchemaRoot 處理寫入磁盤的操作。用這種方式批量寫出數據集非常容易。最後一個要點是,不要忘記啟動寫入器(writer)。

該方法的其餘部分則將 Person 數組按塊向量化到向量 schema 根中,然後逐批寫出。

分批寫入有什麼好處?在某些時候將從磁盤讀取數據。如果將數據一次寫入,則必須一次讀取所有數據並將其存儲在主內存中。通過分批寫入,我們允許讀取器(reader)以較小的塊處理數據,從而控制了內存佔用。

一定不要忘記設置向量的值計數或向量 schema 根的行計數(這會間接設置所有包含的向量的值計數)。如果不設置計數,即使將值存儲在向量中,向量也將顯示為空。

最後,當所有數據都存儲在向量中時,fileWriter.writeBatch() 會將它們提交到磁盤。

有關內存管理的說明

請注意第(3)和(4)行上的 schemaRoot.clear() 和 allocator.close()。前者清除 VectorSchemaRoot 中包含的所有向量中的所有數據,並將行和值計數重置為零。後者關閉分配器。如果你忘記釋放所有分配的緩衝區,則此調用將通知你出現了一個內存洩漏。

在這裡的設置下關閉有點多餘,因為程序在分配器關閉之後不久就會退出。但在真實的,長期運行的應用程序中,內存管理至關重要。

Java 程序員並不習慣關注內存管理。但在這種情況下,這是為性能付出的代價。要特別注意分配的緩衝區,並在其生命週期結束時釋放它們。

讀取數據

從 Arrow 格式的文件中讀取數據類似於寫入。你設置了一個分配器、一個向量 schema 根(沒有 schema,它是文件的一部分),打開了一個文件,然後讓 ArrowFileReader 負責其餘的工作。不要忘記初始化,因為這將從文件中讀取 Schema。

要讀取一個 batch,請調用 fileReader.loadNextBatch()。下一批(如果仍然可用)會從磁盤讀取,並且 schemaRoot 中的向量緩衝區將用數據填充,準備處理。

以下代碼段簡要描述瞭如何讀取一個 Arrow 文件。while 循環的每次執行都會將一個批處理加載到 VectorSchemaRoot 中。批處理的內容由 VectorSchemaRoot 描述:(i)VectorSchemaRoot 的架構,和(ii)值計數,等於條目數。

<code>try (FileInputStream fd = new FileInputStream("people.arrow");
    ArrowFileReader fileReader = new ArrowFileReader(new SeekableReadChannel(fd.getChannel()), allocator)) {
   // Setup file reader
   fileReader.initialize();
   VectorSchemaRoot schemaRoot = fileReader.getVectorSchemaRoot();
   // Aggregate: Using ByteString as it is faster than creating a String from a byte[]
   while (fileReader.loadNextBatch()) {
      // Processing … 
   }
}
/<code>

處理數據

最後的要點是過濾,分組和彙總步驟,通過它們你應該能大致瞭解如何在數據分析軟件中使用 Arrow 向量。我並不是說這就是使用 Arrow 向量的方法,但它應該為探索 Apache Arrow 的過程提供一個穩固的起點。在這裡查看用於實際 Arrow 代碼的 Gandiva 處理引擎的源代碼。使用 Apache Arrow 處理數據是一個很大的話題。你甚至可以為此寫一本書。

請注意,示例代碼是特別針對 Person 用例的。在其他情況下,比如構建一個 Arrow 向量的查詢處理器時,我們事先無法知道這些向量的名稱和類型,這就會帶來更通用,更難理解的代碼。

由於 Arrow 是一種列式格式,因此我們可以只使用一列就獨立應用過濾步驟。

<code>private IntArrayList filterOnAge(VectorSchemaRoot schemaRoot) {
    UInt4Vector age = (UInt4Vector) schemaRoot.getVector("age");
    IntArrayList ageSelectedIndexes = new IntArrayList();
    for (int i = 0; i < schemaRoot.getRowCount(); i++) {
        int currentAge = age.get(i);
        if (18 <= currentAge && currentAge <= 35) {
            ageSelectedIndexes.add(i);
        }
    }
    ageSelectedIndexes.trim();
    return ageSelectedIndexes;
}
/<code>

此方法收集年齡向量的加載塊中,全部值在 18 到 35 之間的索引。每個過濾器都會生成一個此類索引的排序列表。在下一步中,我們將這些列表相交 / 合併到選定索引的單個列表中。該列表包含滿足所有條件的行的所有索引。

下一個代碼片段顯示瞭如何從向量和選定 ID 的集合中輕鬆填充彙總數據結構(將城市映射為一個計數和一個總和)。


<code>VarCharVector cityVector = (VarCharVector) ((StructVector) schemaRoot.getVector("address")).getChild("city");
UInt4Vector ageDataVector = (UInt4Vector) schemaRoot.getVector("age");
for (int selectedIndex : selectedIndexes) {
   String city = new String(cityVector.get(selectedIndex));
   perCityCount.put(city, perCityCount.getOrDefault(city, 0L) + 1);
   perCitySum.put(city, perCitySum.getOrDefault(city, 0L) + ageDataVector.get(selectedIndex));
}
/<code>

填充彙總數據結構後,很容易打印出每個城市的平均年齡:


<code>for (String city : perCityCount.keySet()) {
    double average = (double) perCitySum.get(city) / perCityCount.get(city);
    LOGGER.info("City = {}; Average = {}", city, average);
}
/<code>

總結

本文介紹了 Apache Arrow,這是一種列式,內存中,跨語言的數據佈局格式。它是大數據系統的基礎,主要關注群集中機器之間以及不同大數據系統之間的高效數據傳輸。為了使用 Apache Arrow 開發 Java 應用程序,我們看了兩個示例應用程序,它們以 Arrow 格式寫入和讀取數據。我們還試著使用了 Apache Arrow Java 庫來處理數據。

Apache Arrow 是一種列式格式。面向列的佈局通常比面向行的佈局更適合分析負載。但權衡取捨總是免不了的。對於你的特定負載而言,面向行的格式可能會帶來更好的結果。

VectorSchemaRoots,緩衝區和內存管理用起來並不像你慣用的那些 Java 代碼。如果你可以從其他框架(例如 FlatBuffers)中獲得所需的全部性能,那麼在你決定是否在應用程序中採用 Apache Arrow 時,這種不常用的工作方式可能就要考慮進來了。


分享到:


相關文章: