Apache Spark渐进式学习教程(五): 数据的读取与保存

15 篇文章 0 订阅

目录

 

一,前言

1.1,文件格式与文件系统

1.2,Spark SQL中的结构化数据源

1.3,数据库与键值存储

二,文件格式

2.1,文本文件

2.2,JSON

2.3,逗号分隔值与制表符分隔值

三,文件系统

3.1 本地/“常规”文件系统

3.2 Amazon S3

3.3 HDFS

四,数据库

JdbcRDD操作 MySQL等关系型数据库


一,前言

Spark 支持很多种输入输出源。一部分原因是 Spark 本身是基于 Hadoop 生态圈而构建,特别是 Spark 可以通过 Hadoop MapReduce 所使用的 InputFormat 和 OutputFormat 接口访问数据,而大部分常见的文件格式与存储系统(例如 S3、HDFS、Cassandra、HBase 等)都支持这种接口。不过,基于这些原始接口构建出的高层 API 会更常用。幸运的是,Spark 及其生态系统提供了很多可选方案。本章会介绍以下三类常见的数据源。

1.1,文件格式与文件系统

对于存储在本地文件系统或分布式文件系统(比如 NFS、HDFS、Amazon S3 等)中的数据,Spark 可以访问很多种不同的文件格式,包括文本文件、JSON、SequenceFile,以及 protocol buffer。我们会展示几种常见格式的用法,以及 Spark 针对不同文件系统的配置和压缩选项。

1.2,Spark SQL中的结构化数据源

Spark SQL模块针对包括 JSON 和 Apache Hive 在内的结构化数据源,为我们提供了一套更加简洁高效的 API。

1.3,数据库与键值存储

本章还会概述 Spark 自带的库和一些第三方库,它们可以用来连接 Cassandra、HBase、Elasticsearch 以及 JDBC 源。

二,文件格式

Spark 对很多种文件格式的读取和保存方式都很简单。从诸如文本文件的非结构化的文件,到诸如 JSON 格式的半结构化的文件,再到诸如 SequenceFile 这样的结构化的文件,Spark都可以支持(见表 5-1)。Spark 会根据文件扩展名选择对应的处理方式。这一过程是封装好的,对用户透明。

 

表5-1:Spark支持的一些常见格式

除了 Spark 中直接支持的输出机制,还可以对键数据(或成对数据)使用 Hadoop 的新旧文件 API。由于 Hadoop 接口要求使用键值对数据,所以也只能这样用,即使有些格式事实上忽略了键。对于那些会忽视键的格式,通常使用假的键(比如 null)。

2.1,文本文件

在 Spark 中读写文本文件很容易。当我们将一个文本文件读取为 RDD 时,输入的每一行都会成为 RDD 的一个元素。也可以将多个完整的文本文件一次性读取为一个 pair RDD,其中键是文件名,值是文件内容。

package com.t9vg

import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author tianfusheng
  * @e-mail linuxmorebetter@gmail.com
  * @date 2019/7/31
  */
object TextFile {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("WordCount")
    val sc = new SparkContext(conf)
    //read
    val text = sc.textFile("readAndWrite/src/main/resources/1.txt")
    //遍历输出到控制台
    text.foreach(x=>println(x))
    //write
    text.saveAsTextFile("readAndWrite/src/main/resources/2")
  }
}

2.2,JSON

JSON 是一种使用较广的半结构化数据格式。读取 JSON 数据的最简单的方式是将数据作为文本文件读取,然后使用 JSON 解析器来对 RDD 中的值进行映射操作。类似地,也可以使用我们喜欢的 JSON 序列化库来将数据转为字符串,然后将其写出去。

package com.t9vg
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
/**
  * @author tianfusheng
  * @e-mail linuxmorebetter@gmail.com
  * @date 2019/7/31
  */
object Json {
  def main(args: Array[String]): Unit = {
    // 第二种方法解析json文件
    val conf = new SparkConf().setAppName("Json").setMaster("local")
    val sc = new SparkContext(conf)
    sc.setLogLevel("WARN")  // 设置日志显示级别
    implicit val formats = Serialization.formats(ShortTypeHints(List()))
    val input = sc.textFile("readAndWrite/src/main/resources/pandainfo.json")
    input.collect().foreach(x=>{
      var c = 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("readAndWrite/src/main/resources/savejson")
  }
}

2.3,逗号分隔值与制表符分隔值

逗号分隔值(CSV)文件每行都有固定数目的字段,字段间用逗号隔开(在制表符分隔值文件,即 TSV 文件中用制表符隔开)。记录通常是一行一条,不过也不总是这样,有时也可以跨行。CSV 文件和 TSV 文件有时支持的标准并不一致,主要是在处理换行符、转义字符、非 ASCII 字符、非整数值等方面。CSV 原生并不支持嵌套字段,所以需要手动组合和分解特定的字段。

与 JSON 中的字段不一样的是,这里的每条记录都没有相关联的字段名,只能得到对应的序号。常规做法是使用第一行中每列的值作为字段名。

package com.t9vg
import org.apache.spark.SparkContext
import org.apache.spark.SparkConf
import au.com.bytecode.opencsv.CSVReader
import java.io.StringReader

/**
  * @author tianfusheng
  * @e-mail linuxmorebetter@gmail.com
  * @date 2019/8/1
  */
object Csv {
  def main(args: Array[String]): Unit = {

    // 在Scala中使用textFile()读取CSV
    val conf = new SparkConf().setAppName("Csv").setMaster("local")
    val sc = new SparkContext(conf)
    val inputFile = "readAndWrite/src/main/resources/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)
  }
}

三,文件系统

3.1 本地/“常规”文件系统

Spark 支持从本地文件系统中读取文件,不过它要求文件在集群中所有节点的相同路径下都可以找到。一些像 NFS、AFS 以及 MapR 的 NFSlayer 这样的网络文件系统会把文件以常规文件系统的形式暴露给用户。如果你的数据已经在这些系统中,那么你只需要指定输入为一个 file://路径;只要这个文件系统挂载在每个节点的同一个路径下,Spark 就会自动处理。在 Scala 中从本地文件系统读取一个压缩的文本文件

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

如果文件还没有放在集群中的所有节点上,你可以在驱动器程序中从本地读取该文件而无需使用整个集群,然后再调用 parallelize 将内容分发给工作节点。不过这种方式可能会比较慢,所以推荐的方法是将文件先放到像 HDFS、NFS、S3 等共享文件系统上。

3.2 Amazon S3

用 Amazon S3 来存储大量数据正日益流行。当计算节点部署在 Amazon EC2 上的时候,使用 S3 作为存储尤其快,但是在需要通过公网访问数据时性能会差很多。要在 Spark 中访问 S3 数据,你应该首先把你的 S3 访问凭据设置为 AWS_ACCESS_KEY_ID 和AWS_SECRET_ACCESS_KEY 环境变量。你可以从 Amazon Web Service 控制台创建这些凭据。接下来,将一个以 s3n:// 开头的路径以 s3n://bucket/path-within-bucket 的形式传给Spark 的输入方法。和其他所有文件系统一样,Spark 也能在 S3 路径中支持通配字符,例如 s3n://bucket/my-Files/*.txt。

如果你从 Amazon 那里得到 S3 访问权限错误,请确保你指定了访问密钥的账号对数据桶有“read”(读)和“list”(列表)的权限。Spark 需要列出桶内的内容,来找到想要读取的数据。

3.3 HDFS

Hadoop 分布式文件系统(HDFS)是一种广泛使用的文件系统,Spark 能够很好地使用它。HDFS 被设计为可以在廉价的硬件上工作,有弹性地应对节点失败,同时提供高吞吐量。Spark 和 HDFS 可以部署在同一批机器上,这样 Spark 可以利用数据分布来尽量避免一些网络开销。

在 Spark 中使用 HDFS 只需要将输入输出路径指定为 hdfs://master:port/path 就够了。

四,数据库

JdbcRDD操作 MySQL等关系型数据库

package com.t9vg.sql

import java.sql.{Connection, DriverManager, ResultSet}
import org.apache.spark.rdd.JdbcRDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * @author tianfusheng
  * @e-mail linuxmorebetter@gmail.com
  * @date 2019/8/1
  */
object JdbcRDD {
  def main(args: Array[String]) {
    //val conf = new SparkConf().setAppName("spark_mysql").setMaster("local")
    val sc = new SparkContext("local","JdbcRDD")

    def createConnection() = {
      Class.forName("com.mysql.jdbc.Driver").newInstance()
      DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root")
    }

    def extractValues(r: ResultSet) = {
      (r.getString(1), r.getString(2))
    }

    val data = new JdbcRDD(sc, createConnection, "SELECT id,name FROM person where ? <= ID AND ID <= ?", lowerBound = 0, upperBound =5, numPartitions = 1, mapRow = extractValues)

    println(data.collect().toList)

    sc.stop()
  }

}

JdbcRDD的sql参数要带有两个?的占位符,而这两个占位符是给参数lowerBound和参数upperBound定义where语句的边界的。

项目源码:https://github.com/JDZW2018/learningSpark ,中子项目readAndWrite。

转载请注明出处。

欢迎加入:巨匠IT-Java/Scala/大数据/SpringCloud 技术讨论qq群:854150511

本文转载至:https://www.t9vg.com/archives/611

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值