Spark--數據的讀取與保存

一、動機

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

  Spark 及其生態系統提供了很多可選方案。本章會介紹以下三類常見的數據源。

  • 文件格式與文件系統:對於存儲在本地文件系統或分佈式文件系統(比如 NFS、HDFS、Amazon S3 等)中的數據,Spark 可以訪問很多種不同的文件格式,包括文本文件、JSON、SequenceFile,以及 protocol buffer。我們會展示幾種常見格式的用法,以及 Spark 針對不同文件系統的配置和壓縮選項。

  • Spark SQL中的結構化數據源:後面會學習 Spark SQL 模塊,它針對包括 JSON 和 Apache Hive 在內的結構化數據源,爲我們提供了一套更加簡潔高效的 API。此處會粗略地介紹一下如何使用 SparkSQL。

  • 數據庫與鍵值存儲:概述 Spark 自帶的庫和一些第三方庫,它們可以用來連接 Cassandra、HBase、Elasticsearch 以及 JDBC 源。

二、文件格式

  Spark 對很多種文件格式的讀取和保存方式都很簡單。從諸如文本文件的非結構化的文件,到諸如 JSON 格式的半結構化的文件,再到諸如 SequenceFile 這樣的結構化的文件,Spark都可以支持(見表)。Spark 會根據文件擴展名選擇對應的處理方式。這一過程是封裝好的,對用戶透明。

  

1、文本文件

  在 Spark 中讀寫文本文件很容易。當我們將一個文本文件讀取爲 RDD 時,輸入的每一行都會成爲 RDD 的一個元素。( SparkContext.wholeTextFiles() 方法)也可以將多個完整的文本文件一次性讀取爲一個 pair RDD,其中鍵是文件名,值是文件內容。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

import org.apache.spark.SparkContext

import org.apache.spark.SparkConf

 

object Test {

  def main(args: Array[String]): Unit = {

    // Scala 中讀取一個文本文件

    val conf = new SparkConf().setAppName("wordcount").setMaster("local")

    val sc = new SparkContext(conf)

    sc.setLogLevel("WARN")  // 設置日誌顯示級別

    val input = sc.textFile("words.txt")

    input.foreach(println)

  }

 

}

  

2、保存文本文件

  輸出文本文件也相當簡單。saveAsTextFile(outputFile) 方法接收一個路徑,並將RDD 中的內容都輸入到路徑對應的文件中。Spark 將傳入的路徑作爲目錄對待,會在那個目錄下輸出多個文件。這樣,Spark 就可以從多個節點上並行輸出了。在這個方法中,我們不能控制數據的哪一部分輸出到哪個文件中,不過有些輸出格式支持控制。

3、讀取JSON

  JSON 是一種使用較廣的半結構化數據格式。這裏有兩種方式解析JSON數據,一種是通過Scala自帶的JSON包(import scala.util.parsing.json.JSON)。後面還會展示使用Spark SQL讀取JSON數據。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

import org.apache.spark.SparkContext

import org.apache.spark.SparkConf

import scala.util.parsing.json.JSON

 

object Test {

  def main(args: Array[String]): Unit = {

    val conf = new SparkConf().setAppName("JSONTest").setMaster("local")

    val sc = new SparkContext(conf)

    sc.setLogLevel("WARN")  // 設置日誌顯示級別

    val inputFile = "pandainfo.json"//讀取json文件

    val jsonStr = sc.textFile(inputFile);

    val result = jsonStr.map(s => JSON.parseFull(s));//逐個JSON字符串解析

    result.foreach(

        {

            => r match {

            case Some(map:Map[String,Any]) => println(map)

            case None => println("parsing failed!")

            case other => println("unknown data structure" + other)

            }

        }

    );

  }

 

}

  

  

  第二種方法是通過json4s來解析JSON文件。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

import org.apache.spark.SparkContext

import org.apache.spark.SparkConf

import org.json4s.jackson.Serialization

import org.json4s.ShortTypeHints

import org.json4s.jackson.JsonMethods._

import org.json4s.DefaultFormats

 

object Test {

  def main(args: Array[String]): Unit = {

    // 第二種方法解析json文件 

    val conf = new SparkConf().setAppName("wordcount").setMaster("local")

    val sc = new SparkContext(conf)

    sc.setLogLevel("WARN")  // 設置日誌顯示級別

    implicit val formats = Serialization.formats(ShortTypeHints(List()))

    val input = sc.textFile("pandainfo.json")

    input.collect().foreach(x=>{

      var = parse(x).extract[Panda]

      println(c.name+","+c.lovesPandas)

    })

    case class Panda(name:String,lovesPandas:Boolean)

  }

 

}

  

4、保存JSON

  寫出 JSON 文件比讀取它要簡單得多。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

import org.apache.spark.SparkContext

import org.apache.spark.SparkConf

import org.json4s.jackson.Serialization

import org.json4s.ShortTypeHints

import org.json4s.jackson.JsonMethods._

import org.json4s.DefaultFormats

 

object Test {

  def main(args: Array[String]): Unit = {

    // 第二種方法解析json文件 

    val conf = new SparkConf().setAppName("wordcount").setMaster("local")

    val sc = new SparkContext(conf)

    sc.setLogLevel("WARN")  // 設置日誌顯示級別

    implicit val formats = Serialization.formats(ShortTypeHints(List()))

    val input = sc.textFile("pandainfo.json")

    input.collect().foreach(x=>{

      var = parse(x).extract[Panda]

      println(c.name+","+c.lovesPandas)

    })

    case class Panda(name:String,lovesPandas:Boolean)

     

    // 保存json

    val datasave = input.map{  myrecord =>

      implicit val formats = DefaultFormats

      val jsonObj = parse(myrecord)

      jsonObj.extract[Panda]

    }

    datasave.saveAsTextFile("savejson")

  }

 

}

  

5、逗號分隔值與製表符分隔值

  逗號分隔值(CSV)文件每行都有固定數目的字段,字段間用逗號隔開(在製表符分隔值文件,即 TSV 文件中用製表符隔開)。記錄通常是一行一條,不過也不總是這樣,有時也可以跨行。讀取 CSV/TSV 數據和讀取 JSON 數據相似,都需要先把文件當作普通文本文件來讀取數據,再對數據進行處理。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

import org.apache.spark.SparkContext

import org.apache.spark.SparkConf

 

import au.com.bytecode.opencsv.CSVReader

import java.io.StringReader

import java.io.StringWriter

import au.com.bytecode.opencsv.CSVWriter

 

object Test {

  def main(args: Array[String]): Unit = {

 

    // 在Scala中使用textFile()讀取CSV

    val conf = new SparkConf().setAppName("wordcount").setMaster("local")

    val sc = new SparkContext(conf)

    sc.setLogLevel("WARN")  // 設置日誌顯示級別

    val inputFile = "favourite_animals.csv"//讀取csv文件   

    val input = sc.textFile(inputFile)

    val result = input.map{

      line =val reader = new CSVReader(new StringReader(line))

      reader.readNext()

    }

    // result.foreach(println)

    for(res <- result)

      for(r <- res)

        println(r)   

  }

 

}

  

  

在spark中讀csv文件的方式

用CSVReader這個類去讀

用法:

//1、讀csv文件

    val stringReader: StringReader = new StringReader("")

    val reader: CSVReader = new CSVReader(stringReader)

    reader.readNext()

在rdd中應用CSVReader

導包:

import scala.collection.JavaConversions._
//2、讀的方法已經有了,需要把這個方法用到我們的spark程序中,rdd

    val sc: SparkContext = initSpark()

    val lines: RDD[String] = sc.textFile(localFilePath)

    val result: RDD[Array[String]] = lines.map(line => {

      val stringReader = new StringReader(line)

      val reader = new CSVReader(stringReader)

      reader.readNext()

    })

    result.foreach(x=> {

      x.foreach(println)

      println("===================================")

})
def initSpark(): SparkContext = {

    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("readcsvdemo")

    val sc = new SparkContext(conf)

    sc

  }

 

2、保存csv文件

CSVWriter這個類能往外保存csv文件

val stringWriter = new StringWriter()

    val writer: CSVWriter = new CSVWriter(stringWriter)



    val strings: Array[String] = Array("hello","hi")

    val list: List[Array[String]] = List(array)

writer.writeAll(list)

println(stringWriter.toString)

在rdd應用:

導包:

import scala.collection.JavaConversions._

val sc: SparkContext = initSpark()

    //在rdd中應用往外寫csv文件的方法

    //創建一個Array[String]數據類型的array數組

    val array:Array[String] = Array("hello","hi")

    //用List包裝array,產生一個List[Array[String]]的集合list

    //爲什麼需要List[Array[String]]這個數據類型,因爲csvwriter的write方法要求這種數據類型

    val list: List[Array[String]] = List(array)


   

 //創建RDD[Array[String]]類型的RDD

    val input: RDD[Array[String]] = sc.parallelize(list)

    //rdd的轉化操作,應用csvwriter的保存方法

    val result: RDD[String] = input.mapPartitions(arr => {

      val list: List[Array[String]] = arr.toList

      val stringWriter = new StringWriter()

      val writer = new CSVWriter(stringWriter)

      writer.writeAll(list)

      //這個方法要求最後的返回類型爲Iterator(迭代器),就把stringWriter中的數據放到了迭代器中

      Iterator(stringWriter.toString)

    })

    //調用rdd的saveAsTextFile方法保存數據

    result.saveAsTextFile(outputPath)

 

6、SequenceFile

  SequenceFile 是由沒有相對關係結構的鍵值對文件組成的常用 Hadoop 格式。SequenceFile文件有同步標記,Spark 可以用它來定位到文件中的某個點,然後再與記錄的邊界對齊。這可以讓 Spark 使用多個節點高效地並行讀取 SequenceFile 文件。SequenceFile 也是Hadoop MapReduce 作業中常用的輸入輸出格式,所以如果你在使用一個已有的 Hadoop 系統,數據很有可能是以 SequenceFile 的格式供你使用的。

  由於 Hadoop 使用了一套自定義的序列化框架,因此 SequenceFile 是由實現 Hadoop 的 Writable接口的元素組成。下表 列出了一些常見的數據類型以及它們對應的 Writable 類。標準的經驗法則是嘗試在類名的後面加上 Writable 這個詞,然後檢查它是否是 org.apache.hadoop.io.Writable 已知的子類。如果你無法爲要寫出的數據找到對應的 Writable 類型(比如自定義的 case class),你可以通過重載 org.apache.hadoop.io.Writable 中的 readfields 和 write 來實現自己的 Writable 類。

   

  讀取SequenceFile:Spark 有專門用來讀取 SequenceFile 的接口。在 SparkContext 中,可以調用 sequenceFile(path,keyClass, valueClass, minPartitions) 。前面提到過,SequenceFile 使用 Writable 類,因此 keyClass 和 valueClass 參數都必須使用正確的 Writable 類。

  保存SequenceFile:在 Scala 中將數據寫出到 SequenceFile 的做法也很類似。可以直接調用 saveSequenceFile(path) 保存你的 PairRDD ,它會幫你寫出數據。

package com.km.sparkdemo

import org.apache.hadoop.io.{IntWritable, Text}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @Author Lucas
  * @Date 2020/4/7 21:31
  * @Version 1.0
  */
object SequenceFileDemo1 {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("sequenceFileDemo")
    val sc = new SparkContext(conf)
    val outPath = "c:/sparkdata/out/sequenceout"
    //寫出(創造)一個sequenceFile
    val rdd: RDD[(String, Int)] = sc.parallelize(List(("panda",3),("kay",6),("snail",3)))
    rdd.saveAsSequenceFile(outPath)

    //讀SequenceFile
    val lines: RDD[(Text, IntWritable)] = sc.sequenceFile(outPath,classOf[Text],classOf[IntWritable])
    val output: RDD[(String, Int)] = lines.map{case(x,y)=>(x.toString,y.get())}
    output.foreach(println)
  }

}

 

輸出結果:
 

7、對象文件

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

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

讀取文件——用 SparkContext 中的 objectFile() 函數接收一個路徑,返回對應的 RDD。

寫入文件——要 保存對象文件, 只需在 RDD 上調用 saveAsObjectFile

8.Hadoop輸入輸出格式

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

舊的API:hadoopFile,使用舊的 API 實現的 Hadoop 輸入格式

新的API:newAPIHadoopFile 
接收一個路徑以及三個類。第一個類是“格式”類,代表輸入格式。第二個類是鍵的類,最後一個類是值的類。如果需要設定額外的 H adoop 配置屬性,也可以傳入一個 conf 對象。

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


 

package com.km.sparkdemo

import org.apache.hadoop.io.Text
import org.apache.hadoop.mapreduce.Job
import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @Author Lucas
  * @Date 2020/4/7 21:54
  * @Version 1.0
  */
object HadoopDemo {
  def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf().setMaster("local").setAppName("sequenceFileDemo")
    val sc = new SparkContext(conf)
    val inputFile = "c:/sparkdata/pandainfo.json"
    val outputFile = "c:/sparkdata/out/hadoopout/hadoopfile.json"
    val job = new Job()
    val data: RDD[(Text, Text)] = sc.newAPIHadoopFile(
      inputFile,
      classOf[KeyValueTextInputFormat],
      classOf[Text],
      classOf[Text],
      job.getConfiguration)
    data.foreach(println)


    //保存文件,注意導包
    data.saveAsNewAPIHadoopFile(
      outputFile,
      classOf[Text],
      classOf[Text],
      classOf[TextOutputFormat[Text,Text]],
      job.getConfiguration
    )
  }



}

Hadoop 的非文件系統數據源

除 了 hadoopFile() 和 saveAsHadoopFile() 這 一 大 類 函 數, 還 可 以 使 用 hadoopDataset/saveAsHadoopDataSet 和 newAPIHadoopDataset/ saveAsNewAPIHadoopDataset 來訪問 Hadoop 所支持的非文件系統的存儲格式。例如,許多像 HBase 和 MongoDB 這樣的鍵值對存儲都提供了用來直接讀取 Hadoop 輸入格式的接口。我們可以在 Spark 中很方便地使用這些格式。

 

7.文件壓縮

Spark 原生的輸入方式( textFile 和 sequenceFile)可以自動處理一些類型的壓縮。在讀取壓縮後的數據時,一些壓縮編解碼器可以推測壓縮類型。 
這些壓縮選項只適用於支持壓縮的 Hadoop 格式,也就是那些寫出到文件系統的格式。寫入數據庫的 Hadoop 格式一般沒有實現壓縮支持。如果數據庫中有壓縮過的記錄,那應該是數據庫自己配置的。

 

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