目录
一,前言
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