可以在Spark中使用的各种其他数据源以及来自社区构建的各种其他数据源
Spark包含六大核心数据源以及由社区生成的许多外部数据源,提供对各种数据源的读写能力支持,以下是Spark的核心数据源:
CSV、JSON、Parquet、ORC、纯文本文件、JDBC/ODBC连接
Spark有许多由社区创建的数据源,如下只是一小部分样本:
Hbase、MongoDB、XML等
数据源API的结构
Read API结构
读取数据的核心结构如下:
bash
DataFrameReader.format(...).option("key","value").schema(...).load()
我们将使用此格式来读取所有数据源,format是可选的,默认情况下Spark将使用Parquet格式,option使你能配置键值对来参数话读取数据的方式。最后,如果数据源包含某种schema或你想使用模式推理,则可以选择指定schema。
数据读取基础
Spark数据读取使用DataFrameReader,通过SparkSession的read属性得到
有了DataFrame reader之后,我们需要指定几个值:
format
schema
read模式
一系列option选项
format,option和schema都会返回一个DataFrameReader,它可以进行进一步的转换,并且都是可选的。每个数据源都有一组特定的选项
读取模式
从外部源读取数据很容易会遇到错误格式的数据,尤其是在处理半结构化数据时,读取模式指定当Spark遇到错误格式的记录应该采取什么操作。
默认是permissive
| 读取模式 | 说明 |
|---|---|
| permissive | 当遇到错误格式的记录时,将所有字段设置为null并将所有错误格式的记录放在名为_corrupt_record字符串列中 |
| dropMalformed | 删除包含错误格式记录的行 |
| failFast | 遇到错误格式的记录后立即返回失败 |
Write API结构
写数据的核心结构如下:
DataFrameWriter.format(...).option(...).partitionBy(...).bucketBy(...).sortBy(...).save()
我们将使用此格式向所有数据源写入数据。format是可选的,默认情况下Spark将使用parquet格式,option仍然用于配置写出数据的方法,PartitionBy,bucketBy和sortBy仅适用基于文件的数据源
写数据基础
写数据与读取数据非常相似,不同的是,需要用到的是DataFramWriter而不是DataFrameReader了,因为总是需要将数据写入一些给定数据源中,所以我们通过每个DataFrame的write属性来获取DataFrameWriter:
dataFrame.write
有了DataFrameWriter之后,我们需要指定三个值:format,一系列option选项和save模式,并且必须至少提供一条写入路径(来指定目标地址)
保存模式
保存模式指明如果Spark在指定目标路径发现有其他数据占用时应该采取什么操作
| 保存模式 | 描述 |
|---|---|
| append | 将输出文件追加到目标路径已存在的文件上或目录的文件列表 |
| overwrite | 将完全覆盖目标路径中已经存在的任何数据 |
| errorIfExists | 如果目标路径已存在数据或文件,则抛出错误并返回写入操作失败 |
| ignore | 如果目标路径已经存在数据或文件,则不执行任何操作 |
默认值为errorIfExists,也就是说如果目标路径已有数据存在数据则Spark立即写入失败
CSV文件
csv即逗号分隔值(comma-separated values),这是一种常见的文本文件格式,其中每行表示一条记录,用逗号分隔记录中的每个字段。虽然CSV文件看起来结构良好,实际上它存在各种各样的问题,是最难处理的文件格式之一,这是因为实际应用场景中遇到的数据内容或数据结构并不会那么规范。因此,csv读取程序包含大量选项,通过这些选项可以帮助你解决像忽略特定字符等的这种问题,比如当一列的内容也以逗号分隔时,需要识别出该逗号是列中的内容,还是列间分隔符。
读CSV文件
与读取其他格式一样,要读取csv文件必须首先为该特定格式创建一个DataFrameReader,这里我们将格式指定为CSV:
bash
spark.read.format("csv")
然后,我们可以选择指定schema和modes选项
写CSV文件
同读取数据一样,写CSV文件时,也有多种选项用于写入数据。写入选项表是读取选项表的子集,因为很多选项在写入数据时并不适用
我们可以很容易读取csv文件内容,并写入tsv文件,当列出目录时,可以看到结果实际上是一个包含大量文件的文件夹,这实际上对应着写出时dataframe的数据分片,如果在写出之前对数据进行重新分片,最终会得到不同的文件数量
JSON文件
JSON(JavaScript Object Notation)
在处理json数据之前需要了解的是,在spark中,我们提及的json文件指的是换行符分隔的json,每行必须包含一个单独的,独立的有效json对象,这与包含大的json对象或数组的文件是有区别的
换行符分隔json对象还是一个对象可以跨越多行,这个可以由multiLine选项控制,当multiLine为true时,则可以将整个文件作为一个json对象读取,并且spark将其解析为dataframe。换行符分隔的json实际上是一种更稳定的格式,因为它可以在文件末尾追加新纪录(而不是必须读入整个文件然后再写出),我们也建议使用换行符分隔的格式,换行符分隔的json格式流行的另一个关键原因是json对象具有结构化信息,并且(基于json)JavaScript也支持基本类型
读取换行符分隔的json文件仅仅是指定的格式(format)和选项有所不同:
spark.read.format("json")
Parquet文件
Parquet是一种开源的面向列的数据存储格式,它提供了各种存储优化,尤其适合数据分析。Parquet提供列压缩从而可以节省空间,而且它支持按列读取而非整个文件地读取。作为一种文件格式,Parquet与Apache Spark配合的很好,而且实际上也是Spark的默认文件格式。我们建议将数据写到Parquet以便于长期存储,因为从Parquet文件读取始终比从json文件或csv文件效率更高。Parquet的另一个优点是它支持复杂类型,也就是说如果列是一个数组(csv文件无法存储数组列)、map映射或struct结构体,仍可以正常读取和写入
bash
spark.read.format("parquet")
ORC文件
ORC是为Hadoop作业而设计的自描述,类型感知的列存储文件格式。它针对大型流式数据读取进行优化,但集成了对快速查找所需行的相关支持。实际上,读取ORC文件数据时没用可选项,这是因为Spark非常了解该文件格式。ORC和Parquet有什么区别?在大多数情况下,它们非常相似,本质区别是,Parquet针对Spark进行了优化,而ORC则是针对Hive进行了优化。
SQL数据库
因为很多系统的标准语言都采用SQL,所以SQL数据源是很强大的连接器,只要支持SQL就可以和许多系统兼容。例如,你可以连接到MySQL数据库,PostgreSQL数据库或Oracle数据库,还可以连接到SQLite,我们的例子就是连接SQLite。数据库不仅仅是一些数据文件,而是一个系统,你有许多连接数据库的方式可供选择。
从SQL数据库中读取数据
从SQL数据库读取文件和之前看到的其他数据源没用什么不同,与其他数据源一样,我们先指定格式和选项,然后加载数据:
bash
val df = spark.read.format("jdbc").option("url","jdbc:mysql://master:3306/simian").option("driver","com.mysql.jdbc.Driver").option("user","root").option("password","123456").option("dbtable","anime_avg_play_count_by_rating").load()

查询下推
把过滤条件尽量下推到离数据源最近的地方执行,而不是等全部数据拉到内存再过滤
类比:去超市采购
不好方式:把所有商品全部装车运回家里,再挑选想要的
查询下推:直接在超市货架上先筛选,只买需要的商品带回家
并行读数据库
整本书中,我们介绍了数据划分及其在数据处理中的重要性,Spark有一个底层算法,可以将多个文件放入一个数据分片,或者反过来将一个文件划分到多个数据分片,这取决于文件大小以及文件类型和压缩格式是否允许划分。SQL数据库中也存在与文件一样的分片灵活性,但是你必须手动配置它,正如前面介绍的选项配置,你可以通过指定最大分区数量来限制并行读写的最大数量
bash
val df = spark.read.format("jdbc").option("url","jdbc:mysql://master:3306/simian").option("driver","com.mysql.jdbc.Driver").option("user","root").option("password","123456").option("dbtable","anime_avg_play_count_by_rating").option("numPartitions",10).load()
在这种情况下,由于数据不够多,所以仍然作为一个分区,但是,此配置可以帮助你确保在读取和写入数据时不会导致数据库过载
文本文件
Spark还支持读取纯文本文件,文件中的每一行将被解析为DataFrame中的一条记录,然后根据你的要求进行转换
读文本文件
读文本文件非常简单:只需指定类型为textFile即可,如果使用textFile,分区目录名将被忽略。如果要根据分区读取和写入文本文件,你应该使用text,它会在读写时考虑分区:
高级I/O概念
我们可以通过在写入之前控制数据分片来控制写入文件的并行度,还可以通过控制数据分桶(bucketing)和数据划分(partitioning)来控制特定的数据布局方式
可分割的文件类型和压缩
某些文件格式是"可分割的",因此Spark可以只获取该文件中满足查询条件
的某一个部分,无需读取整个文件,从而提高读取效率。此外,假设你使用的是Hadoop分布式文件系统(HDFS),则如果该文件
。与此同时需要进行压缩管理,并非所有的压缩格式都是可分割的。存储数据的方式对Spark作业稳定运行至关重要,推荐采用gzip压缩格式的Parquet文件格式
并行读数据
多个执行器不能同时读取同一个文件,但可以同时读取不同的文件。通常,这意味着当你从包含多个文件的文件夹中读取时,每个文件都将被视为DataFrame的一个分片,并由执行器并行读取,多余的文件会进入读取队列等候
并行写数据
写数据涉及的文件数量取决于DataFrame的分区数,默认情况是每个数据分片都会有一定的数据写入,这意味着虽然我们指定的是一个"文件",但实际上它是由一个文件夹中的多个文件组成,每个文件对应着一个数据分片


数据划分
数据划分工具支持你在写入数据时控制存储数据以及存储数据的位置。将文件写出时,你可以将列编码为文件夹,这使得你在之后读取时可跳过大量数据,只读入与问题相关的列数据而不必扫描整个数据集。所有基于文件的数据源都支持这些:


数据分桶
数据分桶是另一种文件组织方法,你可以使用该方法控制写入每个文件的数据,具有相同桶ID(哈希分桶的ID)的数据将放置到一个物理分区中,这样就可以避免在稍后读取数据时进行shuffle(洗牌)。根据你之后希望如何使用该数据来对数据进行预分区,就可以避免连接或聚合操作时执行代价很大的shuffle操作
与其根据某列进行数据划分,不如考虑对数据进行分桶,因为某列如果存在很多不同的值,就可能写出一大堆目录。这将创建一定数量的文件,数据也可以按照要求组织起来放置到这些"桶"中:
数据分桶仅支持Spark管理的表
管理文件大小
管理文件大小对数据写入不那么重要,但对之后的读取很重要。当你写入大量的小文件时,由于管理所有的这些小文件而产生很大的元数据开销。许多文件系统(如HDFS)都不能很好地处理大量的小文件,而Spark特别不适合处理小文件。你可能听说过"小文件问题",反之亦然,你也不希望文件太大,因为当你只需要其中几行时,必须读取整个数据块就会使效率低下。
Spark2.2中引入了一种更自动化地控制文件大小的新方法。之前介绍了输出文件数量,与写入时数据分片数量以及选取的划分列有关。现在,则可以利用另一个工具来限制输出文件大小,从而可以选出最优的文件大小。可以使用maxRecordsPerFile选项来指定每个文件的最大记录数,这使得你可以通过控制写入每个文件的记录数来控制文件大小。例如,如果你将写程序(write)的选项设置为df.write.option("maxRecordsPerFile",5000)Spark将确保每个文件最多包含5000条记录