企業自有資料格式雜亂,MapReduce如何搞定?

趙鈺瑩發表於2018-09-26

本文作為《Hadoop從入門到精通》大型專題第三章的最後一篇文章,主要介紹了SequenceFile和Avro之外的其它資料格式,以及與MapReduce的相容性,並介紹了企業常用的自定義資料格式或CSV格式如何作為MapReduce作業輸入等內容。

3.4 柱狀儲存

當資料寫入I/O裝置(比如檔案或關聯式資料庫中的表)時,佈局該資料的常見方式是基於行,這意味著第一行的所有欄位將首先被寫入,緊接著是第二行的所有欄位,依此類推。這是大多數關係型資料庫預設的匯出表方式,對於大多資料序列化格式(如XML,JSON和Avro容器檔案)也是如此。

列式儲存的工作方式則有些不同,其首先按列對資料進行排序,然後按行排列。首先寫入所有記錄第一個欄位的值,然後是第二個欄位,依此類推。圖3.12顯示了兩種儲存方案在資料佈局方式上的差異。

圖3.12 行和列儲存系統資料佈局方式

以柱狀形式儲存資料有兩大主要好處:

  • 讀取列式資料的系統可以有效提取列的子集,從而減少I/O。基於行的系統通常需要讀取整行,即使只需要一列或兩列。

  • 在編寫柱狀資料(例如run-length編碼和位打包)時可以進行優化,以有效壓縮正在寫入的資料大小。通用壓縮方案適用於壓縮列式資料,因為壓縮最適用於具有大量重複資料的檔案。

因此,在處理有過濾或投影需求的大型資料集時,列式檔案格式最有效,這正是OLAP型別用例以及MapReduce中常用的方式。

Hadoop中使用的大多資料格式(如JSON和Avro)都是有序的,這意味著在讀取和寫入這些檔案時無法應用前面提到的優化。想象一下,圖3.12中的資料位於Hive表中,你將執行以下查詢:

如果資料是以基於行的格式佈局,則必須讀取每一行,即使只需要讀取價格列。在面向列的表格中,只會讀取價格列,這可能會導致處理大型資料集時大幅縮短處理時間。在Hadoop中可以使用許多列式儲存選項:

  • RCFile是Hadoop中第一個可用的柱狀格式,是Facebook和學術界在2009年合作研發的。RCFile是一個基本的柱狀格式,支援單獨的列儲存、列壓縮和讀取期間投影,但是沒有其他更高階的技術,例如run-length編碼(遊標編碼或行程編碼)。 因此,Facebook從RCFile轉移到ORC檔案。

  • ORC檔案由Facebook和Hortonworks建立,以解決RCFile的缺點,與RCFile相比,它的序列化優化可以產生更小的資料大小,使用索引啟用謂詞下推以優化查詢,過濾以跳過與列不匹配的謂詞。ORC檔案與Hive型別系統完全整合,可以支援巢狀結構。

  • Parquet是Twitter和Cloudera合作開發的,它採用ORC檔案用於生成壓縮檔案。Parquet是一種與語言無關的格式,具有正式的使用規範。

RCFile和ORC檔案旨在主要支援Hive,而Parquet獨立於所有Hadoop工具,並嘗試最大化與Hadoop生態系統相容。表3.2顯示了這些格式如何與各種工具和語言整合。

表3.2 Hadoop支援的列式儲存格式

Elephant Bird提供了用RCFile使用Thrift和Protocol Buffers的能力。本節,由於考慮到與前一章節提及的Avro等物件模型的相容性,我將專注於介紹Parquet。

3.4.1 瞭解物件模型和儲存格式

在開始使用之前,我將介紹一些Parquet的概念,這些對於理解Parquet和Avro(以及Thrift和Protocol Buffers)之間的相互作用非常重要:

  • 物件模型是資料的記憶體表示。Parquet公開了一個簡單的物件模型,它可以作為示例參考也僅此而已。Avro,Thrift和Protocol Buffers是功能齊全的物件模型。一個例子是Avro Stock類,它由Avro生成,使用Java POJO對模式進行豐富建模。

  • 儲存格式是資料模型的序列化表示。Parquet是一種以列為導向的序列化資料儲存格式。Avro,Thrift和Protocol Buffers都是以行為導向的序列化資料儲存格式。

  • Parquet物件模型轉換器負責將物件模型轉換為Parquet資料型別,反之亦然。 Parquet與許多轉換器捆綁在一起,以最大限度提高Parquet的互操作性和採用率。

圖3.13 Parquet儲存格式和物件模型轉換器

Parquet的獨特之處在於支援常見物件模型(如Avro)的轉換器。資料以Parquet二進位制形式儲存,但是當處理資料時,使用者依然可以選擇首選物件模型,例如Avro。這種方式的好處在於:使用者可繼續使用Avro等豐富的物件模型與資料互動,並使用Parquet將資料有效地放置在磁碟上。

儲存格式互操作性

儲存格式通常不可互操作,當將Avro和Parquet結合在一起時,就相當於結合了Avro物件模型和Parquet儲存格式。因此,如果現有的Avro資料位於使用Avro儲存格式序列化的HDFS中,則無法使用Parquet儲存格式讀取,因為這是兩種截然不同的資料編碼方式。 反之亦然——使用普通的Avro方法(例如MapReduce中的AvroInputFormat)無法讀取Parquet,必須使用Parquet輸入格式實現和Hive SerDes來處理Parquet資料。

總而言之,如果希望以列形式序列化資料,請選擇Parquet。選擇Parquet後,需要確定要使用的物件模型。建議選擇企業中使用最多的物件模型,如果不清楚則建議使用Avro(前一章節介紹了Avro的優勢)。

3.4.2 Parquet 和Hadoop生態系統

Parquet的目標是最大限度支援整個Hadoop生態系統。,目前已支援MapReduce,Hive,Pig,Impala和Spark,我們希望未來能看到起得到其他系統的支援,比如Sqoop。

由於Parquet是標準檔案格式,因此任何一種技術都可讀取編寫好的Parquet檔案。 最大限度地提高對Hadoop生態系統的支援對於檔案格式的成功至關重要,而Parquet有望成為大資料中無處不在的檔案格式。

此外,Parquet沒有專注於特定的技術子集——用Parquet官網的話來說,“在生態系統支援方面,我們對玩遊戲不感興趣”(http://parquet.io)。 這意味著該專案的主要目標是最大限度支援可能使用的工具,這一點非常重要,畢竟技術雷達上會不斷出現新的工具。

3.4.3 Parquet block和page sizes

圖3.14 顯示了Parquet檔案格式的高階表示,並突出了關鍵概念。

通過命令列讀取Parquet檔案

Parquet是一種二進位制儲存格式,因此使用標準的hadoop fs -cat命令會在命令列產生垃圾。在這種技術中,我們將探索如何使用命令列檢視Parquet檔案內容,並檢查Parquet檔案中包含的模式和其他後設資料。

問題

希望使用命令列來檢查Parquet檔案的內容。

解決方案

使用Parquet工具捆綁的實用程式。

討論

Parquet與工具JAR捆綁,其中包含一些有用的實用程式,可以將Parquet檔案中的資訊轉儲到標準輸出。

在開始之前,需要建立一個Parquet檔案以便測試這些工具。以下示例通過編寫Avro記錄來建立Parquet檔案:

使用的第一個Parquet工具是cat,它將Parquet檔案資料簡單轉儲到標準輸出:

我們可以在前面的示例中使用Parquet head命令而不是cat來僅發出前五個記錄,dump命令允許指定應該轉儲的列子集,儘管輸出不可讀。

Parquet有自己的內部資料型別和模式,由轉換器對映到外部物件模型,我們可以使用schema選項檢視內部Parquet架構:

Parquet允許物件模型使用後設資料來儲存反序列化所需的資訊。例如,Avro使用後設資料來儲存Avro架構,如下命令輸出所示:

接下來讓我們看看如何編寫和閱讀Parquet檔案。

使用Java在Parquet中讀取和編寫Avro資料

使用新檔案格式時,首先要做的事情之一是瞭解獨立的Java應用程式如何讀取和寫入資料。 此技術顯示瞭如何將Avro資料寫入Parquet檔案並將其返回。

問題

希望使用Avro物件模型直接從Hadoop之外的Java程式碼讀取和寫入Parquet資料。

解決方案

使用AvroParquetWriter和AvroParquetReader類。

討論

Parquet是Hadoop的柱狀儲存格式,其支援Avro並允許使用Avro類處理資料,使用Parquet檔案格式可有效對資料編碼,以便利用資料列式佈局。

Parquet是一種儲存格式,具有正式的程式語言無關規範,可以直接使用Parquet而不需要其他任何資料格式,例如Avro,但Parquet的核心是簡單資料格式,不支援複雜型別,如map或union,這就是Avro發揮作用的地方。Avro支援更豐富的型別以及程式碼生成和模式演變等功能。因此,將Parquet與Avro等豐富的資料格式結合在一起,可以完美匹配複雜的模式功能以及高效的資料編碼。

對於此技術,我們將繼續使用Avro Stock架構。首先,讓我們看看如何使用這些Stock物件編寫Parquet檔案。

以下命令通過執行前面的程式碼生成Parquet檔案:

之前的技術展示瞭如何使用Parquet工具將檔案轉儲到標準輸出。但是,如果想用Java讀取檔案怎麼辦?

以下命令顯示上述程式碼輸出:

Parquet和MapReduce

此技術檢查如何在MapReduce中使用Parquet檔案。我將介紹使用Parquet作為資料來源以及MapReduce中的資料接收器。

問題

希望使用在MapReduce中序列化為Parquet的Avro資料。

解決方案

使用AvroParquetInputFormat和AvroParquetOutputFormat類。

討論

Parquet中的Avro子專案帶有MapReduce輸入和輸出格式,可讓你使用Parquet作為儲存格式讀取和寫入Avro資料。以下示例是計算每個symbol的平均stock價格:

在Parquet中使用Avro非常簡單,並且比使用Avro序列化資料更容易,可以執行以下示例:

Parquet附帶了一些工具來幫助使用Parquet檔案,其中一個允許將內容轉儲到標準輸出:

你可能已經注意到輸出目錄中有一個名為_metadata的附加檔案。當Parquet OutputComitter在作業完成時執行,它會讀取所有輸出檔案(包含檔案後設資料)的頁尾並生成此彙總的後設資料檔案。之後,MapReduce(或Pig / Hive)作業使用此檔案將減少作業啟動時間。

總結

如果不想使用這種方式,則可以使用一些其他選項來使用Avro資料,比如Avro的GenericData類:

  • 如果使用GenericData物件編寫Avro資料,那麼這就是Avro將其提供給mapper的格式。

  • 排除包含Avro生成程式碼的JAR也會導致GenericData物件被送到mapper。

  • 可以通過改變輸入模式來使Avro無法載入特定類,從而強制它提供GenericData例項。

以下程式碼顯示瞭如何執行第三個選項——基本上採用原始模式並複製,但在此過程中,因為提供了一個不同的類名,Avro將無法載入(參見“foobar” 在第一行):

如果想在本地使用Parquet資料怎麼辦?Parquet附帶了一個示例物件模型,允許使用任何Parquet資料,而不管用於寫入資料的物件模型,使用Group類來表示記錄,並提供一些基本的getter和setter來檢索欄位。

以下程式碼再次顯示瞭如何計算stock平均值。輸入是Avro / Parquet資料,輸出是全新的Parquet架構:

示例物件模型非常基礎,目前缺少一些功能——例如,沒有雙型別的getter,這就是之前的程式碼使用getValueToString方法訪問stock值的原因,但是正在努力提供更好的物件模型,包括POJO介面卡。

Parquet,Hive和Impala

在Hive和Impala中使用時,Parquet自成一體。柱狀儲存因其能夠使用下推來優化讀取路徑而自然適合這些系統,該技術顯示了Parquet如何在這些系統中使用。

問題

希望能夠在Hive和Impala中使用Parquet資料。

解決方案

使用Hive和Impala對Parquet的內建支援。

討論

Hive要求資料存在於目錄中,因此首先需要建立一個目錄並將庫存Parquet檔案複製到其中:

接下來,建立一個外部Hive表並定義架構。如果不確定模式結構,請檢視正在使用的Parquet檔案模式資訊(使用Parquet工具中的模式命令):

自Hive 0.13就新增了對Parquet作為本機Hive儲存的支援(請參閱https://issues.apache.org/jira/browse/HIVE-5783)。如果使用的是較舊版本的Hive,則需要使用ADD JAR命令手動載入所有Parquet JAR並使用Parquet輸入和輸出格式。

可以執行簡單查詢以從資料中提取唯一的stock程式碼:

可以使用相同的語法在Impala中建立表。

用Parquet進行謂詞下推和投影

二者需要執行引擎推向儲存格式以優化可能的低階別操作。這產生了空間和時間優勢,因為不需要獲取不需要的列並將其提供給執行引擎。

這對於列式儲存尤其有用,因為下推允許儲存格式跳過查詢時不需要的列,並且列式格式可以非常有效地執行此操作。

問題

希望在Hadoop中使用謂詞下推來優化查詢

解決方案

將Hive、Pig與Parquet結合使用可提供開箱即用的投影和下推功能。使用MapReduce,則需要在驅動程式程式碼中採用一些手動步驟來啟用下推。

討論

AvroParquetInputFormat有兩種方法可用於謂詞下推,在以下示例中,僅投影Stock物件的兩個欄位,並新增謂詞以便僅選擇Google stock:

當提供的謂詞過濾掉記錄時,就會向mapper提供空值。這就是為什麼在使用mapper輸入之前必須檢查null。

如果執行該作業並檢查輸出,只能找到谷歌stock的平均值,證明該謂詞有效:

總結

該技術不包括任何Hive或Pig下推細節,因為二者都可自動執行下推。如果你的Parquet使用的第三方庫或工具沒有下推工具,可以請求社群幫助。

3.4.4 Parquet限制

使用Parquet時,需要注意以下幾點:

  • Parquet在寫入檔案時需要大量記憶體,因為會緩衝記憶體中的寫入以優化資料編碼和壓縮。如果編寫Parquet檔案時遇到記憶體問題,可以增加堆大小(建議2GB),或者減少parquet.block.size配置。

  • 對Parquet使用重度巢狀的資料結構可能會限制Parquet下推優化。 如果可能,請嘗試展平架構。

  • Impala不支援Parquet中的巢狀資料或複雜資料型別(如map,struct或者array),這在Impala 2.x版本中修復。

  • 當Parquet檔案包含單個行組以及整個檔案適合HDFS塊時,Impala等工具的效果最佳。 實際上,當你在MapReduce等系統中編寫Parquet檔案時很難實現這個目標,但是在製作Parquet檔案時要記住這一點。

我們已經介紹了使用常見檔案格式以及各種資料序列化工具與MapReduce實現緊密相容。現在是時候看看如何支援企業專有檔案格式,甚至是沒有MapReduce輸入或輸出的檔案格式。

3.5自定義檔案格式

在任何企業,我們通常都會發現過多的自定義或不常見的檔案格式,可能有後端伺服器以專有格式轉儲審計檔案,或舊程式碼或使用不常用的格式寫入檔案的系統。如果想在MapReduce中使用此類資料,則需要編寫自己的輸入和輸出格式類來處理資料。

3.5.1輸入和輸出格式

在本章開頭,我們就瞭解了MapReduce中輸入和輸出格式類的功能。輸入和輸出類需要將資料提供給map函式並寫入reduce函式輸出。

編寫CSV輸入和輸出格式

想象一下,如果你在CSV檔案中有大量資料,並且正在編寫多個MapReduce作業,這些作業以CSV格式讀寫資料。由於CSV是文字,因此可以使用內建的TextInputFormat和TextOutputFormat,並在MapReduce程式碼中解析CSV。但是,這很快就會變得很累,並導致在所有作業中複製和貼上相同的解析程式碼。

問題

希望在MapReduce中使用CSV並以更豐富的格式呈現CSV記錄,如果使用的TextInputFormat將提供表示行的字串。

解決方案

編寫適用於CSV的輸入和輸出格式。

討論

我們將介紹編寫自己的格式類以使用CSV輸入和輸出所需的步驟。CSV是可以使用的更簡單的檔案格式之一,這將使您更容易專注於MapReduce細節,而無需過多考慮檔案格式。

自定義InputFormat和RecordReader類將解析CSV檔案,並以使用者友好的格式將資料提供給mapper,其支援非逗號分隔符的自定義欄位分隔符。如果不想重新發明輪子,你可以在開源OpenCSV專案(http:// opencsv.sourceforge.net/)中使用CSV解析器,這將處理引用的欄位並忽略引用的分隔符。

InputFormat

第一步是定義InputFormat。InputFormat的功能是驗證提供給作業的輸入集,識別輸入拆分,並建立RecordReader類以讀取源的輸入。以下程式碼從作業配置中讀取分隔符(如果提供)並構造CSVRecordReader:

當壓縮時,可以看到返回一個標誌以指示它無法拆分。這樣做的原因是除了LZOP之外,壓縮編解碼器不可拆分。但是,可拆分LZOP不能與常規的InputFormat類一起使用,它需要特殊的LZOP InputFormat類。

至此,InputFormat類已完成,我擴充套件了FileInputFormat類,該類包含計算沿HDFS塊邊界的輸入拆分程式碼,使不必自己計算輸入拆分。FileInputFormat管理所有輸入檔案和拆分。

RecordReader執行了兩個主要功能。首先,它必須根據提供的輸入拆分開啟輸入源,並且可選地在該輸入拆分中尋找特定的偏移量。RecordReader的第二個功能是從輸入源讀取各個記錄。

在此示例中,邏輯記錄等同於CSV檔案的一行,因此將使用MapReduce中的現有LineRecordReader類處理檔案。當使用InputSplit初始化RecordReader時,它將開啟輸入檔案,尋找輸入拆分的開始,並繼續讀取字元,直到到達下一條記錄的開頭,如果是一行,則表示換行符。以下程式碼顯示了LineRecordReader.initialize方法的簡化版本:

LineRecordReader以LongWritable / Text形式返回每一行的鍵/值對。因為需要在Record Reader中提供某些功能,所以需要在類中封裝LineRecordReader。RecordReader需要向mapper提供記錄的key/value對錶示,在這種情況下,key是檔案中的位元組偏移量,value是包含CSV行的標記化部分陣列:

接下來,需要提供讀取下一條記錄的方法,並獲取該記錄的key和value:

此時,我已經建立了一個可以使用CSV檔案的InputFormat和RecordReader,是時候轉到OutputFormat了。

OutputFormat

OutputFormat類遵循類似於InputFormat類的模式,OutputFormat類處理建立輸出流邏輯,然後將流寫入委託給RecordWriter。

CSVOutputFormat間接擴充套件了FileOutputFormat類(通過TextOutputFormat),處理與建立輸出檔名相關的所有邏輯,建立壓縮編解碼器的例項(如果啟用了壓縮),以及輸出提交。

這使得OutputFormat類的任務是支援CSV輸出檔案的自定義欄位分隔符,並在需要時建立壓縮的OutputStream。它還必須返回CSVRecordWriter,因為它會將CSV行寫入輸出流:


在以下程式碼中,RecordWriter將reducer發出的每條記錄寫入目標輸出。我需要reducer輸出鍵以陣列形式表示CSV行中的每個標記,並指定reducer輸出值必須是NullWritable,這意味著不關心輸出的值部分。

我們來看看CSVRecordWriter類,排除僅設定欄位分隔符和輸出流的建構函式,如下所示。

程式碼3.6 以CSV格式生成MapReduce輸出的RecordWriter

現在,需要在MapReduce作業中應用新的輸入和輸出格式類。

MapReduce

如果MapReduce作業使用CSV作為輸入,將生成以冒號分隔的CSV,而不是逗號。該作業將執行有身份標識的map和reduce功能,這意味著不會在通過MapReduce時更改資料。輸入檔案將使用製表符分隔,輸出檔案將以逗號分隔。輸入和輸出格式類將通過Hadoop配置屬性支援自定義分隔符的概念。

MapReduce程式碼如下:

map和reduce函式除了將輸入回顯到輸出之外沒有其他功能,但包含它們以便可以在MapReduce程式碼中看到如何使用CSV:

如果針對製表符分隔檔案執行此示例MapReduce作業,則可以檢查mapper輸出並檢視結果是否符合預期:

至此,我擁有了一個可以在MapReduce中使用和生成CSV的函式InputFormat和OutputFormat。

Pig

Pig的piggybank庫包含一個CSVLoader,可用於將CSV檔案載入到元組中,支援CSV記錄中的雙引號欄位,並將每個專案作為位元組陣列提供。

有一個名為csv-serde的GitHub專案,具備可序列化和反序列化CSV的Hive SerDe。與之前的InputFormat示例一樣,它也使用OpenCSV專案來讀取和寫入CSV。

總結

這種技術演示瞭如何編寫自己的MapReduce格式類來處理基於文字的資料。當然,國內也有不少工具可以滿足這一要求。可以說,使用TextInputFormat並在mapper中拆分會更簡單。 如果需要多次這樣做,可能會遇到複製貼上反模式,因為用於標記CSV的相同程式碼可能存在於多個位置。如果程式碼是在編寫程式碼重用的情況下編寫的,那麼將受到保護。

MapReduce中的大多數OutputFormats使用FileOutputFormat,使用FileOutputCommitter進行輸出提交。當查詢FileOutputFormat有關輸出檔案的位置時,它會將輸出所在位置的決定委託給FileOutputCommitter,而FileOutputCommitter又指定輸出應該轉到作業輸出目錄下的臨時目錄(<job -output> / _temporary / <task-attempt-id>)。只有在整個任務完成後才會通知FileOutputCommitter,此時臨時輸出將移動到作業輸出目錄。當整個作業成功完成後,將再次通知FileOutputCommitter,這次它會觸及作業輸出目錄中的_SUCCESS檔案,以幫助下游處理器知道作業成功。

如果資料接收器是HDFS,則可以使用FileOutputFormat及其提交機制。當使用除檔案之外的資料來源(例如資料庫)時,事情開始變得棘手,這需要包括將資料從Hadoop匯出到資料庫,這將在之後的章節中討論。

3.6 總結

本章展示瞭如何在MapReduce中使用常見的檔案格式,如XML和JSON;研究了更復雜的檔案格式,如SequenceFile,Avro和Parquet,它們提供了處理大資料的有用功能,例如版本控制,壓縮和複雜資料結構;介紹了使用自定義檔案格式的過程,以確保其可以在MapReduce中工作。

至此,我們掌握了在MapReduce中使用任何檔案格式。第四章將著重介紹儲存模式,以幫助你有效地處理資料並優化儲存和磁碟或網路I/O。

相關文章:

1、《第一章:Hadoop生態系統及執行MapReduce任務介紹!》連結:     http://blog.itpub.net/31077337/viewspace-2213549/

2、《學習Hadoop生態第一步:Yarn基本原理和資源排程解析!》連結:     http://blog.itpub.net/31077337/viewspace-2213602/

3、《MapReduce如何作為Yarn應用程式執行?》連結:     http://blog.itpub.net/31077337/viewspace-2213676/

4、《Hadoop生態系統各元件與Yarn的相容性如何?》連結:     http://blog.itpub.net/31077337/viewspace-2213960/

5、《MapReduce資料序列化讀寫概念淺析!》連結:    http://blog.itpub.net/31077337/viewspace-2214151/

6、《MapReuce中對大資料處理最合適的資料格式是什麼?》連結:   http://blog.itpub.net/31077337/viewspace-2214325/

7、《如何在MapReduce中使用SequenceFile資料格式?》連結:  http://blog.itpub.net/31077337/viewspace-2214505/

8、《如何在MapReduce中使用Avro資料格式?》連結: http://blog.itpub.net/31077337/viewspace-2214709/

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31077337/viewspace-2214826/,如需轉載,請註明出處,否則將追究法律責任。

相關文章