Spark由淺到深(4) -- 數據讀取與保存

文本數據讀取 → Spark操作 → 處理後數據寫入


0. 目的

目前爲止,所展示的示例都是從本地集合或者普通文件中進行數據讀取和保存的。但有時候,數據量可能大到無法放在一臺機器中,這時就需要探索別的數據讀取和保存的方法了。

Spark 支持很多種輸入輸出源

三類常見的數據源:

  • 文件格式與文件系統.
    • 本地文件系統或分佈式文件系統(比如 NFS、 HDFS、 Amazon S3 等)中的數據;
    • 文件格式,包括文本文件、 JSON、 SequenceFile,以及 protocol buffer。
  • Spark SQL中的結構化數據源.
  • 數據庫與鍵值存儲.

Part 1 文件格式

Spark 對很多種文件格式的讀取和保存方式都很簡單。

從諸如文本文件的非結構化的文件,到諸如 JSON 格式的半結構化的文件,再到諸如 SequenceFile 這樣的結構化的文件,Spark都可以支持.

格式名稱 結構化 備註
文本文件 普通的文本文件,每行一條記錄
JSON 半結構化 常見的基於文本的格式,半結構化;大多數庫都要求每行一條記錄
CSV 非常常見的基於文本的格式,通常在電子表格應用中使用
SequenceFiles 一種用於鍵值對數據的常見 Hadoop 文件格式
Protocol buffers 一種快速、節約空間的跨語言格式
對象文件 用來將 Spark 作業中的數據存儲下來以讓共享的代碼讀取。改變類的時候它會失效,因爲它依賴於 Java 序列化**

1.1 文件格式

  • 單個文本:
    一個文本文件讀取爲 RDD 時,輸入的每一行都會成爲 RDD 的一個元素。

  • 多個文本:
    將多個完整的文本文件一次性讀取爲一個 pair RDD,其中鍵是文件名,值是文件內容。

1. 讀取文本文件

只需要使用文件路徑作爲參數調用 SparkContext 中的 textFile() 函數,就可以讀取一個文本文件.

如果要控制分區數的話,可以指定 minPartitions。

//  Python 中讀取一個文本文件
input = sc.textFile("file:///home/holden/repos/spark/README.md")

如果多個輸入文件以一個包含數據所有部分的目錄的形式出現,可以用兩種方式來處理。

  • 仍使用 textFile 函數,傳遞目錄作爲參數,這樣它會把各部分都讀取到 RDD中;

  • 如果文件足夠小,那麼可以使用 SparkContext.wholeTextFiles() 方法,該方法會返回一個 pair RDD,其中鍵是輸入文件的文件名。

wholeTextFiles() 在每個文件表示一個特定時間段內的數據時非常有用。如果有表示不同階段銷售數據的文件,則可以很容易地求出每個階段的平均值.

Spark 支持讀取給定目錄中的所有文件,以及在輸入路徑中使用通配字符(如 part-*.txt)。大規模數據集通常存放在多個文件中,因此這一特性很有用,尤其是在同一目錄中存在一些別的文件(比如成功標記文件)的時候。
2. 保存文本文件
  • saveAsTextFile() 方法接收一個路徑,並將RDD 中的內容都輸入到路徑對應的文件中.
  • Spark 將傳入的路徑作爲目錄對待,會在那個目錄下輸出多個文件。這樣, Spark 就可以從多個節點上並行輸出
// Python 中將數據保存爲文本文件
result.saveAsTextFile(outputFile)

1.2 JSON

方法: 讀取 JSON 數據的最簡單的方式是將數據作爲文本文件讀取, 然後使用 JSON 解析器來對 RDD 中的值進行映射操作。也可以使用我們喜歡的 JSON 序列化庫來將數據轉爲字符串,然後將其寫出去。

1. 讀取JSON

Python 中使用的是內建的庫(https://docs.python.org/2/library/json.html )

// Python 中讀取非結構化的 JSON
import json
data = input.map(lambda x: json.loads(x))
2. 保存JSON
 // Python 保存爲 JSON
(data.filter(lambda x: x["lovesPandas"]).map(lambda x: json.dumps(x)).saveAsTextFile(outputFile))

1.3 逗號分隔值與製表符分隔值

對象: 逗號分隔值(CSV)文件
屬性:

  • 每行都有固定數目的字段,字段間用逗號隔開(在製表符分隔值
    文件,即 TSV 文件中用製表符隔開)。
  • 記錄通常是一行一條,不過也不總是這樣,有時也可以跨行。
  • CSV 文件和 TSV 文件有時支持的標準並不一致,主要是在處理換行符、轉義字符、非 ASCII 字符、非整數值等方面。
  • CSV 原生並不支持嵌套字段,所以需要手動組合和分解特定的字段。

方法: 與 JSON 中的字段不一樣的是,這裏的每條記錄都沒有相關聯的字段名,只能得到對應的序號。常規做法是使用第一行中每列的值作爲字段名。

1. 讀取CSV

讀取 CSV/TSV 數據和讀取 JSON 數據相似,都需要先把文件當作普通文本文件來讀取數據,再對數據進行處理。 由於格式標準的缺失,同一個庫的不同版本有時也會用不同的方式處理輸入數據.

JSON 一樣, CSV 也有很多不同的庫,但是隻在每種語言中使用一個庫.

// Python 中使用 textFile() 讀取 CSV
import csv
import StringIO

...
def loadRecord(line):
    """解析一行CSV記錄"""
    input = StringIO.StringIO(line)
    reader = csv.DictReader(input, fieldnames=["name", "favouriteAnimal"])
    return reader.next()
    input = sc.textFile(inputFile).map(loadRecord)

如果在字段中嵌有換行符,就需要完整讀入每個文件,然後解析各段.

// Python 中完整讀取 CSV
def loadRecords(fileNameContents):
    """讀取給定文件中的所有記錄"""
    input = StringIO.StringIO(fileNameContents[1])
    reader = csv.DictReader(input, fieldnames=["name",  "favoriteAnimal"])
    return reader
    fullFileData = sc.wholeTextFiles(inputFile).flatMap(loadRecords)
2. 保存CSV

由於在 CSV 中我們不會在每條記錄中輸出字段名,因此爲了使輸出保持一致,需要創建一種映射關係。

一種簡單做法是寫一個函數,用於將各字段轉爲指定順序的數組。在
Python 中, 如果輸出字典, CSV 輸出器會根據創建輸出器時給定的 fieldnames 的順序幫我們完成這一行爲。

使用的 CSV 庫要輸出到文件或者輸出器,所以可以使用 StringWriter 或 StringIO來將結果放到 RDD 中.

// Python 中寫 CSV
def writeRecords(records):
    """寫出一些CSV記錄"""
    output = StringIO.StringIO()
    writer = csv.DictWriter(output, fieldnames=["name", "favoriteAnimal"])
    for record in records:
        writer.writerow(record)
    return [output.getvalue()]

pandaLovers.mapPartitions(writeRecords).saveAsTextFile(outputFile)

1.4 SequenceFile

對象: SequenceFile
屬性:

  • 由沒有相對關係結構的鍵值對文件組成的常用 Hadoop 格式。
  • SequenceFile文件有同步標記, Spark 可以用它來定位到文件中的某個點,然後再與記錄的邊界對齊。
  • SequenceFile 也是Hadoop MapReduce 作業中常用的輸入輸出格式.

由於 Hadoop 使用了一套自定義的序列化框架,因此 SequenceFile 是由實現 Hadoop 的 Writable接口的元素組成。

1. 讀取SequenceFile

Spark 有專門用來讀取 SequenceFile 的接口。
在 SparkContext 中,可以調用 sequenceFile(path, keyClass, valueClass, minPartitions)。

// Python 讀取 SequenceFile
val data = sc.sequenceFile(inFile,
"org.apache.hadoop.io.Text", "org.apache.hadoop.io.IntWritable")

2 . 保存SequenceFile
首先,因爲 SequenceFile 存儲的是鍵值對,所以需要創建一個由可以寫出到 SequenceFile 的類型構成的PairRDD。

1.5 對象文件

對象文件看起來就像是對 SequenceFile 的簡單封裝,它允許存儲只包含值的 RDD。

和SequenceFile 不一樣的是,對象文件是使用 Java 序列化寫出的。

對象文件在 Python 中無法使用,不過 Python 中的 RDD 和SparkContext 支持 saveAsPickleFile()和 pickleFile() 方法作爲替代。

這使用了 Python 的 pickle 序列化庫。不過,對象文件的
注意事項同樣適用於 pickle 文件: pickle 庫可能很慢,並且在修改類定義後,已經生產的數據文件可能無法再讀出來。

1.6 Hadoop輸入輸出格式

除了 Spark 封裝的格式之外,也可以與任何 Hadoop 支持的格式交互。 Spark 支持新舊兩套Hadoop 文件 API,提供了很大的靈活性。

1. 讀取其他Hadoop輸入格式

要使用新版的 Hadoop API 讀入一個文件,需要告訴 Spark 一些東西。

newAPIHadoopFile接收一個路徑以及三個類。
第一個類是“格式”類,代表輸入格式。相似的函數
hadoopFile() 則用於使用舊的 API 實現的 Hadoop 輸入格式。
第二個類是鍵的類,
最後一個類是值的類。如果需要設定額外的 Hadoop 配置屬性,也可以傳入一個 conf 對象。

KeyValueTextInputFormat 是最簡單的 Hadoop 輸入格式之一,可以用於從文本文件中讀取鍵值對數據.每一行都會被獨立處理,鍵和值之間用製表符隔開。

2. 保存Hadoop輸出格式
3. 非文件系統數據源
4. 示例: protocol buffer

1.7 文件壓縮

需要對數據進行壓縮以節省存儲空間和網絡傳輸開銷。對於大多數 Hadoop 輸出格式來說,我們可以指定一種壓縮編解碼器來壓縮數據。

Spark 原生的輸入方式(textFile 和 sequenceFile)可以自動處理一些類型的壓縮。在讀取壓縮後的數據時,一些壓縮編解碼器可以推測壓縮類型。

壓縮選項只適用於支持壓縮的 Hadoop 格式,也就是那些寫出到文件系統的格式。

Part 2 文件系統

2.1 本地 /“常規”文件系統

Spark 支持從本地文件系統中讀取文件,不過它要求文件在集羣中所有節點的相同路徑下都可以找到.

如果你的數據已經在這些系統中,那麼你只需要指定輸入爲一個 file://路徑;只要這個文件系統掛載在每個節點的同一個路徑下, Spark 就會自動處理.

如果文件還沒有放在集羣中的所有節點上,你可以在驅動器程序中從本地讀取該文件而無需使用整個集羣, 然後再調用 parallelize 將內容分發給工作節點。不過這種方式可能會比較慢,所以推薦的方法是將文件先放到像 HDFS、 NFS、 S3 等共享文件系統上。

2.2 HDFS

Spark 中使用 HDFS 只需要將輸入輸出路徑指定爲hdfs://master:port/path 就夠了。

Part 3 Spark SQL中的結構化數據

Spark SQL 是在 Spark 1.0 中新加入 Spark 的組件,並快速成爲了 Spark 中較受歡迎的操作結構化和半結構化數據的方式。

原理: 在各種情況下, 我們把一條 SQL 查詢給 Spark SQL,讓它對一個數據源執行查詢(選出一些字段或者對字段使用一些函數),然後得到由 Row 對象組成的 RDD,每個 Row 對象表示一條記錄。

在 Python 中,可以使用 row[column_number] 以及row.column_name 來訪問元素。

3.1 Apache Hive

  1. Apache Hive 是 Hadoop 上的一種常見的結構化數據源。
  2. SparkSQL 可以讀取 Hive 支持的任何表。

方法:
1. 要把 Spark SQL 連接到已有的 Hive 上,你需要提供 Hive 的配置文件。你需要將 hive-site.xml 文件複製到 Spark 的 ./conf/ 目錄下。
2. 再創建出 HiveContext 對象,也就是 Spark SQL 的入口,然後你就可以使用 Hive 查詢語言(HQL)來對你的表進行查詢,並以由行組成的 RDD 的形式拿到返回數據.

// Python 創建 HiveContext 並查詢數據
from pyspark.sql import HiveContext

hiveCtx = HiveContext(sc)
rows = hiveCtx.sql("SELECT name, age FROM users")
firstRow = rows.first()
print firstRow.name

Part 4. 數據庫

通過數據庫提供的 Hadoop 連接器或者自定義的 Spark 連接器, Spark 可以訪問一些常用的數據庫系統.

4.1 Java數據庫連接

Spark 可 以 從 任 何 支 持 Java 數 據 庫 連 接(JDBC) 的 關 系 型 數 據 庫 中 讀 取 數 據, 包括 MySQL、 Postgre 等系統。要訪問這些數據,需要構建一個 org.apache.spark.rdd.JdbcRDD,將 SparkContext 和其他參數一起傳給它。

4.2 HBase

由 於 org.apache.hadoop.hbase.mapreduce.TableInputFormat 類 的 實 現, Spark 可 以 通 過Hadoop 輸入格式訪問 HBase。

要將 Spark 用於 HBase,你需要使用正確的輸入格式調用SparkContext.newAPIHadoopRDD。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章