Spark由浅到深(4) -- 数据读取与保存

版权声明:本文为博主原创文章,未经博主允许不得转载。对于文中涉及到的参考文章,感谢作者分享,侵删。 https://blog.csdn.net/Kohang/article/details/78362109

文本数据读取 → 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。

猜你喜欢

转载自blog.csdn.net/Kohang/article/details/78362109