《Spark快速大數據分析》——讀書筆記(5)

第五章 數據讀取與保存

5.1 動機

動機:數據量比較大,單臺機器無法完成。
三類常見的數據源:

  • 文件格式與文件系統。對於存儲在本地文件系統或分佈式文件系統(比如NFS、HDFS、Amazon S3等)中的數據,Spark可以訪問很多種不同的文件格式,包括文本文件、JSONSequenceFile以及protocal buffer。
  • Spark SQL中的結構化數據源。
  • 數據庫與鍵值存儲。

5.2 文件格式

這裏寫圖片描述

5.2.1 文本文件

將一個文本文件讀取爲RDD時,輸入的每一行都會成爲RDD的一個元素。也可以將多個完整的文本文件一次性讀取爲一個pair RDD,鍵是文件名,值是文件內容。
1. 讀取文本文件
使用SparkContext的textFile()函數。

例5-1:在Python中讀取一個文本文件

input=sc.textFile("file:///..../README.md")

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

  • 仍使用textFile函數,傳遞目錄作爲參數。
  • 使用SparkContext.wholeTextFiles()方法,該方法會返回一個pairRDD,鍵是輸入文件的文件名。

Spark支持讀取給定目錄中的所有文件,以及在輸入路徑中使用通配字符。
2. 保存文本文件
saveAsTextFile()方法接收一個路徑,並將RDD中的內容都輸入到路徑對應的文件中。這個方法中,我們不能控制數據的哪一部分輸出到那個文件中,不過有些輸出格式支持控制。

例5-5:在Python中將數據保存爲文本文件

result.saveAsTextFile(outputFile)

5.2.2 JSON

1. 讀取JSON
將數據作爲文本文件讀取,然後對JSON數據進行解析,該方法在所有支持的編程語言中都可以使用。該方法假設文件中每一行都是一條JSON記錄。

例5-6:在Python中讀取非結構化的JSON

import json
data=input.map(lambda x:json.loads(x))

需要注意格式不正確的記錄的處理。
2. 保存JSON
可以使用之前將字符串RDD轉爲解析好的JSON數據的苦,將由結構化數據組成的RDD轉爲字符串RDD,然後使用Spark的文本文件API寫出去。

例5-9:在Python保存爲JSON

(data.filter(lambda x:x["lovePandas"]).map(lambda x:json.dumps(x)).saveAsTextFile(outputFile))

5.2.3 逗號分隔值與製表符分割值

1. 讀取CSV
先把文件當做普通文本文件來讀取數據,再對數據進行處理。
如果CSV的所有數據字段均沒有包含換行符,可以使用textFile()讀取並解析數據。

例5-12:在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)

如果在字段中嵌有換行符,就需要完整讀入每個文件,然後解析各段,如果每個文件都很大,讀取和解析過程可能會成爲性能瓶頸。

例5-15:在Python中完整讀取CSV

def loadRecords(fileNameContents):
    """讀取給定文件中的所有記錄"""
    input=StringIO.StringIO(fileNameContents[1])
    reader=csv.DictReader(input,fieldnames=["name","favouriteAnimal"])
    return reader
fullFileData=sc.wholeTextFiles(inputFile).flatMap(loadRecords)

2. 保存CSV
和JSON數據一樣,寫出CSV/TSV可以通過重用輸出編碼器來加速。由於在CSV中我們不會在每條記錄中輸出字段名,因此爲了使輸出保持一致,需要創建一種映射關係。

例5-18:在Python中寫CSV

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

5.2.4 SequenceFile

SequenceFile是由沒有相對關係結構的鍵值對文件組成的常用Hadoop格式。SequenceFile文件有同步標記,Spark可以用它定位到文件中的某個點,然後再與記錄的邊界對齊。這可以讓Spark使用多個節點高效地並行讀取SequenceFile文件。
由於Hadoop使用了一套自定義的序列化框架,因此SequenceFile是由實現Hadoop的Writable接口元素組成。
這裏寫圖片描述
1. 讀取SequenceFile
Spark有專門用來讀取SequenceFile的接口。在SparkContext中,可以調用sequenceFile(path, keyClass, valueClass, minpartitions),前面提及SequenceFile使用Writable類,因此keyClass和valueClass都必須使用正確的Writable類。

例5-20:在Python中讀取SequenceFile

val data=sc.sequenceFile(inFile,"org.apache.hadoop.io.Text", "org.apache.hadoop.io.IntWritable")

2. 保存SequenceFile
在Scala中,需要創建一個又可以寫出到SequenceFile的類型構成的PairRDD,如果要保存的是Scala的原生類型,可以直接調用saveSequenceFile(path) 。如果鍵和值不能自動轉爲Writable類型,或想使用變長類型,可以對數據進行映射操作,在保存之前進行類型轉換。

5.2.5 對象文件

對象文件看起來詳實對SequenceFile的簡單封裝,它允許存儲至包含值的RDD。和SequenceFile不一樣的是,對象文件是使用Java序列化寫出的。

注意:如果你修改了類,比如增減了幾個字段,已經生成的對象文件就不再可讀了。

對對象文件使用Java序列化需要注意:

  • 和普通的SequenceFile不同,對同樣的對象,對象文件的輸出和Hadoop的輸出不一樣。
  • 對象文件通常用於Spark作業間的通信。
  • Java序列化有可能相當慢。

對象文件的保存:RDD上調用saveAsObjectFile。
對象文件的讀取:用SparkContext中的objectFile()接受路徑,返回RDD。
對象文件的優點:可以用來保存幾乎任意對象而不需要額外的工作。

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

5.2.6 Hadoop輸入輸出格式

除了Spark封裝的格式外,也可以與任何Hadoop支持的格式交互。Spark支持新舊兩套Hadoop文件API。
1. 讀取其他Hadoop輸入格式
新版的Hadoop API讀入文件,newAPIHadoopFile。第一個類是“格式”類,代表輸入格式,第二個類是鍵的類,最後一個類是值的類。
舊版的Hadoop API讀入文件,HadoopFile。
我們學習了通過讀取文本文件並加以解析以讀取JSON數據的方法。也可以自定義Hadoop輸入格式來讀取JSON數據。
2. 保存Hadoop輸出格式
使用舊式API保存pair RDD。

例5-26:在Java保存SequenceFile

public static class ConvertToWritableTypes implements
    PairFunction<Tuple2<String,Integer>,Text,IntWritable>{
    public Tuple2<Text,IntWritable> call(Tuple2<String,Integer> record){
        return new Tuple2(new Text(record._1),new IntWritable(record._2));
    }
}
JavaPairRDD<String, Integer> rdd=sc.parallelizePairs(input);
JavaPairRDD<Text,IntWritable> result=rdd.mapToPair(new ConvertToWritableTypes());
result.saveAsHadoopFile(fileName,Text.calss,IntWritable.class,SequenceFileOutputFormat.class);

3. 非文件系統數據源
hadoopDataset/saveAsHadoopDataSet和newAPIHadoopDataset/saveAsNewAPIHadoopDataset可以訪問Hadoop所支持的非文件系統的存儲格式。

5.2.7 文件壓縮

這裏寫圖片描述
大數據工作中,我們經常需要對數據進行壓縮以節省存儲空間和網絡傳輸開銷。對於大多數Hadoop輸出格式,我們可以制定一種壓縮編碼器來壓縮數據。
這些壓縮選項只是用與支持壓縮的Hadoop格式,也就是那些寫出到文件系統的格式。寫入數據庫的Hadoop格式一般沒有實現壓縮支持。
可以很容易從多個節點上並行讀取的格式被稱爲“可分割”的格式。

5.3 文件系統

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

Spark支持從本地文件系統中讀取文件,不過它要求文件在集羣中所有節點的相同路徑下可以找到。
一些像NFS、AFS以及MapR的NFS layer這樣的網絡文件系統會把文件以常規文件系統的形式暴露給用戶。如果數據已經在這些系統中,則指定輸入爲一個file://路徑;只要這個文件系統掛載在每個節點的同一個路徑下,Spark就會自動處理,如例5-29。

例5-29:在Scala中從本地文件系統讀取一個壓縮的文本文件

val rdd=sc.textFile("file:///home/holden/happypandas.gz")

如果文件還沒有放在集羣中的所有節點上,可以在驅動器程序中從本地讀取改文件而無需使用整個集羣,然後再調用parallellize將內容分發給工作節點。不過這種方式可能會比較慢。

5.3.2 Amazon S3

要在Spark中訪問S3數據,
首先應該吧S3訪問憑據設置爲AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY環境變量。
然後,將以s3n://開頭的路徑以s3n://bucket/path-within-bucket的形式傳給Spark的輸入方法。
如果得到S3訪問權限錯誤,請確保制定了訪問祕鑰的賬號對數據通有“read”和“list”的權限。

5.3.3 HDFS

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

5.4 Spark SQL中的結構化數據

在各種情況下,我們把一條SQL查詢給Spark SQL,讓它對一個數據源執行查,然後得到有Row對象組成的RDD,每個Row對象表示一條記錄。在Java和Scala中,Row對象的訪問是基於下標的。每個Row都有一個get()方法,會返回一個一般類型讓我們可以進行類型轉換。在Python中,可以使用row[column_number]以及row.column_name來訪問元素。

5.4.1 Apache Hive

Spark可以讀取Hive支持的任何表。
要把Spark SQL連接到已有的Hive上,你需要提供Hive的配置文件。你需要將hive-site.xml文件複製到Spark的./conf/目錄下。這樣做好之後,再創建出HiveContext對象,也就是Spark SQL的入口,就可以使用HQL進行查詢。

例5-30:用Python創建HiveContext並查詢數據

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

5.4.2 JSON

要讀取JSON數據,首先需要和使用Hive一樣創建一個HiveContext(這時不用安裝好Hive)。然後使用HiveContext.jsonFile方法來從整個文件中獲取由Row對象組成的RDD。

例5-34:在Python中使用SparkSQL讀取JSON數據

tweets = hiveCtx.jsonFile("tweets.json")
tweets.registerTempTable("tweets")
results = hiveCtx.sql("SELECT user.name, text FROM tweets")

5.5 數據庫

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

5.5.1 Java數據庫連接

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

  • 一個用於對數據庫創建連接的函數。這個函數讓每個節點在連接必要的配置後創建自己讀取數據的連接。
  • 一個可讀取一定範圍內數據的查詢,以及查詢參數中的lowerBound和upperBound的值。
  • 可以將輸出結果從java.sql.ResultSet轉爲對操作數據有用的格式的函數。

和其他數據源一樣,使用JdbcRDD時,需確保數據庫可以應付Spark並行讀取的負載。

5.5.2 Cassandra

隨着DataStax 開源其用於Spark 的Cassandra 連接器(https://github.com/datastax/spark-cassandraconnector),Spark 對Cassandra 的支持大大提升。這個連接器目前還不是Spark 的一部分,因此你需要添加一些額外的依賴到你的構建文件中才能使用它。Cassandra 還沒有使用Spark SQL,不過它會返回由CassandraRow 對象組成的RDD,這些對象有一部分方法與Spark SQL 的Row 對象的方法相同,如例5-38 和例5-39 所示。Spark 的Cassandra 連接器目前只能在Java 和Scala 中使用。

5.5.3 HBase

由於org.apache.hadoop.hbase.mapreduce.TableInputFormat 類的實現,Spark 可以通過Hadoop 輸入格式訪問HBase。這個輸入格式會返回鍵值對數據,其中鍵的類型爲org.apache.hadoop.hbase.io.ImmutableBytesWritable,而值的類型爲org.apache.hadoop.hbase.client.Result。Result 類包含多種根據列獲取值的方法,在其API 文檔(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Result.html)中有所描述。

5.5.4 Elasticsearch

Spark 可以使用Elasticsearch-adoop(https://github.com/elastic/elasticsearch-hadoop)從Elasticsearch中讀寫數據。Elasticsearch 是一個開源的、基於Lucene 的搜索系統。Elasticsearch 連接器和我們研究過的其他連接器不大一樣,它會忽略我們提供的路徑信息,而依賴於在SparkContext 中設置的配置項。Elasticsearch 的OutputFormat 連接器也沒有用到Spark 所封裝的類型,所以我們使用saveAsHadoopDataSet 來代替,這意味着我們需要手動設置更多屬性。

5.6 總結

在本章結束之際,你應該已經能夠將數據讀取到Spark 中,並將計算結果以你所希望的方式存儲起來。我們調查了數據可以使用的一些不同格式,一些壓縮選項以及它們對應的數據處理的方式。現在我們已經掌握了讀取和保存大規模數據集的方法,後續章節會介紹一些用來編寫更高效更強大的Spark 程序的方法。

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