• Apache Spark 2.2.0 中文文档


    Apache Spark 2.2.0 中文文档 - 快速入门 | ApacheCN

    Geekhoo 关注    

    2017.09.20 13:55* 字数 2062 阅读 13评论 0喜欢 1

    快速入门

    使用 Spark Shell 进行交互式分析

    基础

    Dataset 上的更多操作

    缓存

    独立的应用

    快速跳转

    本教程提供了如何使用 Spark 的快速入门介绍。首先通过运行 Spark 交互式的 shell(在 Python 或 Scala 中)来介绍 API, 然后展示如何使用 Java , Scala 和 Python 来编写应用程序。

    为了继续阅读本指南, 首先从Spark 官网下载 Spark 的发行包。因为我们将不使用 HDFS, 所以你可以下载一个任何 Hadoop 版本的软件包。

    请注意, 在 Spark 2.0 之前, Spark 的主要编程接口是弹性分布式数据集(RDD)。 在 Spark 2.0 之后, RDD 被 Dataset 替换, 它是像RDD 一样的 strongly-typed(强类型), 但是在引擎盖下更加优化。 RDD 接口仍然受支持, 您可以在RDD 编程指南中获得更完整的参考。 但是, 我们强烈建议您切换到使用 Dataset(数据集), 其性能要更优于 RDD。 请参阅SQL 编程指南获取更多有关 Dataset 的信息。

    使用 Spark Shell 进行交互式分析

    基础

    Spark shell 提供了一种来学习该 API 比较简单的方式, 以及一个强大的来分析数据交互的工具。在 Scala(运行于 Java 虚拟机之上, 并能很好的调用已存在的 Java 类库)或者 Python 中它是可用的。通过在 Spark 目录中运行以下的命令来启动它:

    Scala

    Python

    ./bin/spark-shell

    Spark 的主要抽象是一个称为 Dataset 的分布式的 item 集合。Datasets 可以从 Hadoop 的 InputFormats(例如 HDFS文件)或者通过其它的 Datasets 转换来创建。让我们从 Spark 源目录中的 README 文件来创建一个新的 Dataset:

    scala>valtextFile=spark.read.textFile("README.md")textFile:org.apache.spark.sql.Dataset[String]=[value:string]

    您可以直接从 Dataset 中获取 values(值), 通过调用一些 actions(动作), 或者 transform(转换)Dataset 以获得一个新的。更多细节, 请参阅API doc

    scala>textFile.count()// Number of items in this Datasetres0:Long=126// May be different from yours as README.md will change over time, similar to other outputsscala>textFile.first()// First item in this Datasetres1:String=#ApacheSpark

    现在让我们 transform 这个 Dataset 以获得一个新的。我们调用filter以返回一个新的 Dataset, 它是文件中的 items 的一个子集。

    scala>vallinesWithSpark=textFile.filter(line=>line.contains("Spark"))linesWithSpark:org.apache.spark.sql.Dataset[String]=[value:string]

    我们可以链式操作 transformation(转换)和 action(动作):

    scala>textFile.filter(line=>line.contains("Spark")).count()// How many lines contain "Spark"?res3:Long=15

    Dataset 上的更多操作

    Dataset actions(操作)和 transformations(转换)可以用于更复杂的计算。例如, 统计出现次数最多的单词 :

    Scala

    Python

    scala>textFile.map(line=>line.split(" ").size).reduce((a,b)=>if(a>b)aelseb)res4:Long=15

    第一个 map 操作创建一个新的 Dataset, 将一行数据 map 为一个整型值。在 Dataset 上调用reduce来找到最大的行计数。参数map与reduce是 Scala 函数(closures), 并且可以使用 Scala/Java 库的任何语言特性。例如, 我们可以很容易地调用函数声明, 我们将定义一个 max 函数来使代码更易于理解 :

    scala>importjava.lang.Mathimportjava.lang.Mathscala>textFile.map(line=>line.split(" ").size).reduce((a,b)=>Math.max(a,b))res5:Int=15

    一种常见的数据流模式是被 Hadoop 所推广的 MapReduce。Spark 可以很容易实现 MapReduce:

    scala>valwordCounts=textFile.flatMap(line=>line.split(" ")).groupByKey(identity).count()wordCounts:org.apache.spark.sql.Dataset[(String,Long)]=[value:string,count(1):bigint]

    在这里, 我们调用了flatMap以 transform 一个 lines 的 Dataset 为一个 words 的 Dataset, 然后结合groupByKey和count来计算文件中每个单词的 counts 作为一个 (String, Long) 的 Dataset pairs。要在 shell 中收集 word counts, 我们可以调用collect:

    scala>wordCounts.collect()res6:Array[(String,Int)]=Array((means,1),(under,2),(this,3),(Because,1),(Python,2),(agree,1),(cluster.,1),...)

    缓存

    Spark 还支持 Pulling(拉取)数据集到一个群集范围的内存缓存中。例如当查询一个小的 “hot” 数据集或运行一个像 PageRANK 这样的迭代算法时, 在数据被重复访问时是非常高效的。举一个简单的例子, 让我们标记我们的linesWithSpark数据集到缓存中:

    Scala

    Python

    scala>linesWithSpark.cache()res7:linesWithSpark.type=[value:string]scala>linesWithSpark.count()res8:Long=15scala>linesWithSpark.count()res9:Long=15

    使用 Spark 来探索和缓存一个 100 行的文本文件看起来比较愚蠢。有趣的是, 即使在他们跨越几十或者几百个节点时, 这些相同的函数也可以用于非常大的数据集。您也可以像编程指南. 中描述的一样通过连接bin/spark-shell到集群中, 使用交互式的方式来做这件事情。

    独立的应用

    假设我们希望使用 Spark API 来创建一个独立的应用程序。我们在 Scala(SBT), Java(Maven)和 Python 中练习一个简单应用程序。

    Scala

    Java

    Python

    我们将在 Scala 中创建一个非常简单的 Spark 应用程序 - 很简单的, 事实上, 它名为SimpleApp.scala:

    /* SimpleApp.scala */importorg.apache.spark.sql.SparkSessionobjectSimpleApp{defmain(args:Array[String]){vallogFile="YOUR_SPARK_HOME/README.md"// Should be some file on your systemvalspark=SparkSession.builder.appName("Simple Application").getOrCreate()vallogData=spark.read.textFile(logFile).cache()valnumAs=logData.filter(line=>line.contains("a")).count()valnumBs=logData.filter(line=>line.contains("b")).count()println(s"Lines with a:$numAs, Lines with b:$numBs")spark.stop()}}

    注意, 这个应用程序我们应该定义一个main()方法而不是去扩展scala.App。使用scala.App的子类可能不会正常运行。

    该程序仅仅统计了 Spark README 文件中每一行包含 ‘a’ 的数量和包含 ‘b’ 的数量。注意, 您需要将 YOUR_SPARK_HOME 替换为您 Spark 安装的位置。不像先前使用 spark shell 操作的示例, 它们初始化了它们自己的 SparkContext, 我们初始化了一个 SparkContext 作为应用程序的一部分。

    我们调用SparkSession.builder以构造一个 [[SparkSession]], 然后设置 application name(应用名称), 最终调用getOrCreate以获得 [[SparkSession]] 实例。

    我们的应用依赖了 Spark API, 所以我们将包含一个名为build.sbt的 sbt 配置文件, 它描述了 Spark 的依赖。该文件也会添加一个 Spark 依赖的 repository:

    name:="Simple Project"version:="1.0"scalaVersion:="2.11.8"libraryDependencies+="org.apache.spark"%%"spark-sql"%"2.2.0"

    为了让 sbt 正常的运行, 我们需要根据经典的目录结构来布局SimpleApp.scala和build.sbt文件。在成功后, 我们可以创建一个包含应用程序代码的 JAR 包, 然后使用spark-submit脚本来运行我们的程序。

    # Your directory layout should look like this$ find .../build.sbt./src./src/main./src/main/scala./src/main/scala/SimpleApp.scala# Package a jar containing your application$ sbt package...[info]Packaging{..}/{..}/target/scala-2.11/simple-project_2.11-1.0.jar# Use spark-submit to run your application$ YOUR_SPARK_HOME/bin/spark-submit--class"SimpleApp"--master local[4] arget/scala-2.11/simple-project_2.11-1.0.jar...Lines with a:46, Lines with b:23

    快速跳转

    恭喜您成功的运行了您的第一个 Spark 应用程序!

    更多 API 的深入概述, 从RDD programming guideSQL programming guide这里开始, 或者看看 “编程指南” 菜单中的其它组件。

    为了在集群上运行应用程序, 请前往deployment overview.

    最后, 在 Spark 的examples目录中包含了一些 (Scala,Java,Python,R) 示例。您可以按照如下方式来运行它们:

    # 针对 Scala 和 Java, 使用 run-example:./bin/run-example SparkPi# 针对 Python 示例, 直接使用 spark-submit:./bin/spark-submit examples/src/main/python/pi.py# 针对 R 示例, 直接使用 spark-submit:./bin/spark-submit examples/src/main/r/dataframe.R

    我们一直在努力

    apachecn/spark-doc-zh

     

     

    原文地址: http://spark.apachecn.org/docs/cn/2.2.0/quick-start.html

    网页地址: http://spark.apachecn.org/

    github: https://github.com/apachecn/spark-doc-zh(觉得不错麻烦给个 Star,谢谢!~)

     

     

     

     

     

     

     

     

     

     

    Apache Spark 2.2.0 中文文档 - Spark 编程指南 | ApacheCN

    Geekhoo 关注

    2017.09.21 16:23* 字数 11326 阅读 127评论 0喜欢 5

    Spark 编程指南

    概述

    Spark 依赖

    初始化 Spark

    使用 Shell

    弹性分布式数据集 (RDDs)

    并行集合

    外部 Datasets(数据集)

    RDD 操作

    基础

    传递 Functions(函数)给 Spark

    理解闭包

    示例

    Local(本地)vs. cluster(集群)模式

    打印 RDD 的 elements

    Key-Value Pairs 一起使用

    Transformations(转换)

    Actions(动作)

    Shuffle 操作

    Background(幕后)

    性能影响

    RDD Persistence(持久化)

    如何选择存储级别 ?

    删除数据

    共享变量

    广播变量

    Accumulators(累加器)

    部署应用到集群中

    Java / Scala 启动 Spark jobs

    单元测试

    快速链接

    概述

    在一个较高的概念上来说,每一个 Spark 应用程序由一个在集群上运行着用户的main函数和执行各种并行操作的driver program(驱动程序)组成。Spark 提供的主要抽象是一个弹性分布式数据集(RDD),它是可以执行并行操作且跨集群节点的元素的集合。RDD 可以从一个 Hadoop 文件系统(或者任何其它 Hadoop 支持的文件系统),或者一个在 driver program(驱动程序)中已存在的 Scala 集合,以及通过 transforming(转换)来创建一个 RDD。用户为了让它在整个并行操作中更高效的重用,也许会让 Spark persist(持久化)一个 RDD 到内存中。最后,RDD 会自动的从节点故障中恢复。

    Spark 中的第二个抽象是能够用于并行操作的shared variables(共享变量),默认情况下,当 Spark 的一个函数作为一组不同节点上的任务运行时,它将每一个变量的副本应用到每一个任务的函数中去。有时候,一个变量需要在整个任务中,或者在任务和 driver program(驱动程序)之间来共享。Spark 支持两种类型的共享变量 :broadcast variables(广播变量),它可以用于在所有节点上缓存一个值,和accumulators(累加器),他是一个只能被 “added(增加)” 的变量,例如 counters 和 sums。

    本指南介绍了每一种 Spark 所支持的语言的特性。如果您启动 Spark 的交互式 shell - 针对 Scala shell 使用bin/spark-shell或者针对 Python 使用bin/pyspark是很容易来学习的。

    Spark 依赖

    Scala

    Java

    Python

    Spark 2.2.0 默认使用 Scala 2.11 来构建和发布直到运行。(当然,Spark 也可以与其它的 Scala 版本一起运行)。为了使用 Scala 编写应用程序,您需要使用可兼容的 Scala 版本(例如,2.11.X)。

    要编写一个 Spark 的应用程序,您需要在 Spark 上添加一个 Maven 依赖。Spark 可以通过 Maven 中央仓库获取:

    groupId = org.apache.spark

    artifactId = spark-core_2.11

    version = 2.2.0

    此外,如果您想访问一个 HDFS 集群,则需要针对您的 HDFS 版本添加一个hadoop-client(hadoop 客户端)依赖。

    groupId = org.apache.hadoop

    artifactId = hadoop-client

    version =

    最后,您需要导入一些 Spark classes(类)到您的程序中去。添加下面几行:

    importorg.apache.spark.SparkContextimportorg.apache.spark.SparkConf

    (在 Spark 1.3.0 之前,您需要明确导入org.apache.spark.SparkContext._来启用必要的的隐式转换。)

    初始化 Spark

    Scala

    Java

    Python

    Spark 程序必须做的第一件事情是创建一个SparkContext对象,它会告诉 Spark 如何访问集群。要创建一个SparkContext,首先需要构建一个包含应用程序的信息的SparkConf对象。

    每一个 JVM 可能只能激活一个 SparkContext 对象。在创新一个新的对象之前,必须调用stop()该方法停止活跃的 SparkContext。

    valconf=newSparkConf().setAppName(appName).setMaster(master)newSparkContext(conf)

    这个appName参数是一个在集群 UI 上展示应用程序的名称。master是一个Spark, Mesos 或 YARN 的 cluster URL,或者指定为在 local mode(本地模式)中运行的 “local” 字符串。在实际工作中,当在集群上运行时,您不希望在程序中将 master 给硬编码,而是用使用spark-submit启动应用并且接收它。然而,对于本地测试和单元测试,您可以通过 “local” 来运行 Spark 进程。

    使用 Shell

    Scala

    Python

    Spark Shell 中,一个特殊的 interpreter-aware(可用的解析器)SparkContext 已经为您创建好了,称之为sc的变量。创建您自己的 SparkContext 将不起作用。您可以使用--master参数设置这个 SparkContext 连接到哪一个 master 上,并且您可以通过--jars参数传递一个逗号分隔的列表来添加 JARs 到 classpath 中。也可以通过--packages参数应用一个用逗号分隔的 maven coordinates(maven 坐标)方式来添加依赖(例如,Spark 包)到您的 shell session 中去。任何额外存在且依赖的仓库(例如 Sonatype)可以传递到--repositories参数。例如,要明确使用四个核(CPU)来运行bin/spark-shell,使用:

    $ ./bin/spark-shell --master local[4]

    或者, 也可以添加code.jar到它的 classpath 中去, 使用:

    $ ./bin/spark-shell --master local[4]--jars code.jar

    为了包含一个依赖,使用 Maven 坐标:

    $ ./bin/spark-shell --master local[4]--packages"org.example:example:0.1"

    有关选项的完整列表, 请运行spark-shell --help. 在幕后,spark-shell调用了常用的spark-submit脚本.

    弹性分布式数据集 (RDDs)

    Spark 主要以一个弹性分布式数据集(RDD)的概念为中心,它是一个容错且可以执行并行操作的元素的集合。有两种方法可以创建 RDD : 在你的 driver program(驱动程序)中parallelizing一个已存在的集合,或者在外部存储系统中引用一个数据集,例如,一个共享文件系统,HDFS,HBase,或者提供 Hadoop InputFormat 的任何数据源。

    并行集合

    Scala

    Java

    Python

    可以在您的 driver program (a ScalaSeq) 中已存在的集合上通过调用SparkContext的parallelize方法来创建并行集合。该集合的元素从一个可以并行操作的 distributed dataset(分布式数据集)中复制到另一个 dataset(数据集)中去。例如,这里是一个如何去创建一个保存数字 1 ~ 5 的并行集合。

    valdata=Array(1,2,3,4,5)valdistData=sc.parallelize(data)

    在创建后,该 distributed dataset(分布式数据集)(distData)可以并行的执行操作。例如,我们可以调用distData.reduce((a, b) => a + b) 来合计数组中的元素。后面我们将介绍 distributed dataset(分布式数据集)上的操作。

    并行集合中一个很重要参数是partitions(分区)的数量,它可用来切割 dataset(数据集)。Spark 将在集群中的每一个分区上运行一个任务。通常您希望群集中的每一个 CPU 计算 2-4 个分区。一般情况下,Spark 会尝试根据您的群集情况来自动的设置的分区的数量。当然,您也可以将分区数作为第二个参数传递到parallelize(e.g.sc.parallelize(data, 10)) 方法中来手动的设置它。注意: 代码中的一些地方会使用 term slices (a synonym for partitions) 以保持向后兼容.

    外部 Datasets(数据集)

    Scala

    Java

    Python

    Spark 可以从 Hadoop 所支持的任何存储源中创建 distributed dataset(分布式数据集),包括本地文件系统,HDFS,Cassandra,HBase,Amazon S3等等。 Spark 支持文本文件,SequenceFiles,以及任何其它的 HadoopInputFormat

    可以使用SparkContext的textFile方法来创建文本文件的 RDD。此方法需要一个文件的 URI(计算机上的本地路径 ,hdfs://,s3n://等等的 URI),并且读取它们作为一个 lines(行)的集合。下面是一个调用示例:

    scala>valdistFile=sc.textFile("data.txt")distFile:org.apache.spark.rdd.RDD[String]=data.txtMapPartitionsRDD[10]attextFileat:26

    在创建后,distFile可以使用 dataset(数据集)的操作。例如,我们可以使用下面的 map 和 reduce 操作来合计所有行的数量:distFile.map(s => s.length).reduce((a, b) => a + b)。

    使用 Spark 读取文件时需要注意:

    如果使用本地文件系统的路径,所工作节点的相同访问路径下该文件必须可以访问。复制文件到所有工作节点上,或着使用共享的网络挂载文件系统。

    所有 Spark 基于文件的 input 方法, 包括textFile, 支持在目录上运行, 压缩文件, 和通配符. 例如, 您可以使用textFile("/my/directory"),textFile("/my/directory/*.txt"), andtextFile("/my/directory/*.gz").

    textFile方法也可以通过第二个可选的参数来控制该文件的分区数量. 默认情况下, Spark 为文件的每一个 block(块)创建的一 个 partition 分区(HDFS 中块大小默认是 128MB),当然你也可以通过传递一个较大的值来要求一个较高的分区数量。请注意,分区的数量不能够小于块的数量。

    除了文本文件之外,Spark 的 Scala API 也支持一些其它的数据格式:

    SparkContext.wholeTextFiles可以读取包含多个小文本文件的目录, 并且将它们作为一个 (filename, content) pairs 来返回. 这与textFile相比, 它的每一个文件中的每一行将返回一个记录. 分区由数据量来确定, 某些情况下, 可能导致分区太少. 针对这些情况,wholeTextFiles在第二个位置提供了一个可选的参数用户控制分区的最小数量.

    针对SequenceFiles, 使用 SparkContext 的sequenceFile[K, V]方法,其中K和V指的是文件中 key 和 values 的类型. 这些应该是 Hadoop 的Writable接口的子类, 像IntWritableandText. 此外, Spark 可以让您为一些常见的 Writables 指定原生类型; 例如,sequenceFile[Int, String]会自动读取 IntWritables 和 Texts.

    针对其它的 Hadoop InputFormats, 您可以使用SparkContext.hadoopRDD方法, 它接受一个任意的JobConf和 input format class, key class 和 value class. 通过相同的方法你可以设置你的 input source(输入源). 你还可以针对 InputFormats 使用基于 “new” MapReduce API (org.apache.hadoop.mapreduce) 的SparkContext.newAPIHadoopRDD.

    RDD.saveAsObjectFile和SparkContext.objectFile支持使用简单的序列化的 Java objects 来保存 RDD. 虽然这不像 Avro 这种专用的格式一样高效,但其提供了一种更简单的方式来保存任何的 RDD。.

    RDD 操作

    RDDs support 两种类型的操作:transformations(转换), 它会在一个已存在的 dataset 上创建一个新的 dataset, 和actions(动作), 将在 dataset 上运行的计算后返回到 driver 程序. 例如,map是一个通过让每个数据集元素都执行一个函数,并返回的新 RDD 结果的 transformation,reducereduce 通过执行一些函数,聚合 RDD 中所有元素,并将最终结果给返回驱动程序(虽然也有一个并行reduceByKey返回一个分布式数据集)的 action.

    Spark 中所有的 transformations 都是lazy(懒加载的), 因此它不会立刻计算出结果. 相反, 他们只记得应用于一些基本数据集的转换 (例如. 文件). 只有当需要返回结果给驱动程序时,transformations 才开始计算. 这种设计使 Spark 的运行更高效. 例如, 我们可以了解到,map所创建的数据集将被用在reduce中,并且只有reduce的计算结果返回给驱动程序,而不是映射一个更大的数据集.

    默认情况下,每次你在 RDD 运行一个 action 的时, 每个 transformed RDD 都会被重新计算。但是,您也可用persist(或cache) 方法将 RDD persist(持久化)到内存中;在这种情况下,Spark 为了下次查询时可以更快地访问,会把数据保存在集群上。此外,还支持持续持久化 RDDs 到磁盘,或复制到多个结点。

    基础

    Scala

    Java

    Python

    为了说明 RDD 基础,请思考下面这个的简单程序:

    vallines=sc.textFile("data.txt")vallineLengths=lines.map(s=>s.length)valtotalLength=lineLengths.reduce((a,b)=>a+b)

    第一行从外部文件中定义了一个基本的 RDD,但这个数据集并未加载到内存中或即将被行动:line仅仅是一个类似指针的东西,指向该文件. 第二行定义了lineLengths作为maptransformation 的结果。请注意,由于laziness(延迟加载)lineLengths不会被立即计算. 最后,我们运行reduce,这是一个 action。此时,Spark 分发计算任务到不同的机器上运行,每台机器都运行在 map 的一部分并本地运行 reduce,仅仅返回它聚合后的结果给驱动程序.

    如果我们也希望以后再次使用lineLengths,我们还可以添加:

    lineLengths.persist()

    reduce之前,这将导致lineLengths在第一次计算之后就被保存在 memory 中。

    传递 Functions(函数)给 Spark

    Scala

    Java

    Python

    driver 程序在集群上运行时,Spark 的 API 在很大程度上依赖于传递函数。有 2 种推荐的方式来做到这一点:

    Anonymous function syntax(匿名函数语法), 它可以用于短的代码片断.

    在全局单例对象中的静态方法. 例如, 您可以定义object MyFunctions然后传递MyFunctions.func1, 如下:

    objectMyFunctions{deffunc1(s:String):String={...}}myRdd.map(MyFunctions.func1)

    请注意,虽然也有可能传递一个类的实例(与单例对象相反)的方法的引用,这需要发送整个对象,包括类中其它方法。例如,考虑:

    classMyClass{deffunc1(s:String):String={...}defdoStuff(rdd:RDD[String]):RDD[String]={rdd.map(func1)}}

    这里,如果我们创建一个MyClass的实例,并调用doStuff,在map内有MyClass实例的func1方法的引用,所以整个对象需要被发送到集群的。它类似于rdd.map(x => this.func1(x))

    类似的方式,访问外部对象的字段将引用整个对象:

    classMyClass{valfield="Hello"defdoStuff(rdd:RDD[String]):RDD[String]={rdd.map(x=>field+x)}}

    相当于写rdd.map(x => this.field + x), 它引用this所有的东西. 为了避免这个问题, 最简单的方式是复制field到一个本地变量,而不是外部访问它:

    defdoStuff(rdd:RDD[String]):RDD[String]={valfield_=this.fieldrdd.map(x=>field_+x)}

    理解闭包

    在集群中执行代码时,一个关于 Spark 更难的事情是理解变量和方法的范围和生命周期. 修改其范围之外的变量 RDD 操作可以混淆的常见原因。在下面的例子中,我们将看一下使用的foreach()代码递增累加计数器,但类似的问题,也可能会出现其他操作上.

    示例

    考虑一个简单的 RDD 元素求和,以下行为可能不同,具体取决于是否在同一个 JVM 中执行. 一个常见的例子是当 Spark 运行在local本地模式(--master = local[n])时,与部署 Spark 应用到群集(例如,通过 spark-submit 到 YARN):

    Scala

    Java

    Python

    varcounter=0varrdd=sc.parallelize(data)// Wrong: Don't do this!!rdd.foreach(x=>counter+=x)println("Counter value: "+counter)

    Local(本地)vs. cluster(集群)模式

    上面的代码行为是不确定的,并且可能无法按预期正常工作。执行作业时,Spark 会分解 RDD 操作到每个 executor 中的 task 里。在执行之前,Spark 计算任务的closure(闭包)。而闭包是在 RDD 上的 executor 必须能够访问的变量和方法(在此情况下的foreach())。闭包被序列化并被发送到每个执行器。

    闭包的变量副本发给每个counter,当counter被foreach函数引用的时候,它已经不再是 driver node 的counter了。虽然在 driver node 仍然有一个 counter 在内存中,但是对 executors 已经不可见。executor 看到的只是序列化的闭包一个副本。所以counter最终的值还是 0,因为对counter所有的操作均引用序列化的 closure 内的值。

    local本地模式,在某些情况下的foreach功能实际上是同一 JVM 上的驱动程序中执行,并会引用同一个原始的counter计数器,实际上可能更新.

    为了确保这些类型的场景明确的行为应该使用的Accumulator累加器。当一个执行的任务分配到集群中的各个 worker 结点时,Spark 的累加器是专门提供安全更新变量的机制。本指南的累加器的部分会更详细地讨论这些。

    在一般情况下,closures - constructs 像循环或本地定义的方法,不应该被用于改动一些全局状态。Spark 没有规定或保证突变的行为,以从封闭件的外侧引用的对象。一些代码,这可能以本地模式运行,但是这只是偶然和这样的代码如预期在分布式模式下不会表现。如果需要一些全局的聚合功能,应使用 Accumulator(累加器)。

    打印 RDD 的 elements

    另一种常见的语法用于打印 RDD 的所有元素使用rdd.foreach(println)或rdd.map(println)。在一台机器上,这将产生预期的输出和打印 RDD 的所有元素。然而,在集群cluster模式下,stdout输出正在被执行写操作 executors 的stdout代替,而不是在一个驱动程序上,因此stdout的driver程序不会显示这些!要打印driver程序的所有元素,可以使用的collect()方法首先把 RDD 放到 driver 程序节点上:rdd.collect().foreach(println)。这可能会导致 driver 程序耗尽内存,虽说,因为collect()获取整个 RDD 到一台机器; 如果你只需要打印 RDD 的几个元素,一个更安全的方法是使用take():rdd.take(100).foreach(println)。

    Key-Value Pairs 一起使用

    Scala

    Java

    Python

    虽然大多数 Spark 操作工作在包含任何类型对象的 RDDs 上,只有少数特殊的操作可用于 Key-Value 对的 RDDs. 最常见的是分布式 “shuffle” 操作,如通过元素的 key 来进行 grouping 或 aggregating 操作.

    Scala 中,这些操作在 RDD 上是自动可用,它包含了Tuple2objects (the built-in tuples in the language, created by simply writing(a, b)). 在PairRDDFunctionsclass 中该 key-value pair 操作是可用的, 其中围绕 tuple 的 RDD 进行自动封装.

    例如,下面的代码使用的Key-Value对的reduceByKey操作统计文本文件中每一行出现了多少次:

    vallines=sc.textFile("data.txt")valpairs=lines.map(s=>(s,1))valcounts=pairs.reduceByKey((a,b)=>a+b)

    我们也可以使用counts.sortByKey(),例如,在对按字母顺序排序,最后counts.collect()把他们作为一个数据对象返回给 driver 程序。

    Note(注意):当在 key-value pair 操作中使用自定义的 objects 作为 key 时, 您必须确保有一个自定义的equals()方法有一个hashCode()方法相匹配. 有关详情, 请参阅Object.hashCode() documentation中列出的约定.

    Transformations(转换)

    下表列出了一些 Spark 常用的 transformations(转换). 详情请参考 RDD API 文档 (Scala,Java,Python,R) 和 pair RDD 函数文档 (Scala,Java).

    Transformation(转换)Meaning(含义)

    map(func)返回一个新的 distributed dataset(分布式数据集),它由每个 source(数据源)中的元素应用一个函数func来生成.

    filter(func)返回一个新的 distributed dataset(分布式数据集),它由每个 source(数据源)中应用一个函数func且返回值为 true 的元素来生成.

    flatMap(func)与 map 类似,但是每一个输入的 item 可以被映射成 0 个或多个输出的 items(所以func应该返回一个 Seq 而不是一个单独的 item).

    mapPartitions(func) map 类似,但是单独的运行在在每个 RDD 的 partition(分区,block)上,所以在一个类型为 T 的 RDD 上运行时func必须是 Iterator => Iterator 类型.

    mapPartitionsWithIndex(func)与 mapPartitions 类似,但是也需要提供一个代表 partition 的 index(索引)的 interger value(整型值)作为参数的func,所以在一个类型为 T 的 RDD 上运行时func必须是 (Int, Iterator) => Iterator 类型.

    sample(withReplacement,fraction,seed)样本数据,设置是否放回(withReplacement), 采样的百分比(fraction)、使用指定的随机数生成器的种子(seed).

    union(otherDataset)反回一个新的 dataset,它包含了 source dataset(源数据集)和 otherDataset(其它数据集)的并集.

    intersection(otherDataset)返回一个新的 RDD,它包含了 source dataset(源数据集)和 otherDataset(其它数据集)的交集.

    distinct([numTasks]))返回一个新的 dataset,它包含了 source dataset(源数据集)中去重的元素.

    groupByKey([numTasks])在一个 (K, V) pair 的 dataset 上调用时,返回一个 (K, Iterable) .

    Note:如果分组是为了在每一个 key 上执行聚合操作(例如,sum 或 average),此时使用reduceByKey或aggregateByKey来计算性能会更好.

    Note:默认情况下,并行度取决于父 RDD 的分区数。可以传递一个可选的numTasks参数来设置不同的任务数.

    reduceByKey(func, [numTasks]) (K, V) pairs 的 dataset 上调用时, 返回 dataset of (K, V) pairs 的 dataset, 其中的 values 是针对每个 key 使用给定的函数func来进行聚合的, 它必须是 type (V,V) => V 的类型. 像groupByKey一样, reduce tasks 的数量是可以通过第二个可选的参数来配置的.

    aggregateByKey(zeroValue)(seqOp,combOp, [numTasks]) (K, V) pairs 的 dataset 上调用时, 返回 (K, U) pairs 的 dataset,其中的 values 是针对每个 key 使用给定的 combine 函数以及一个 neutral "0" 值来进行聚合的. 允许聚合值的类型与输入值的类型不一样, 同时避免不必要的配置. 像groupByKey一样, reduce tasks 的数量是可以通过第二个可选的参数来配置的.

    sortByKey([ascending], [numTasks])在一个 (K, V) pair 的 dataset 上调用时,其中的 K 实现了 Ordered,返回一个按 keys 升序或降序的 (K, V) pairs 的 dataset, 由 boolean 类型的ascending参数来指定.

    join(otherDataset, [numTasks])在一个 (K, V) 和 (K, W) 类型的 dataset 上调用时,返回一个 (K, (V, W)) pairs 的 dataset,它拥有每个 key 中所有的元素对。Outer joins 可以通过leftOuterJoin,rightOuterJoin和fullOuterJoin来实现.

    cogroup(otherDataset, [numTasks])在一个 (K, V) 和的 dataset 上调用时,返回一个 (K, (Iterable, Iterable)) tuples 的 dataset. 这个操作也调用了groupWith.

    cartesian(otherDataset)在一个 T 和 U 类型的 dataset 上调用时,返回一个 (T, U) pairs 类型的 dataset(所有元素的 pairs,即笛卡尔积).

    pipe(command,[envVars])通过使用 shell 命令来将每个 RDD 的分区给 Pipe。例如,一个 Perl 或 bash 脚本。RDD 的元素会被写入进程的标准输入(stdin),并且 lines(行)输出到它的标准输出(stdout)被作为一个字符串型 RDD 的 string 返回.

    coalesce(numPartitions)Decrease(降低)RDD 中 partitions(分区)的数量为 numPartitions。对于执行过滤后一个大的 dataset 操作是更有效的.

    repartition(numPartitions)Reshuffle(重新洗牌)RDD 中的数据以创建或者更多的 partitions(分区)并将每个分区中的数据尽量保持均匀. 该操作总是通过网络来 shuffles 所有的数据.

    repartitionAndSortWithinPartitions(partitioner)根据给定的 partitioner(分区器)对 RDD 进行重新分区,并在每个结果分区中,按照 key 值对记录排序。这比每一个分区中先调用repartition然后再 sorting(排序)效率更高,因为它可以将排序过程推送到 shuffle 操作的机器上进行.

    Actions(动作)

    下表列出了一些 Spark 常用的 actions 操作。详细请参考 RDD API 文档 (Scala,Java,Python,R)

    pair RDD 函数文档 (Scala,Java).

    Action(动作)Meaning(含义)

    reduce(func)使用函数func聚合 dataset 中的元素,这个函数func输入为两个元素,返回为一个元素。这个函数应该是可交换(commutative )和关联(associative)的,这样才能保证它可以被并行地正确计算.

    collect()在 driver 程序中,以一个 array 数组的形式返回 dataset 的所有元素。这在过滤器(filter)或其他操作(other operation)之后返回足够小(sufficiently small)的数据子集通常是有用的.

    count()返回 dataset 中元素的个数.

    first()返回 dataset 中的第一个元素(类似于 take(1).

    take(n)将数据集中的前n个元素作为一个 array 数组返回.

    takeSample(withReplacement,num, [seed])对一个 dataset 进行随机抽样,返回一个包含num个随机抽样(random sample)元素的数组,参数 withReplacement 指定是否有放回抽样,参数 seed 指定生成随机数的种子.

    takeOrdered(n,[ordering])返回 RDD 按自然顺序(natural order)或自定义比较器(custom comparator)排序后的前n个元素.

    saveAsTextFile(path)将 dataset 中的元素以文本文件(或文本文件集合)的形式写入本地文件系统、HDFS 或其它 Hadoop 支持的文件系统中的给定目录中。Spark 将对每个元素调用 toString 方法,将数据元素转换为文本文件中的一行记录.

    saveAsSequenceFile(path)

    (Java and Scala)将 dataset 中的元素以 Hadoop SequenceFile 的形式写入到本地文件系统、HDFS 或其它 Hadoop 支持的文件系统指定的路径中。该操作可以在实现了 Hadoop 的 Writable 接口的键值对(key-value pairs)的 RDD 上使用。在 Scala 中,它还可以隐式转换为 Writable 的类型(Spark 包括了基本类型的转换,例如 Int, Double, String 等等).

    saveAsObjectFile(path)

    (Java and Scala)使用 Java 序列化(serialization)以简单的格式(simple format)编写数据集的元素,然后使用SparkContext.objectFile()进行加载.

    countByKey()仅适用于(K,V)类型的 RDD 。返回具有每个 key 的计数的 (K , Int)pairs 的 hashmap.

    foreach(func)对 dataset 中每个元素运行函数func。这通常用于副作用(side effects),例如更新一个Accumulator(累加器)或与外部存储系统(external storage systems)进行交互。Note:修改除foreach()之外的累加器以外的变量(variables)可能会导致未定义的行为(undefined behavior)。详细介绍请阅读Understanding closures(理解闭包)部分.

    Spark RDD API 还暴露了一些 actions(操作)的异步版本,例如针对foreach的foreachAsync,它们会立即返回一个FutureAction到调用者,而不是在完成 action 时阻塞。 这可以用于管理或等待 action 的异步执行。.

    Shuffle 操作

    Spark 里的某些操作会触发 shuffle。shuffle 是spark 重新分配数据的一种机制,使得这些数据可以跨不同的区域进行分组。这通常涉及在 executors 和 机器之间拷贝数据,这使得 shuffle 成为一个复杂的、代价高的操作。

    Background(幕后)

    为了明白reduceByKey操作的过程,我们以reduceByKey为例。reduceBykey 操作产生一个新的 RDD,其中 key 所有相同的的值组合成为一个 tuple - key 以及与 key 相关联的所有值在 reduce 函数上的执行结果。面临的挑战是,一个 key 的所有值不一定都在一个同一个 paritition 分区里,甚至是不一定在同一台机器里,但是它们必须共同被计算。

    spark 里,特定的操作需要数据不跨分区分布。在计算期间,一个任务在一个分区上执行,为了所有数据都在单个reduceByKey的 reduce 任务上运行,我们需要执行一个 all-to-all 操作。它必须从所有分区读取所有的 key 和 key对应的所有的值,并且跨分区聚集去计算每个 key 的结果 - 这个过程就叫做shuffle.。

    Although the set of elements in each partition of newly shuffled data will be deterministic, and so is the ordering of partitions themselves, the ordering of these elements is not. If one desires predictably ordered data following shuffle then it’s possible to use:

    尽管每个分区新 shuffle 的数据集将是确定的,分区本身的顺序也是这样,但是这些数据的顺序是不确定的。如果希望 shuffle 后的数据是有序的,可以使用:

    mapPartitions对每个 partition 分区进行排序,例如,.sorted

    repartitionAndSortWithinPartitions在分区的同时对分区进行高效的排序.

    sortBy对 RDD 进行全局的排序

    触发的 shuffle 操作包括repartition操作,如repartitioncoalesce,‘ByKey操作 (除了 counting 之外) 像groupByKeyreduceByKey, 和join操作, 像cogroupjoin.

    性能影响

    Shuffle是一个代价比较高的操作,它涉及磁盘 I/O、数据序列化、网络 I/O。为了准备 shuffle 操作的数据,Spark 启动了一系列的任务,map任务组织数据,reduce完成数据的聚合。这些术语来自 MapReduce,跟 Spark 的map操作和reduce操作没有关系。

    在内部,一个 map 任务的所有结果数据会保存在内存,直到内存不能全部存储为止。然后,这些数据将基于目标分区进行排序并写入一个单独的文件中。在 reduce 时,任务将读取相关的已排序的数据块。

    某些 shuffle 操作会大量消耗堆内存空间,因为 shuffle 操作在数据转换前后,需要在使用内存中的数据结构对数据进行组织。需要特别说明的是,reduceByKey和aggregateByKey在 map 时会创建这些数据结构,'ByKey操作在 reduce 时创建这些数据结构。当内存满的时候,Spark 会把溢出的数据存到磁盘上,这将导致额外的磁盘 I/O 开销和垃圾回收开销的增加。

    shuffle 操作还会在磁盘上生成大量的中间文件。在 Spark 1.3 中,这些文件将会保留至对应的 RDD 不在使用并被垃圾回收为止。这么做的好处是,如果在 Spark 重新计算 RDD 的血统关系(lineage)时,shuffle 操作产生的这些中间文件不需要重新创建。如果 Spark 应用长期保持对 RDD 的引用,或者垃圾回收不频繁,这将导致垃圾回收的周期比较长。这意味着,长期运行 Spark 任务可能会消耗大量的磁盘空间。临时数据存储路径可以通过 SparkContext 中设置参数spark.local.dir进行配置。

    shuffle 操作的行为可以通过调节多个参数进行设置。详细的说明请看Spark 配置指南中的 “Shuffle 行为” 部分。

    RDD Persistence(持久化)

    Spark 中一个很重要的能力是将数据persisting持久化(或称为caching缓存),在多个操作间都可以访问这些持久化的数据。当持久化一个 RDD 时,每个节点的其它分区都可以使用 RDD 在内存中进行计算,在该数据上的其他 action 操作将直接使用内存中的数据。这样会让以后的 action 操作计算速度加快(通常运行速度会加速 10 倍)。缓存是迭代算法和快速的交互式使用的重要工具。

    RDD 可以使用persist()方法或cache()方法进行持久化。数据将会在第一次 action 操作时进行计算,并缓存在节点的内存中。Spark 的缓存具有容错机制,如果一个缓存的 RDD 的某个分区丢失了,Spark 将按照原来的计算过程,自动重新计算并进行缓存。

    另外,每个持久化的 RDD 可以使用不同的storage level存储级别进行缓存,例如,持久化到磁盘、已序列化的 Java 对象形式持久化到内存(可以节省空间)、跨节点间复制、以 off-heap 的方式存储在 Tachyon。这些存储级别通过传递一个StorageLevel对象 (Scala,Java,Python) 给persist()方法进行设置。cache()方法是使用默认存储级别的快捷设置方法,默认的存储级别是StorageLevel.MEMORY_ONLY(将反序列化的对象存储到内存中)。详细的存储级别介绍如下:

    Storage Level(存储级别)Meaning(含义)

    MEMORY_ONLY将 RDD 以反序列化的 Java 对象的形式存储在 JVM 中. 如果内存空间不够,部分数据分区将不再缓存,在每次需要用到这些数据时重新进行计算. 这是默认的级别.

    MEMORY_AND_DISK将 RDD 以反序列化的 Java 对象的形式存储在 JVM 中。如果内存空间不够,将未缓存的数据分区存储到磁盘,在需要使用这些分区时从磁盘读取.

    MEMORY_ONLY_SER

    (Java and Scala)将 RDD 以序列化的 Java 对象的形式进行存储(每个分区为一个 byte 数组)。这种方式会比反序列化对象的方式节省很多空间,尤其是在使用fast serializer时会节省更多的空间,但是在读取时会增加 CPU 的计算负担.

    MEMORY_AND_DISK_SER

    (Java and Scala)类似于 MEMORY_ONLY_SER ,但是溢出的分区会存储到磁盘,而不是在用到它们时重新计算.

    DISK_ONLY只在磁盘上缓存 RDD.

    MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc.与上面的级别功能相同,只不过每个分区在集群中两个节点上建立副本.

    OFF_HEAP (experimental 实验性)类似于 MEMORY_ONLY_SER, 但是将数据存储在off-heap memory. 这需要启用 off-heap 内存.

    Note:在 Python 中, stored objects will 总是使用Picklelibrary 来序列化对象, 所以无论你选择序列化级别都没关系. 在 Python 中可用的存储级别有MEMORY_ONLY,MEMORY_ONLY_2,MEMORY_AND_DISK,MEMORY_AND_DISK_2,DISK_ONLY, 和DISK_ONLY_2.

    shuffle 操作中(例如reduceByKey),即便是用户没有调用persist方法,Spark 也会自动缓存部分中间数据.这么做的目的是,在 shuffle 的过程中某个节点运行失败时,不需要重新计算所有的输入数据。如果用户想多次使用某个 RDD,强烈推荐在该 RDD 上调用 persist 方法.

    如何选择存储级别 ?

    Spark 的存储级别的选择,核心问题是在 memory 内存使用率和 CPU 效率之间进行权衡。建议按下面的过程进行存储级别的选择:

    如果您的 RDD 适合于默认存储级别 (MEMORY_ONLY), leave them that way. 这是CPU效率最高的选项,允许RDD上的操作尽可能快地运行.

    如果不是, 试着使用MEMORY_ONLY_SER和selecting a fast serialization library以使对象更加节省空间,但仍然能够快速访问。 (Java和Scala)

    不要溢出到磁盘,除非计算您的数据集的函数是昂贵的, 或者它们过滤大量的数据. 否则, 重新计算分区可能与从磁盘读取分区一样快.

    如果需要快速故障恢复,请使用复制的存储级别 (e.g. 如果使用Spark来服务 来自网络应用程序的请求).All存储级别通过重新计算丢失的数据来提供完整的容错能力,但复制的数据可让您继续在 RDD 上运行任务,而无需等待重新计算一个丢失的分区.

    删除数据

    Spark 会自动监视每个节点上的缓存使用情况,并使用 least-recently-used(LRU)的方式来丢弃旧数据分区。 如果您想手动删除 RDD 而不是等待它掉出缓存,使用RDD.unpersist()方法。

    共享变量

    通常情况下,一个传递给 Spark 操作(例如map或reduce)的函数 func 是在远程的集群节点上执行的。该函数 func 在多个节点执行过程中使用的变量,是同一个变量的多个副本。这些变量的以副本的方式拷贝到每个机器上,并且各个远程机器上变量的更新并不会传播回 driver program(驱动程序)。通用且支持 read-write(读-写) 的共享变量在任务间是不能胜任的。所以,Spark 提供了两种特定类型的共享变量 : broadcast variables(广播变量)和 accumulators(累加器)。

    广播变量

    Broadcast variables(广播变量)允许程序员将一个 read-only(只读的)变量缓存到每台机器上,而不是给任务传递一个副本。它们是如何来使用呢,例如,广播变量可以用一种高效的方式给每个节点传递一份比较大的 input dataset(输入数据集)副本。在使用广播变量时,Spark 也尝试使用高效广播算法分发 broadcast variables(广播变量)以降低通信成本。

    Spark 的 action(动作)操作是通过一系列的 stage(阶段)进行执行的,这些 stage(阶段)是通过分布式的 “shuffle” 操作进行拆分的。Spark 会自动广播出每个 stage(阶段)内任务所需要的公共数据。这种情况下广播的数据使用序列化的形式进行缓存,并在每个任务运行前进行反序列化。这也就意味着,只有在跨越多个 stage(阶段)的多个任务会使用相同的数据,或者在使用反序列化形式的数据特别重要的情况下,使用广播变量会有比较好的效果。

    广播变量通过在一个变量v上调用SparkContext.broadcast(v)方法来进行创建。广播变量是v的一个 wrapper(包装器),可以通过调用value方法来访问它的值。代码示例如下:

    Scala

    Java

    Python

    scala>valbroadcastVar=sc.broadcast(Array(1,2,3))broadcastVar:org.apache.spark.broadcast.Broadcast[Array[Int]]=Broadcast(0)scala>broadcastVar.valueres0:Array[Int]=Array(1,2,3)

    在创建广播变量之后,在集群上执行的所有的函数中,应该使用该广播变量代替原来的v值,所以节点上的v最多分发一次。另外,对象v在广播后不应该再被修改,以保证分发到所有的节点上的广播变量具有同样的值(例如,如果以后该变量会被运到一个新的节点)。

    Accumulators(累加器)

    Accumulators(累加器)是一个仅可以执行 “added”(添加)的变量来通过一个关联和交换操作,因此可以高效地执行支持并行。累加器可以用于实现 counter( 计数,类似在 MapReduce 中那样)或者 sums(求和)。原生 Spark 支持数值型的累加器,并且程序员可以添加新的支持类型。

    作为一个用户,您可以创建 accumulators(累加器)并且重命名. 如下图所示, 一个命名的 accumulator 累加器(在这个例子中是counter)将显示在 web UI 中,用于修改该累加器的阶段。 Spark 在 “Tasks” 任务表中显示由任务修改的每个累加器的值.

     

    UI 中跟踪累加器可以有助于了解运行阶段的进度(注: 这在 Python 中尚不支持).

    Scala

    Java

    Python

    可以通过调用SparkContext.longAccumulator()或SparkContext.doubleAccumulator()方法创建数值类型的accumulator(累加器)以分别累加 Long 或 Double 类型的值。集群上正在运行的任务就可以使用add方法来累计数值。然而,它们不能够读取它的值。只有 driver program(驱动程序)才可以使用value方法读取累加器的值。

    下面的代码展示了一个 accumulator(累加器)被用于对一个数组中的元素求和:

    scala>valaccum=sc.longAccumulator("My Accumulator")accum:org.apache.spark.util.LongAccumulator=LongAccumulator(id:0,name:Some(MyAccumulator),value:0)scala>sc.parallelize(Array(1,2,3,4)).foreach(x=>accum.add(x))...10/09/2918:41:08INFOSparkContext:Tasksfinishedin0.317106sscala>accum.valueres2:Long=10

    虽然此代码使用 Long 类型的累加器的内置支持, 但是开发者通过AccumulatorV2它的子类来创建自己的类型. AccumulatorV2 抽象类有几个需要 override(重写)的方法:reset方法可将累加器重置为 0,add方法可将其它值添加到累加器中,merge方法可将其他同样类型的累加器合并为一个. 其他需要重写的方法可参考API documentation. 例如, 假设我们有一个表示数学上 vectors(向量)的MyVector类,我们可以写成:

    classVectorAccumulatorV2extendsAccumulatorV2[MyVector,MyVector]{privatevalmyVector:MyVector=MyVector.createZeroVectordefreset():Unit={myVector.reset()}defadd(v:MyVector):Unit={myVector.add(v)}...}// Then, create an Accumulator of this type:valmyVectorAcc=newVectorAccumulatorV2// Then, register it into spark context:sc.register(myVectorAcc,"MyVectorAcc1")

    注意,在开发者定义自己的 AccumulatorV2 类型时, resulting type(返回值类型)可能与添加的元素的类型不一致。

    累加器的更新只发生在action操作中,Spark 保证每个任务只更新累加器一次,例如,重启任务不会更新值。在 transformations(转换)中, 用户需要注意的是,如果 task(任务)或 job stages(阶段)重新执行,每个任务的更新操作可能会执行多次。

    累加器不会改变 Spark lazy evaluation(懒加载)的模式。如果累加器在 RDD 中的一个操作中进行更新,它们的值仅被更新一次,RDD 被作为 action 的一部分来计算。因此,在一个像map()这样的 transformation(转换)时,累加器的更新并没有执行。下面的代码片段证明了这个特性:

    Scala

    Java

    Python

    valaccum=sc.longAccumulatordata.map{x=>accum.add(x);x}// Here, accum is still 0 because no actions have caused the map operation to be computed.

    部署应用到集群中

    应用提交指南描述了如何将应用提交到集群中. 简单的说, 在您将应用打包成一个JAR(针对 Java/Scala) 或者一组.py或.zip文件 (针对Python), 该bin/spark-submit脚本可以让你提交它到任何所支持的 cluster manager 上去.

    Java / Scala 启动 Spark jobs

    org.apache.spark.launcherpackage 提供了 classes 用于使用简单的 Java API 来作为一个子进程启动 Spark jobs.

    单元测试

    Spark 可以友好的使用流行的单元测试框架进行单元测试。在将 master URL 设置为local来测试时会简单的创建一个SparkContext,运行您的操作,然后调用SparkContext.stop()将该作业停止。因为 Spark 不支持在同一个程序中并行的运行两个 contexts,所以需要确保使用 finally 块或者测试框架的tearDown方法将 context 停止。

    快速链接

    您可以在 Spark 网站上看一下Spark 程序示例. 此外, Spark 在examples目录中包含了许多示例 (Scala,Java,Python,R). 您可以通过传递 class name 到 Spark 的 bin/run-example 脚本以运行 Java 和 Scala 示例; 例如:

    ./bin/run-example SparkPi

    针对 Python 示例,使用spark-submit来代替:

    ./bin/spark-submit examples/src/main/python/pi.py

    针对 R 示例,使用spark-submit来代替:

    ./bin/spark-submit examples/src/main/r/dataframe.R

    针对应用程序的优化, 该配置优化指南一些最佳实践的信息. 这些优化建议在确保你的数据以高效的格式存储在内存中尤其重要. 针对部署参考, 该集群模式概述描述了分布式操作和支持的 cluster managers 集群管理器的组件.

    最后,所有的 API 文档可在Scala,Java,PythonandR中获取.

    我们一直在努力

    apachecn/spark-doc-zh

     

     

    原文地址: http://spark.apachecn.org/docs/cn/2.2.0/rdd-programming-guide.html

    网页地址: http://spark.apachecn.org/

    github: https://github.com/apachecn/spark-doc-zh(觉得不错麻烦给个 Star,谢谢!~)

     

     

     

     

     

     

     

    Apache Spark 2.2.0 中文文档 - Spark SQL, DataFrames and Datasets Guide | ApacheCN

    Geekhoo 关注

    2017.09.25 11:15* 字数 32982 阅读 894评论 0喜欢 21

    Spark SQL, DataFrames and Datasets Guide

    Overview

    SQL

    Datasets and DataFrames

    开始入门

    起始点: SparkSession

    创建 DataFrames

    无类型的Dataset操作 (aka DataFrame 操作)

    Running SQL Queries Programmatically

    全局临时视图

    创建Datasets

    RDD的互操作性

    使用反射推断Schema

    以编程的方式指定Schema

    Aggregations

    Untyped User-Defined Aggregate Functions

    Type-Safe User-Defined Aggregate Functions

    Data Sources (数据源)

    Generic Load/Save Functions (通用 加载/保存 功能)

    Manually Specifying Options (手动指定选项)

    Run SQL on files directly (直接在文件上运行 SQL)

    Save Modes (保存模式)

    Saving to Persistent Tables (保存到持久表)

    Bucketing, Sorting and Partitioning (分桶, 排序和分区)

    Parquet Files

    Loading Data Programmatically (以编程的方式加载数据)

    Partition Discovery (分区发现)

    Schema Merging (模式合并)

    Hive metastore Parquet table conversion (Hive metastore Parquet table 转换)

    Hive/Parquet Schema Reconciliation

    Metadata Refreshing (元数据刷新)

    Configuration (配置)

    JSON Datasets (JSON 数据集)

    Hive 表

    指定 Hive 表的存储格式

    与不同版本的 Hive Metastore 进行交互

    JDBC 连接其它数据库

    故障排除

    性能调优

    在内存中缓存数据

    其他配置选项

    分布式 SQL 引擎

    运行 Thrift JDBC/ODBC 服务器

    运行 Spark SQL CLI

    迁移指南

    Spark SQL 2.1 升级到 2.2

    Spark SQL 2.0 升级到 2.1

    Spark SQL 1.6 升级到 2.0

    Spark SQL 1.5 升级到 1.6

    Spark SQL 1.4 升级到 1.5

    Spark SQL 1.3 升级到 1.4

    DataFrame data reader/writer interface

    DataFrame.groupBy 保留 grouping columns(分组的列)

    DataFrame.withColumn 上的行为更改

    Spark SQL 1.0-1.2 升级到 1.3

    重命名 DataFrame 的 SchemaRDD

    Java 和 Scala APIs 的统一

    隔离隐式转换和删除 dsl 包(仅Scala)

    针对 DataType 删除在 org.apache.spark.sql 包中的一些类型别名(仅限于 Scala)

    UDF 注册迁移到sqlContext.udf中 (Java & Scala)

    Python DataTypes 不再是 Singletons(单例的)

    Apache Hive 的兼容

    在现有的 Hive Warehouses 中部署

    所支持的 Hive 特性

    未支持的 Hive 函数

    参考

    数据类型

    NaN Semantics

    Overview

    Spark SQL 是 Spark 处理结构化数据的一个模块.与基础的 Spark RDD API 不同, Spark SQL 提供了查询结构化数据及计算结果等信息的接口.在内部, Spark SQL 使用这个额外的信息去执行额外的优化.有几种方式可以跟 Spark SQL 进行交互, 包括 SQL 和 Dataset API.当使用相同执行引擎进行计算时, 无论使用哪种 API / 语言都可以快速的计算.这种统一意味着开发人员能够在基于提供最自然的方式来表达一个给定的 transformation API 之间实现轻松的来回切换不同的 .

    该页面所有例子使用的示例数据都包含在 Spark 的发布中, 并且可以使用spark-shell,pysparkshell, 或者sparkRshell来运行.

    SQL

    Spark SQL 的功能之一是执行 SQL 查询.Spark SQL 也能够被用于从已存在的 Hive 环境中读取数据.更多关于如何配置这个特性的信息, 请参考Hive 表这部分. 当以另外的编程语言运行SQL 时, 查询结果将以Dataset/DataFrame的形式返回.您也可以使用命令行或者通过JDBC/ODBC SQL 接口交互.

    Datasets and DataFrames

    一个 Dataset 是一个分布式的数据集合 Dataset 是在 Spark 1.6 中被添加的新接口, 它提供了 RDD 的优点(强类型化, 能够使用强大的 lambda 函数)与Spark SQL执行引擎的优点.一个 Dataset 可以从 JVM 对象来构造并且使用转换功能(map, flatMap, filter, 等等). Dataset API 在ScalaJava是可用的.Python 不支持 Dataset API.但是由于 Python 的动态特性, 许多 Dataset API 的优点已经可用了 (也就是说, 你可能通过 name 天生的row.columnName属性访问一行中的字段).这种情况和 R 相似.

    一个 DataFrame 是一个Dataset组成的指定列.它的概念与一个在关系型数据库或者在 R/Python 中的表是相等的, 但是有很多优化. DataFrames 可以从大量的sources中构造出来, 比如: 结构化的文本文件, Hive中的表, 外部数据库, 或者已经存在的 RDDs. DataFrame API 可以在 Scala, Java,Python, 和R中实现. 在 Scala 和 Java中, 一个 DataFrame 所代表的是一个多个Row(行)的的 Dataset(数据集合). 在the Scala API,DataFrame仅仅是一个Dataset[Row]类型的别名. 然而, 在Java API, 用户需要去使用Dataset去代表一个DataFrame.

    在此文档中, 我们将常常会引用 Scala/Java Datasets 的Rows 作为 DataFrames.

    开始入门

    起始点: SparkSession

    Scala

    Java

    Python

    R

    Spark SQL中所有功能的入口点是SparkSession. 要创建一个SparkSession, 仅使用SparkSession.builder()就可以了:

    importorg.apache.spark.sql.SparkSessionvalspark=SparkSession.builder().appName("Spark SQL basic example").config("spark.some.config.option","some-value").getOrCreate()// For implicit conversions like converting RDDs to DataFramesimportspark.implicits._

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    Spark 2.0 中的SparkSession为 Hive 特性提供了内嵌的支持, 包括使用 HiveQL 编写查询的能力, 访问 Hive UDF,以及从 Hive 表中读取数据的能力.为了使用这些特性, 你不需要去有一个已存在的 Hive 设置.

    创建 DataFrames

    Scala

    Java

    Python

    R

    在一个SparkSession中, 应用程序可以从一个已经存在的RDD, 从hive表, 或者从Spark数据源中创建一个DataFrames.

    举个例子, 下面就是基于一个JSON文件创建一个DataFrame:

    valdf=spark.read.json("examples/src/main/resources/people.json")// Displays the content of the DataFrame to stdoutdf.show()// +----+-------+// | age|  name|// +----+-------+// |null|Michael|// |  30|  Andy|// |  19| Justin|// +----+-------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    无类型的Dataset操作 (aka DataFrame 操作)

    DataFrames 提供了一个特定的语法用在Scala,Java,PythonandR中机构化数据的操作.

    正如上面提到的一样, Spark 2.0中, DataFrames在Scala 和 Java API中, 仅仅是多个Rows的Dataset. 这些操作也参考了与强类型的Scala/Java Datasets中的”类型转换” 对应的”无类型转换” .

    这里包括一些使用 Dataset 进行结构化数据处理的示例 :

    Scala

    Java

    Python

    R

    // This import is needed to use the $-notationimportspark.implicits._// Print the schema in a tree formatdf.printSchema()// root// |-- age: long (nullable = true)// |-- name: string (nullable = true)// Select only the "name" columndf.select("name").show()// +-------+// |  name|// +-------+// |Michael|// |  Andy|// | Justin|// +-------+// Select everybody, but increment the age by 1df.select($"name",$"age"+1).show()// +-------+---------+// |  name|(age + 1)|// +-------+---------+// |Michael|    null|// |  Andy|      31|// | Justin|      20|// +-------+---------+// Select people older than 21df.filter($"age">21).show()// +---+----+// |age|name|// +---+----+// | 30|Andy|// +---+----+// Count people by agedf.groupBy("age").count().show()// +----+-----+// | age|count|// +----+-----+// |  19|    1|// |null|    1|// |  30|    1|// +----+-----+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    能够在 DataFrame 上被执行的操作类型的完整列表请参考API 文档.

    除了简单的列引用和表达式之外, DataFrame 也有丰富的函数库, 包括 string 操作, date 算术, 常见的 math 操作以及更多.可用的完整列表请参考DataFrame 函数指南.

    Running SQL Queries Programmatically

    Scala

    Java

    Python

    R

    SparkSession的sql函数可以让应用程序以编程的方式运行 SQL 查询, 并将结果作为一个DataFrame返回.

    // Register the DataFrame as a SQL temporary viewdf.createOrReplaceTempView("people")valsqlDF=spark.sql("SELECT * FROM people")sqlDF.show()// +----+-------+// | age|  name|// +----+-------+// |null|Michael|// |  30|  Andy|// |  19| Justin|// +----+-------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    全局临时视图

    Spark SQL中的临时视图是session级别的, 也就是会随着session的消失而消失. 如果你想让一个临时视图在所有session中相互传递并且可用, 直到Spark 应用退出, 你可以建立一个全局的临时视图.全局的临时视图存在于系统数据库global_temp中, 我们必须加上库名去引用它, 比如.SELECT * FROM global_temp.view1.

    Scala

    Java

    Python

    Sql

    // Register the DataFrame as a global temporary viewdf.createGlobalTempView("people")// Global temporary view is tied to a system preserved database `global_temp`spark.sql("SELECT * FROM global_temp.people").show()// +----+-------+// | age|  name|// +----+-------+// |null|Michael|// |  30|  Andy|// |  19| Justin|// +----+-------+// Global temporary view is cross-sessionspark.newSession().sql("SELECT * FROM global_temp.people").show()// +----+-------+// | age|  name|// +----+-------+// |null|Michael|// |  30|  Andy|// |  19| Justin|// +----+-------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    创建Datasets

    Dataset 与 RDD 相似, 然而, 并不是使用 Java 序列化或者 Kryo编码器来序列化用于处理或者通过网络进行传输的对象. 虽然编码器和标准的序列化都负责将一个对象序列化成字节, 编码器是动态生成的代码, 并且使用了一种允许 Spark 去执行许多像 filtering, sorting 以及 hashing 这样的操作, 不需要将字节反序列化成对象的格式.

    Scala

    Java

    // Note: Case classes in Scala 2.10 can support only up to 22 fields. To work around this limit,// you can use custom classes that implement the Product interfacecaseclassPerson(name:String,age:Long)// Encoders are created for case classesvalcaseClassDS=Seq(Person("Andy",32)).toDS()caseClassDS.show()// +----+---+// |name|age|// +----+---+// |Andy| 32|// +----+---+// Encoders for most common types are automatically provided by importing spark.implicits._valprimitiveDS=Seq(1,2,3).toDS()primitiveDS.map(_+1).collect()// Returns: Array(2, 3, 4)// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by namevalpath="examples/src/main/resources/people.json"valpeopleDS=spark.read.json(path).as[Person]peopleDS.show()// +----+-------+// | age|  name|// +----+-------+// |null|Michael|// |  30|  Andy|// |  19| Justin|// +----+-------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    RDD的互操作性

    Spark SQL 支持两种不同的方法用于转换已存在的 RDD 成为 Dataset.第一种方法是使用反射去推断一个包含指定的对象类型的 RDD 的 Schema.在你的 Spark 应用程序中当你已知 Schema 时这个基于方法的反射可以让你的代码更简洁.

    第二种用于创建 Dataset 的方法是通过一个允许你构造一个 Schema 然后把它应用到一个已存在的 RDD 的编程接口.然而这种方法更繁琐, 当列和它们的类型知道运行时都是未知时它允许你去构造 Dataset.

    使用反射推断Schema

    Scala

    Java

    Python

    Spark SQL 的 Scala 接口支持自动转换一个包含 case classes 的 RDD 为 DataFrame.Case class 定义了表的 Schema.Case class 的参数名使用反射读取并且成为了列名.Case class 也可以是嵌套的或者包含像Seq或者Array这样的复杂类型.这个 RDD 能够被隐式转换成一个 DataFrame 然后被注册为一个表.表可以用于后续的 SQL 语句.

    // For implicit conversions from RDDs to DataFramesimportspark.implicits._// Create an RDD of Person objects from a text file, convert it to a DataframevalpeopleDF=spark.sparkContext.textFile("examples/src/main/resources/people.txt").map(_.split(",")).map(attributes=>Person(attributes(0),attributes(1).trim.toInt)).toDF()// Register the DataFrame as a temporary viewpeopleDF.createOrReplaceTempView("people")// SQL statements can be run by using the sql methods provided by SparkvalteenagersDF=spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")// The columns of a row in the result can be accessed by field indexteenagersDF.map(teenager=>"Name: "+teenager(0)).show()// +------------+// |      value|// +------------+// |Name: Justin|// +------------+// or by field nameteenagersDF.map(teenager=>"Name: "+teenager.getAs[String]("name")).show()// +------------+// |      value|// +------------+// |Name: Justin|// +------------+// No pre-defined encoders for Dataset[Map[K,V]], define explicitlyimplicitvalmapEncoder=org.apache.spark.sql.Encoders.kryo[Map[String,Any]]// Primitive types and case classes can be also defined as// implicit val stringIntMapEncoder: Encoder[Map[String, Any]] = ExpressionEncoder()// row.getValuesMap[T] retrieves multiple columns at once into a Map[String, T]teenagersDF.map(teenager=>teenager.getValuesMap[Any](List("name","age"))).collect()// Array(Map("name" -> "Justin", "age" -> 19))

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    以编程的方式指定Schema

    Scala

    Java

    Python

    case class 不能够在执行之前被定义(例如, records 记录的结构在一个 string 字符串中被编码了, 或者一个 text 文本 dataset 将被解析并且不同的用户投影的字段是不一样的).一个DataFrame可以使用下面的三步以编程的方式来创建.

    从原始的 RDD 创建 RDD 的Row(行);

    Step 1 被创建后, 创建 Schema 表示一个StructType匹配 RDD 中的Row(行)的结构.

    通过SparkSession提供的createDataFrame方法应用 Schema 到 RDD 的 RowS(行).

    例如:

    importorg.apache.spark.sql.types._// Create an RDDvalpeopleRDD=spark.sparkContext.textFile("examples/src/main/resources/people.txt")// The schema is encoded in a stringvalschemaString="name age"// Generate the schema based on the string of schemavalfields=schemaString.split(" ").map(fieldName=>StructField(fieldName,StringType,nullable=true))valschema=StructType(fields)// Convert records of the RDD (people) to RowsvalrowRDD=peopleRDD.map(_.split(",")).map(attributes=>Row(attributes(0),attributes(1).trim))// Apply the schema to the RDDvalpeopleDF=spark.createDataFrame(rowRDD,schema)// Creates a temporary view using the DataFramepeopleDF.createOrReplaceTempView("people")// SQL can be run over a temporary view created using DataFramesvalresults=spark.sql("SELECT name FROM people")// The results of SQL queries are DataFrames and support all the normal RDD operations// The columns of a row in the result can be accessed by field index or by field nameresults.map(attributes=>"Name: "+attributes(0)).show()// +-------------+// |        value|// +-------------+// |Name: Michael|// |  Name: Andy|// | Name: Justin|// +-------------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    Aggregations

    Thebuilt-in DataFrames functionsprovide common aggregations such ascount(),countDistinct(),avg(),max(),min(), etc. While those functions are designed for DataFrames, Spark SQL also has type-safe versions for some of them inScalaandJavato work with strongly typed Datasets. Moreover, users are not limited to the predefined aggregate functions and can create their own.

    Untyped User-Defined Aggregate Functions

    Scala

    Java

    Users have to extend theUserDefinedAggregateFunctionabstract class to implement a custom untyped aggregate function. For example, a user-defined average can look like:

    importorg.apache.spark.sql.expressions.MutableAggregationBufferimportorg.apache.spark.sql.expressions.UserDefinedAggregateFunctionimportorg.apache.spark.sql.types._importorg.apache.spark.sql.Rowimportorg.apache.spark.sql.SparkSessionobjectMyAverageextendsUserDefinedAggregateFunction{// Data types of input arguments of this aggregate functiondefinputSchema:StructType=StructType(StructField("inputColumn",LongType)::Nil)// Data types of values in the aggregation bufferdefbufferSchema:StructType={StructType(StructField("sum",LongType)::StructField("count",LongType)::Nil)}// The data type of the returned valuedefdataType:DataType=DoubleType// Whether this function always returns the same output on the identical inputdefdeterministic:Boolean=true// Initializes the given aggregation buffer. The buffer itself is a `Row` that in addition to// standard methods like retrieving a value at an index (e.g., get(), getBoolean()), provides// the opportunity to update its values. Note that arrays and maps inside the buffer are still// immutable.definitialize(buffer:MutableAggregationBuffer):Unit={buffer(0)=0Lbuffer(1)=0L}// Updates the given aggregation buffer `buffer` with new input data from `input`defupdate(buffer:MutableAggregationBuffer,input:Row):Unit={if(!input.isNullAt(0)){buffer(0)=buffer.getLong(0)+input.getLong(0)buffer(1)=buffer.getLong(1)+1}}// Merges two aggregation buffers and stores the updated buffer values back to `buffer1`defmerge(buffer1:MutableAggregationBuffer,buffer2:Row):Unit={buffer1(0)=buffer1.getLong(0)+buffer2.getLong(0)buffer1(1)=buffer1.getLong(1)+buffer2.getLong(1)}// Calculates the final resultdefevaluate(buffer:Row):Double=buffer.getLong(0).toDouble/buffer.getLong(1)}// Register the function to access itspark.udf.register("myAverage",MyAverage)valdf=spark.read.json("examples/src/main/resources/employees.json")df.createOrReplaceTempView("employees")df.show()// +-------+------+// |  name|salary|// +-------+------+// |Michael|  3000|// |  Andy|  4500|// | Justin|  3500|// |  Berta|  4000|// +-------+------+valresult=spark.sql("SELECT myAverage(salary) as average_salary FROM employees")result.show()// +--------------+// |average_salary|// +--------------+// |        3750.0|// +--------------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/UserDefinedUntypedAggregation.scala" in the Spark repo.

    Type-Safe User-Defined Aggregate Functions

    User-defined aggregations for strongly typed Datasets revolve around theAggregatorabstract class. For example, a type-safe user-defined average can look like:

    Scala

    Java

    importorg.apache.spark.sql.expressions.Aggregatorimportorg.apache.spark.sql.Encoderimportorg.apache.spark.sql.Encodersimportorg.apache.spark.sql.SparkSessioncaseclassEmployee(name:String,salary:Long)caseclassAverage(varsum:Long,varcount:Long)objectMyAverageextendsAggregator[Employee,Average,Double]{// A zero value for this aggregation. Should satisfy the property that any b + zero = bdefzero:Average=Average(0L,0L)// Combine two values to produce a new value. For performance, the function may modify `buffer`// and return it instead of constructing a new objectdefreduce(buffer:Average,employee:Employee):Average={buffer.sum+=employee.salarybuffer.count+=1buffer}// Merge two intermediate valuesdefmerge(b1:Average,b2:Average):Average={b1.sum+=b2.sumb1.count+=b2.countb1}// Transform the output of the reductiondeffinish(reduction:Average):Double=reduction.sum.toDouble/reduction.count// Specifies the Encoder for the intermediate value typedefbufferEncoder:Encoder[Average]=Encoders.product// Specifies the Encoder for the final output value typedefoutputEncoder:Encoder[Double]=Encoders.scalaDouble}valds=spark.read.json("examples/src/main/resources/employees.json").as[Employee]ds.show()// +-------+------+// |  name|salary|// +-------+------+// |Michael|  3000|// |  Andy|  4500|// | Justin|  3500|// |  Berta|  4000|// +-------+------+// Convert the function to a `TypedColumn` and give it a namevalaverageSalary=MyAverage.toColumn.name("average_salary")valresult=ds.select(averageSalary)result.show()// +--------------+// |average_salary|// +--------------+// |        3750.0|// +--------------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/UserDefinedTypedAggregation.scala" in the Spark repo.

    Data Sources (数据源)

    Spark SQL 支持通过 DataFrame 接口对各种 data sources (数据源)进行操作. DataFrame 可以使用 relational transformations (关系转换)操作, 也可用于创建 temporary view (临时视图). 将 DataFrame 注册为 temporary view (临时视图)允许您对其数据运行 SQL 查询. 本节 描述了使用 Spark Data Sources 加载和保存数据的一般方法, 然后涉及可用于 built-in data sources (内置数据源)的 specific options (特定选项).

    Generic Load/Save Functions (通用 加载/保存 功能)

    在最简单的形式中, 默认数据源(parquet, 除非另有配置spark.sql.sources.default)将用于所有操作.

    Scala

    Java

    Python

    R

    valusersDF=spark.read.load("examples/src/main/resources/users.parquet")usersDF.select("name","favorite_color").write.save("namesAndFavColors.parquet")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Manually Specifying Options (手动指定选项)

    您还可以 manually specify (手动指定)将与任何你想传递给 data source 的其他选项一起使用的 data source . Data sources 由其 fully qualified name (完全限定名称)(即org.apache.spark.sql.parquet), 但是对于 built-in sources (内置的源), 你也可以使用它们的 shortnames (短名称)(json,parquet,jdbc,orc,libsvm,csv,text).从任何 data source type (数据源类型)加载 DataFrames 可以使用此 syntax (语法)转换为其他类型.

    Scala

    Java

    Python

    R

    valpeopleDF=spark.read.format("json").load("examples/src/main/resources/people.json")peopleDF.select("name","age").write.format("parquet").save("namesAndAges.parquet")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Run SQL on files directly (直接在文件上运行 SQL)

    不使用读取 API 将文件加载到 DataFrame 并进行查询, 也可以直接用 SQL 查询该文件.

    Scala

    Java

    Python

    R

    valsqlDF=spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Save Modes (保存模式)

    Save operations (保存操作)可以选择使用SaveMode, 它指定如何处理现有数据如果存在的话. 重要的是要意识到, 这些 save modes (保存模式)不使用任何 locking (锁定)并且不是 atomic (原子). 另外, 当执行Overwrite时, 数据将在新数据写出之前被删除.

    Scala/JavaAny LanguageMeaning

    SaveMode.ErrorIfExists(default)"error"(default)将 DataFrame 保存到 data source (数据源)时, 如果数据已经存在, 则会抛出异常.

    SaveMode.Append"append"将 DataFrame 保存到 data source (数据源)时, 如果 data/table 已存在, 则 DataFrame 的内容将被 append (附加)到现有数据中.

    SaveMode.Overwrite"overwrite"Overwrite mode (覆盖模式)意味着将 DataFrame 保存到 data source (数据源)时, 如果 data/table 已经存在, 则预期 DataFrame 的内容将 overwritten (覆盖)现有数据.

    SaveMode.Ignore"ignore"Ignore mode (忽略模式)意味着当将 DataFrame 保存到 data source (数据源)时, 如果数据已经存在, 则保存操作预期不会保存 DataFrame 的内容, 并且不更改现有数据. 这与 SQL 中的CREATE TABLE IF NOT EXISTS类似.

    Saving to Persistent Tables (保存到持久表)

    DataFrames也可以使用saveAsTable命令作为 persistent tables (持久表)保存到 Hive metastore 中. 请注意, existing Hive deployment (现有的 Hive 部署)不需要使用此功能. Spark 将为您创建默认的 local Hive metastore (本地 Hive metastore)(使用 Derby ). 与createOrReplaceTempView命令不同,saveAsTable将 materialize (实现) DataFrame 的内容, 并创建一个指向 Hive metastore 中数据的指针. 即使您的 Spark 程序重新启动, Persistent tables (持久性表)仍然存在, 因为您保持与同一个 metastore 的连接. 可以通过使用表的名称在SparkSession上调用table方法来创建 persistent tabl (持久表)的 DataFrame .

    对于 file-based (基于文件)的 data source (数据源), 例如 text, parquet, json等, 您可以通过path选项指定 custom table path (自定义表路径), 例如df.write.option("path", "/some/path").saveAsTable("t"). 当表被 dropped (删除)时, custom table path (自定义表路径)将不会被删除, 并且表数据仍然存在. 如果未指定自定义表路径, Spark 将把数据写入 warehouse directory (仓库目录)下的默认表路径. 当表被删除时, 默认的表路径也将被删除.

    Spark 2.1 开始, persistent datasource tables (持久性数据源表)将 per-partition metadata (每个分区元数据)存储在 Hive metastore 中. 这带来了几个好处:

    由于 metastore 只能返回查询的必要 partitions (分区), 因此不再需要将第一个查询上的所有 partitions discovering 到表中.

    Hive DDLs 如ALTER TABLE PARTITION ... SET LOCATION现在可用于使用 Datasource API 创建的表.

    请注意, 创建 external datasource tables (外部数据源表)(带有path选项)的表时, 默认情况下不会收集 partition information (分区信息). 要 sync (同步) metastore 中的分区信息, 可以调用MSCK REPAIR TABLE.

    Bucketing, Sorting and Partitioning (分桶, 排序和分区)

    对于 file-based data source (基于文件的数据源), 也可以对 output (输出)进行 bucket 和 sort 或者 partition . Bucketing 和 sorting 仅适用于 persistent tables :

    Scala

    Java

    Python

    Sql

    peopleDF.write.bucketBy(42,"name").sortBy("age").saveAsTable("people_bucketed")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    在使用 Dataset API 时, partitioning 可以同时与save和saveAsTable一起使用.

    Scala

    Java

    Python

    Sql

    usersDF.write.partitionBy("favorite_color").format("parquet").save("namesPartByColor.parquet")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    可以为 single table (单个表)使用 partitioning 和 bucketing:

    Scala

    Java

    Python

    Sql

    peopleDF.write.partitionBy("favorite_color").bucketBy(42,"name").saveAsTable("people_partitioned_bucketed")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    partitionBy创建一个 directory structure (目录结构), 如Partition Discovery部分所述. 因此, 对 cardinality (基数)较高的 columns 的适用性有限. 相反,bucketBy可以在固定数量的 buckets 中分配数据, 并且可以在 a number of unique values is unbounded (多个唯一值无界时)使用数据.

    Parquet Files

    Parquet是许多其他数据处理系统支持的 columnar format (柱状格式). Spark SQL 支持读写 Parquet 文件, 可自动保留 schema of the original data (原始数据的模式). 当编写 Parquet 文件时, 出于兼容性原因, 所有 columns 都将自动转换为可空.

    Loading Data Programmatically (以编程的方式加载数据)

    使用上面例子中的数据:

    Scala

    Java

    Python

    R

    Sql

    // Encoders for most common types are automatically provided by importing spark.implicits._importspark.implicits._valpeopleDF=spark.read.json("examples/src/main/resources/people.json")// DataFrames can be saved as Parquet files, maintaining the schema informationpeopleDF.write.parquet("people.parquet")// Read in the parquet file created above// Parquet files are self-describing so the schema is preserved// The result of loading a Parquet file is also a DataFramevalparquetFileDF=spark.read.parquet("people.parquet")// Parquet files can also be used to create a temporary view and then used in SQL statementsparquetFileDF.createOrReplaceTempView("parquetFile")valnamesDF=spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")namesDF.map(attributes=>"Name: "+attributes(0)).show()// +------------+// |      value|// +------------+// |Name: Justin|// +------------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Partition Discovery (分区发现)

    Table partitioning (表分区)是在像 Hive 这样的系统中使用的常见的优化方法. 在 partitioned table (分区表)中, 数据通常存储在不同的目录中, partitioning column values encoded (分区列值编码)在每个 partition directory (分区目录)的路径中. Parquet data source (Parquet 数据源)现在可以自动 discover (发现)和 infer (推断)分区信息. 例如, 我们可以使用以下 directory structure (目录结构)将所有以前使用的 population data (人口数据)存储到 partitioned table (分区表)中, 其中有两个额外的列gender和country作为 partitioning columns (分区列):

    path└── to    └── table        ├── gender=male        │   ├── ...        │   │        │   ├── country=US        │   │   └── data.parquet        │   ├── country=CN        │   │   └── data.parquet        │   └── ...        └── gender=female           ├── ...           │           ├── country=US           │   └── data.parquet           ├── country=CN           │   └── data.parquet           └── ...

    通过将path/to/table传递给SparkSession.read.parquet或SparkSession.read.load, Spark SQL 将自动从路径中提取 partitioning information (分区信息). 现在返回的 DataFrame 的 schema (模式)变成:

    root|-- name: string (nullable = true)|-- age: long (nullable = true)|-- gender: string (nullable = true)|-- country: string (nullable = true)

    请注意, 会自动 inferred (推断) partitioning columns (分区列)的 data types (数据类型).目前, 支持 numeric data types (数字数据类型)和 string type (字符串类型).有些用户可能不想自动推断 partitioning columns (分区列)的数据类型.对于这些用例, automatic type inference (自动类型推断)可以由spark.sql.sources.partitionColumnTypeInference.enabled配置, 默认为true.当禁用 type inference (类型推断)时, string type (字符串类型)将用于 partitioning columns (分区列).

    Spark 1.6.0 开始, 默认情况下, partition discovery (分区发现)只能找到给定路径下的 partitions (分区).对于上述示例, 如果用户将path/to/table/gender=male传递给SparkSession.read.parquet或SparkSession.read.load, 则gender将不被视为 partitioning column (分区列).如果用户需要指定 partition discovery (分区发现)应该开始的基本路径, 则可以在数据源选项中设置basePath.例如, 当path/to/table/gender=male是数据的路径并且用户将basePath设置为path/to/table/,gender将是一个 partitioning column (分区列).

    Schema Merging (模式合并)

    ProtocolBuffer , Avro 和 Thrift 一样, Parquet 也支持 schema evolution (模式演进). 用户可以从一个 simple schema (简单的架构)开始, 并根据需要逐渐向 schema 添加更多的 columns (列). 以这种方式, 用户可能会使用不同但相互兼容的 schemas 的 multiple Parquet files (多个 Parquet 文件). Parquet data source (Parquet 数据源)现在能够自动检测这种情况并 merge (合并)所有这些文件的 schemas .

    由于 schema merging (模式合并)是一个 expensive operation (相对昂贵的操作), 并且在大多数情况下不是必需的, 所以默认情况下从 1.5.0 开始. 你可以按照如下的方式启用它:

    读取 Parquet 文件时, 将 data source option (数据源选项)mergeSchema设置为true(如下面的例子所示), 或

    global SQL option (全局 SQL 选项)spark.sql.parquet.mergeSchema设置为true.

    Scala

    Java

    Python

    R

    // This is used to implicitly convert an RDD to a DataFrame.importspark.implicits._// Create a simple DataFrame, store into a partition directoryvalsquaresDF=spark.sparkContext.makeRDD(1to5).map(i=>(i,i*i)).toDF("value","square")squaresDF.write.parquet("data/test_table/key=1")// Create another DataFrame in a new partition directory,// adding a new column and dropping an existing columnvalcubesDF=spark.sparkContext.makeRDD(6to10).map(i=>(i,i*i*i)).toDF("value","cube")cubesDF.write.parquet("data/test_table/key=2")// Read the partitioned tablevalmergedDF=spark.read.option("mergeSchema","true").parquet("data/test_table")mergedDF.printSchema()// The final schema consists of all 3 columns in the Parquet files together// with the partitioning column appeared in the partition directory paths// root//  |-- value: int (nullable = true)//  |-- square: int (nullable = true)//  |-- cube: int (nullable = true)//  |-- key: int (nullable = true)

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Hive metastore Parquet table conversion (Hive metastore Parquet table 转换)

    当读取和写入 Hive metastore Parquet 表时, Spark SQL 将尝试使用自己的 Parquet support (Parquet 支持), 而不是 Hive SerDe 来获得更好的性能. 此 behavior (行为)由spark.sql.hive.convertMetastoreParquet配置控制, 默认情况下 turned on (打开).

    Hive/Parquet Schema Reconciliation

    table schema processing (表格模式处理)的角度来说, Hive 和 Parquet 之间有两个关键的区别.

    Hive 不区分大小写, 而 Parquet 不是

    Hive 认为所有 columns (列)都可以为空, 而 Parquet 中的可空性是 significant (重要)的.

    由于这个原因, 当将 Hive metastore Parquet 表转换为 Spark SQL Parquet 表时, 我们必须调整 metastore schema 与 Parquet schema. reconciliation 规则是:

    在两个 schema 中具有 same name (相同名称)的 Fields (字段)必须具有 same data type (相同的数据类型), 而不管 nullability (可空性). reconciled field 应具有 Parquet 的数据类型, 以便 nullability (可空性)得到尊重.

    reconciled schema (调和模式)正好包含 Hive metastore schema 中定义的那些字段.

    只出现在 Parquet schema 中的任何字段将被 dropped (删除)在 reconciled schema 中.

    仅在 Hive metastore schema 中出现的任何字段在 reconciled schema 中作为 nullable field (可空字段)添加.

    Metadata Refreshing (元数据刷新)

    Spark SQL 缓存 Parquet metadata 以获得更好的性能. 当启用 Hive metastore Parquet table conversion (转换)时, 这些 converted tables (转换表)的 metadata (元数据)也被 cached (缓存). 如果这些表由 Hive 或其他外部工具更新, 则需要手动刷新以确保 consistent metadata (一致的元数据).

    Scala

    Java

    Python

    Sql

    // spark is an existing SparkSessionspark.catalog.refreshTable("my_table")

    Configuration (配置)

    可以使用SparkSession上的setConf方法或使用 SQL 运行SET key = value命令来完成 Parquet 的配置.

    Property Name (参数名称)Default(默认)Meaning(含义)

    spark.sql.parquet.binaryAsStringfalse一些其他 Parquet-producing systems (Parquet 生产系统), 特别是 Impala, Hive 和旧版本的 Spark SQL , 在 writing out (写出) Parquet schema 时, 不区分 binary data (二进制数据)和 strings (字符串). 该 flag 告诉 Spark SQL 将 binary data (二进制数据)解释为 string (字符串)以提供与这些系统的兼容性.

    spark.sql.parquet.int96AsTimestamptrue一些 Parquet-producing systems , 特别是 Impala 和 Hive , 将 Timestamp 存入INT96 . 该 flag 告诉 Spark SQL 将 INT96 数据解析为 timestamp 以提供与这些系统的兼容性.

    spark.sql.parquet.cacheMetadatatrue打开 Parquet schema metadata 的缓存. 可以加快查询静态数据.

    spark.sql.parquet.compression.codecsnappy在编写 Parquet 文件时设置 compression codec (压缩编解码器)的使用. 可接受的值包括: uncompressed, snappy, gzip, lzo .

    spark.sql.parquet.filterPushdowntrue设置为 true 时启用 Parquet filter push-down optimization .

    spark.sql.hive.convertMetastoreParquettrue当设置为 false 时, Spark SQL 将使用 Hive SerDe 作为 parquet tables , 而不是内置的支持.

    spark.sql.parquet.mergeSchemafalse当为 true 时, Parquet data source (Parquet 数据源) merges (合并)从所有 data files (数据文件)收集的 schemas , 否则如果没有可用的 summary file , 则从 summary file 或 random data file 中挑选 schema .

    spark.sql.optimizer.metadataOnlytrue如果为 true , 则启用使用表的 metadata 的 metadata-only query optimization 来生成 partition columns (分区列)而不是 table scans (表扫描). 当 scanned (扫描)的所有 columns (列)都是 partition columns (分区列)并且 query (查询)具有满足 distinct semantics (不同语义)的 aggregate operator (聚合运算符)时, 它将适用.

    JSON Datasets (JSON 数据集)

    Scala

    Java

    Python

    R

    Sql

    Spark SQL 可以 automatically infer (自动推断)JSON dataset 的 schema, 并将其作为Dataset[Row]加载. 这个 conversion (转换)可以在Dataset[String]上使用SparkSession.read.json()来完成, 或 JSON 文件.

    请注意, 以a json file提供的文件不是典型的 JSON 文件. 每行必须包含一个 separate (单独的), self-contained valid (独立的有效的)JSON 对象. 有关更多信息, 请参阅JSON Lines text format, also called newline-delimited JSON.

    对于 regular multi-line JSON file (常规的多行 JSON 文件), 将multiLine选项设置为true.

    // Primitive types (Int, String, etc) and Product types (case classes) encoders are// supported by importing this when creating a Dataset.importspark.implicits._// A JSON dataset is pointed to by path.// The path can be either a single text file or a directory storing text filesvalpath="examples/src/main/resources/people.json"valpeopleDF=spark.read.json(path)// The inferred schema can be visualized using the printSchema() methodpeopleDF.printSchema()// root//  |-- age: long (nullable = true)//  |-- name: string (nullable = true)// Creates a temporary view using the DataFramepeopleDF.createOrReplaceTempView("people")// SQL statements can be run by using the sql methods provided by sparkvalteenagerNamesDF=spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")teenagerNamesDF.show()// +------+// |  name|// +------+// |Justin|// +------+// Alternatively, a DataFrame can be created for a JSON dataset represented by// a Dataset[String] storing one JSON object per stringvalotherPeopleDataset=spark.createDataset("""{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}"""::Nil)valotherPeople=spark.read.json(otherPeopleDataset)otherPeople.show()// +---------------+----+// |        address|name|// +---------------+----+// |[Columbus,Ohio]| Yin|// +---------------+----+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Hive 表

    Spark SQL 还支持读取和写入存储在Apache Hive中的数据。 但是,由于 Hive 具有大量依赖关系,因此这些依赖关系不包含在默认 Spark 分发中。 如果在类路径中找到 Hive 依赖项,Spark 将自动加载它们。 请注意,这些 Hive 依赖关系也必须存在于所有工作节点上,因为它们将需要访问 Hive 序列化和反序列化库 (SerDes),以访问存储在 Hive 中的数据。

    通过将hive-site.xml,core-site.xml(用于安全配置)和hdfs-site.xml(用于 HDFS 配置)文件放在conf/中来完成配置。

    当使用 Hive 时,必须用 Hive 支持实例化SparkSession,包括连接到持续的 Hive 转移,支持 Hive serdes 和 Hive 用户定义的功能。 没有现有 Hive 部署的用户仍然可以启用 Hive 支持。 当hive-site.xml未配置时,上下文会自动在当前目录中创建metastore_db,并创建由spark.sql.warehouse.dir配置的目录,该目录默认为Spark应用程序当前目录中的spark-warehouse目录 开始了 请注意,自从2.0.0以来,hive-site.xml中的hive.metastore.warehouse.dir属性已被弃用。 而是使用spark.sql.warehouse.dir来指定仓库中数据库的默认位置。 您可能需要向启动 Spark 应用程序的用户授予写权限。å

    Scala

    Java

    Python

    R

    importjava.io.Fileimportorg.apache.spark.sql.Rowimportorg.apache.spark.sql.SparkSessioncaseclassRecord(key:Int,value:String)// warehouseLocation points to the default location for managed databases and tablesvalwarehouseLocation=newFile("spark-warehouse").getAbsolutePathvalspark=SparkSession.builder().appName("Spark Hive Example").config("spark.sql.warehouse.dir",warehouseLocation).enableHiveSupport().getOrCreate()importspark.implicits._importspark.sqlsql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING) USING hive")sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")// Queries are expressed in HiveQLsql("SELECT * FROM src").show()// +---+-------+// |key|  value|// +---+-------+// |238|val_238|// | 86| val_86|// |311|val_311|// ...// Aggregation queries are also supported.sql("SELECT COUNT(*) FROM src").show()// +--------+// |count(1)|// +--------+// |    500 |// +--------+// The results of SQL queries are themselves DataFrames and support all normal functions.valsqlDF=sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")// The items in DataFrames are of type Row, which allows you to access each column by ordinal.valstringsDS=sqlDF.map{caseRow(key:Int,value:String)=>s"Key:$key, Value:$value"}stringsDS.show()// +--------------------+// |              value|// +--------------------+// |Key: 0, Value: val_0|// |Key: 0, Value: val_0|// |Key: 0, Value: val_0|// ...// You can also use DataFrames to create temporary views within a SparkSession.valrecordsDF=spark.createDataFrame((1to100).map(i=>Record(i,s"val_$i")))recordsDF.createOrReplaceTempView("records")// Queries can then join DataFrame data with data stored in Hive.sql("SELECT * FROM records r JOIN src s ON r.key = s.key").show()// +---+------+---+------+// |key| value|key| value|// +---+------+---+------+// |  2| val_2|  2| val_2|// |  4| val_4|  4| val_4|// |  5| val_5|  5| val_5|// ...

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/hive/SparkHiveExample.scala" in the Spark repo.

    指定 Hive 表的存储格式

    创建 Hive 表时,需要定义如何 从/向 文件系统 read/write 数据,即 “输入格式” 和 “输出格式”。 您还需要定义该表如何将数据反序列化为行,或将行序列化为数据,即 “serde”。 以下选项可用于指定存储格式 (“serde”, “input format”, “output format”),例如,CREATE TABLE src(id int) USING hive OPTIONS(fileFormat 'parquet')。 默认情况下,我们将以纯文本形式读取表格文件。 请注意,Hive 存储处理程序在创建表时不受支持,您可以使用 Hive 端的存储处理程序创建一个表,并使用 Spark SQL 来读取它。

    Property NameMeaning

    fileFormatfileFormat是一种存储格式规范的包,包括 "serde","input format" 和 "output format"。 目前我们支持6个文件格式:'sequencefile','rcfile','orc','parquet','textfile'和'avro'。

    inputFormat, outputFormat这两个选项将相应的 "InputFormat" 和 "OutputFormat" 类的名称指定为字符串文字,例如: `org.apache.hadoop.hive.ql.io.orc.OrcInputFormat`。 这两个选项必须成对出现,如果您已经指定了 "fileFormat" 选项,则无法指定它们。

    serde此选项指定 serde 类的名称。 当指定 `fileFormat` 选项时,如果给定的 `fileFormat` 已经包含 serde 的信息,那么不要指定这个选项。 目前的 "sequencefile", "textfile" 和 "rcfile" 不包含 serde 信息,你可以使用这3个文件格式的这个选项。

    fieldDelim, escapeDelim, collectionDelim, mapkeyDelim, lineDelim这些选项只能与 "textfile" 文件格式一起使用。它们定义如何将分隔的文件读入行。

    使用OPTIONS定义的所有其他属性将被视为 Hive serde 属性。

    与不同版本的 Hive Metastore 进行交互

    Spark SQL 的 Hive 支持的最重要的部分之一是与 Hive metastore 进行交互,这使得 Spark SQL 能够访问 Hive 表的元数据。 从 Spark 1.4.0 开始,使用 Spark SQL 的单一二进制构建可以使用下面所述的配置来查询不同版本的 Hive 转移。 请注意,独立于用于与转移点通信的 Hive 版本,内部 Spark SQL 将针对 Hive 1.2.1 进行编译,并使用这些类进行内部执行(serdes,UDF,UDAF等)。

    以下选项可用于配置用于检索元数据的 Hive 版本:

    属性名称默认值含义

    spark.sql.hive.metastore.version1.2.1Hive metastore 版本。 可用选项为0.12.0至1.2.1。

    spark.sql.hive.metastore.jarsbuiltin当启用-Phive时,使用 Hive 1.2.1,它与 Spark 程序集捆绑在一起。选择此选项时,spark.sql.hive.metastore.version 必须为1.2.1或未定义。 行家 使用从Maven存储库下载的指定版本的Hive jar。 通常不建议在生产部署中使用此配置。 ***** 应用于实例化 HiveMetastoreClient 的 jar 的位置。该属性可以是三个选项之一:

    builtin当启用-Phive时,使用 Hive 1.2.1,它与 Spark 程序集捆绑在一起。选择此选项时,spark.sql.hive.metastore.version必须为1.2.1或未定义。

    maven使用从 Maven 存储库下载的指定版本的 Hive jar。通常不建议在生产部署中使用此配置。

    JVM 的标准格式的 classpath。 该类路径必须包含所有 Hive 及其依赖项,包括正确版本的 Hadoop。这些罐只需要存在于 driver 程序中,但如果您正在运行在 yarn 集群模式,那么您必须确保它们与应用程序一起打包。

    spark.sql.hive.metastore.sharedPrefixescom.mysql.jdbc,

    org.postgresql,

    com.microsoft.sqlserver,

    oracle.jdbc使用逗号分隔的类前缀列表,应使用在 Spark SQL 和特定版本的 Hive 之间共享的类加载器来加载。 一个共享类的示例就是用来访问 Hive metastore 的 JDBC driver。 其它需要共享的类,是需要与已经共享的类进行交互的。 例如,log4j 使用的自定义 appender。

    spark.sql.hive.metastore.barrierPrefixes(empty)一个逗号分隔的类前缀列表,应该明确地为 Spark SQL 正在通信的 Hive 的每个版本重新加载。 例如,在通常将被共享的前缀中声明的 Hive UDF (即: �org.apache.spark.*)。

    JDBC 连接其它数据库

    Spark SQL 还包括可以使用 JDBC 从其他数据库读取数据的数据源。此功能应优于使用JdbcRDD 这是因为结果作为 DataFrame 返回,并且可以轻松地在 Spark SQL 中处理或与其他数据源连接。 JDBC 数据源也更容易从 Java 或 Python 使用,因为它不需要用户提供 ClassTag。(请注意,这不同于 Spark SQL JDBC 服务器,允许其他应用程序使用 Spark SQL 运行查询)。

    要开始使用,您需要在 Spark 类路径中包含特定数据库的 JDBC driver 程序。 例如,要从 Spark Shell 连接到 postgres,您将运行以下命令:

    bin/spark-shell --driver-class-path postgresql-9.4.1207.jar --jars postgresql-9.4.1207.jar

    可以使用 Data Sources API 将来自远程数据库的表作为 DataFrame 或 Spark SQL 临时视图进行加载。 用户可以在数据源选项中指定 JDBC 连接属性。用户和密码通常作为登录数据源的连接属性提供。 除了连接属性外,Spark 还支持以下不区分大小写的选项:

    �属性名称含义

    url要连接的JDBC URL。 源特定的连接属性可以在URL中指定。 例如jdbc:jdbc:postgresql://localhost/test?user=fred&password=secret

    dbtable应该读取的 JDBC 表。请注意,可以使用在SQL查询的FROM子句中有效的任何内容。 例如,您可以使用括号中的子查询代替完整表。

    driver用于连接到此 URL 的 JDBC driver 程序的类名。

    partitionColumn, lowerBound, upperBound如果指定了这些选项,则必须指定这些选项。 另外,必须指定numPartitions. 他们描述如何从多个 worker 并行读取数据时将表给分区。partitionColumn必须是有问题的表中的数字列。 请注意,lowerBound和upperBound仅用于决定分区的大小,而不是用于过滤表中的行。 因此,表中的所有行将被分区并返回。此选项仅适用于读操作。

    numPartitions在表读写中可以用于并行度的最大分区数。这也确定并发JDBC连接的最大数量。 如果要写入的分区数超过此限制,则在写入之前通过调用coalesce(numPartitions)将其减少到此限制。

    fetchsizeJDBC 抓取的大小,用于确定每次数据往返传递的行数。 这有利于提升 JDBC driver 的性能,它们的默认值较小(例如: Oracle 是 10 行)。 该选项仅适用于读取操作。

    batchsizeJDBC 批处理的大小,用于确定每次数据往返传递的行数。 这有利于提升 JDBC driver 的性能。 该选项仅适用于写操作。默认值为1000.

    isolationLevel事务隔离级别,适用于当前连接。 它可以是NONE,READ_COMMITTED,READ_UNCOMMITTED,REPEATABLE_READ, 或SERIALIZABLE之一,对应于 JDBC 连接对象定义的标准事务隔离级别,默认为READ_UNCOMMITTED。 此选项仅适用于写操作。请参考java.sql.Connection中的文档。

    truncate这是一个与 JDBC 相关的选项。 启用SaveMode.Overwrite时,此选项会导致 Spark 截断现有表,而不是删除并重新创建。 这可以更有效,并且防止表元数据(例如,索引)被移除。 但是,在某些情况下,例如当新数据具有不同的模式时,它将无法工作。 它默认为false。 此选项仅适用于写操作。

    createTableOptions这是一个与JDBC相关的选项。 如果指定,此选项允许在创建表时设置特定于数据库的表和分区选项(例如:CREATE TABLE t (name string) ENGINE=InnoDB.)。此选项仅适用于写操作。

    createTableColumnTypes使用数据库列数据类型而不是默认值,创建表时。 数据类型信息应以与 CREATE TABLE 列语法相同的格式指定(例如:"name CHAR(64), comments VARCHAR(1024)")。 指定的类型应该是有效的 spark sql 数据类型。此选项仅适用于写操作。

    Scala

    Java

    Python

    R

    Sql

    // Note: JDBC loading and saving can be achieved via either the load/save or jdbc methods// Loading data from a JDBC sourcevaljdbcDF=spark.read.format("jdbc").option("url","jdbc:postgresql:dbserver").option("dbtable","schema.tablename").option("user","username").option("password","password").load()valconnectionProperties=newProperties()connectionProperties.put("user","username")connectionProperties.put("password","password")valjdbcDF2=spark.read.jdbc("jdbc:postgresql:dbserver","schema.tablename",connectionProperties)// Saving data to a JDBC sourcejdbcDF.write.format("jdbc").option("url","jdbc:postgresql:dbserver").option("dbtable","schema.tablename").option("user","username").option("password","password").save()jdbcDF2.write.jdbc("jdbc:postgresql:dbserver","schema.tablename",connectionProperties)// Specifying create table column data types on writejdbcDF.write.option("createTableColumnTypes","name CHAR(64), comments VARCHAR(1024)").jdbc("jdbc:postgresql:dbserver","schema.tablename",connectionProperties)

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    故障排除

    JDBC driver 程序类必须对客户端会话和所有执行程序上的原始类加载器可见。 这是因为 Java 的 DriverManager 类执行安全检查,导致它忽略原始类加载器不可见的所有 driver 程序,当打开连接时。一个方便的方法是修改所有工作节点上的compute_classpath.sh 以包含您的 driver 程序 JAR。

    一些数据库,例如 H2,将所有名称转换为大写。 您需要使用大写字母来引用 Spark SQL 中的这些名称。

    性能调优

    对于某些工作负载,可以通过缓存内存中的数据或打开一些实验选项来提高性能。

    在内存中缓存数据

    Spark SQL 可以通过调用spark.catalog.cacheTable("tableName")或dataFrame.cache()来使用内存中的列格式来缓存表。 然后,Spark SQL 将只扫描所需的列,并将自动调整压缩以最小化内存使用量和 GC 压力。 您可以调用spark.catalog.uncacheTable("tableName")从内存中删除该表。

    内存缓存的配置可以使用SparkSession上的setConf方法或使用 SQL 运行SET key=value命令来完成。

    属性名称默认含义

    spark.sql.inMemoryColumnarStorage.compressedtrue当设置为 true 时,Spark SQL 将根据数据的统计信息为每个列自动选择一个压缩编解码器。

    spark.sql.inMemoryColumnarStorage.batchSize10000控制批量的柱状缓存的大小。更大的批量大小可以提高内存利用率和压缩率,但是在缓存数据时会冒出 OOM 风险。

    其他配置选项

    以下选项也可用于调整查询执行的性能。这些选项可能会在将来的版本中被废弃,因为更多的优化是自动执行的。

    属性名称默认值含义

    spark.sql.files.maxPartitionBytes134217728 (128 MB)在读取文件时,将单个分区打包的最大字节数。

    spark.sql.files.openCostInBytes4194304 (4 MB)按照字节数来衡量的打开文件的估计费用可以在同一时间进行扫描。 将多个文件放入分区时使用。最好过度估计,那么具有小文件的分区将比具有较大文件的分区(首先计划的)更快。

    spark.sql.broadcastTimeout300广播连接中的广播等待时间超时(秒)

    spark.sql.autoBroadcastJoinThreshold10485760 (10 MB)配置执行连接时将广播给所有工作节点的表的最大大小(以字节为单位)。 通过将此值设置为-1可以禁用广播。 请注意,目前的统计信息仅支持 Hive Metastore 表,其中已运行命令ANALYZE TABLE COMPUTE STATISTICS noscan。

    spark.sql.shuffle.partitions200Configures the number of partitions to use when shuffling data for joins or aggregations.

    分布式 SQL 引擎

    Spark SQL 也可以充当使用其 JDBC/ODBC 或命令行界面的分布式查询引擎。 在这种模式下,最终用户或应用程序可以直接与 Spark SQL 交互运行 SQL 查询,而不需要编写任何代码。

    运行 Thrift JDBC/ODBC 服务器

    这里实现的 Thrift JDBC/ODBC 服务器对应于 Hive 1.2 中的HiveServer2 您可以使用 Spark 或 Hive 1.2.1 附带的直线脚本测试 JDBC 服务器。

    要启动 JDBC/ODBC 服务器,请在 Spark 目录中运行以下命令:

    ./sbin/start-thriftserver.sh

    此脚本接受所有bin/spark-submit命令行选项,以及--hiveconf选项来指定 Hive 属性。 您可以运行./sbin/start-thriftserver.sh --help查看所有可用选项的完整列表。 默认情况下,服务器监听 localhost:10000. 您可以通过环境变量覆盖此行为,即:

    exportHIVE_SERVER2_THRIFT_PORT=exportHIVE_SERVER2_THRIFT_BIND_HOST=./sbin/start-thriftserver.sh--master ...

    or system properties:

    ./sbin/start-thriftserver.sh--hiveconf hive.server2.thrift.port=--hiveconf hive.server2.thrift.bind.host=--master   ...

    现在,您可以使用 beeline 来测试 Thrift JDBC/ODBC 服务器:

    ./bin/beeline

    使用 beeline 方式连接到 JDBC/ODBC 服务器:

    beeline> !connect jdbc:hive2://localhost:10000

    Beeline 将要求您输入用户名和密码。 在非安全模式下,只需输入机器上的用户名和空白密码即可。 对于安全模式,请按照beeline 文档中的说明进行操作。

    配置Hive是通过将hive-site.xml,core-site.xml和hdfs-site.xml文件放在conf/中完成的。

    您也可以使用 Hive 附带的 beeline 脚本。

    Thrift JDBC 服务器还支持通过 HTTP 传输发送 thrift RPC 消息。 使用以下设置启用 HTTP 模式作为系统属性或在conf/中的hive-site.xml文件中启用:

    hive.server2.transport.mode - Set this to value: http

    hive.server2.thrift.http.port - HTTP port number to listen on; default is 10001

    hive.server2.http.endpoint - HTTP endpoint; default is cliservice

    要测试,请使用 beeline 以 http 模式连接到 JDBC/ODBC 服务器:

    beeline> !connect jdbc:hive2://:/?hive.server2.transport.mode=http;hive.server2.thrift.http.path=

    运行 Spark SQL CLI

    Spark SQL CLI 是在本地模式下运行 Hive 转移服务并执行从命令行输入的查询的方便工具。 请注意,Spark SQL CLI 不能与 Thrift JDBC 服务器通信。

    要启动 Spark SQL CLI,请在 Spark 目录中运行以下命令:

    ./bin/spark-sql

    配置 Hive 是通过将hive-site.xml,core-site.xml和hdfs-site.xml文件放在conf/中完成的。 您可以运行./bin/spark-sql --help获取所有可用选项的完整列表。

    迁移指南

    Spark SQL 2.1 升级到 2.2

    Spark 2.1.1 介绍了一个新的配置 key:spark.sql.hive.caseSensitiveInferenceMode. 它的默认设置是NEVER_INFER, 其行为与 2.1.0 保持一致. 但是,Spark 2.2.0 将此设置的默认值更改为 “INFER_AND_SAVE”,以恢复与底层文件 schema(模式)具有大小写混合的列名称的 Hive metastore 表的兼容性。使用INFER_AND_SAVE配置的 value, 在第一次访问 Spark 将对其尚未保存推测 schema(模式)的任何 Hive metastore 表执行 schema inference(模式推断). 请注意,对于具有数千个 partitions(分区)的表,模式推断可能是非常耗时的操作。如果不兼容大小写混合的列名,您可以安全地将spark.sql.hive.caseSensitiveInferenceMode设置为NEVER_INFER,以避免模式推断的初始开销。请注意,使用新的默认INFER_AND_SAVE设置,模式推理的结果被保存为 metastore key 以供将来使用。因此,初始模式推断仅发生在表的第一次访问。

    Spark SQL 2.0 升级到 2.1

    Datasource tables(数据源表)现在存储了 Hive metastore 中的 partition metadata(分区元数据). 这意味着诸如ALTER TABLE PARTITION ... SET LOCATION这样的 Hive DDLs 现在使用 Datasource API 可用于创建 tables(表).

    遗留的数据源表可以通过MSCK REPAIR TABLE命令迁移到这种格式。建议迁移遗留表利用 Hive DDL 的支持和提供的计划性能。

    要确定表是否已迁移,当在表上发出DESCRIBE FORMATTED命令时请查找PartitionProvider: Catalog属性.

    Datasource tables(数据源表)的INSERT OVERWRITE TABLE ... PARTITION ...行为的更改。

    在以前的 Spark 版本中,INSERT OVERWRITE覆盖了整个 Datasource table,即使给出一个指定的 partition. 现在只有匹配规范的 partition 被覆盖。

    请注意,这仍然与 Hive 表的行为不同,Hive 表仅覆盖与新插入数据重叠的分区。

    Spark SQL 1.6 升级到 2.0

    SparkSession现在是 Spark 新的切入点, 它替代了老的SQLContext和HiveContext。注意 : 为了向下兼容,老的 SQLContext 和 HiveContext 仍然保留。可以从SparkSession获取一个新的catalog接口 — 现有的访问数据库和表的 API,如listTables,createExternalTable,dropTempView,cacheTable都被移到该接口。

    Dataset API 和 DataFrame API 进行了统一。在 Scala 中,DataFrame变成了Dataset[Row]类型的一个别名,而 Java API 使用者必须将DataFrame替换成Dataset。Dataset 类既提供了强类型转换操作(如map,filter以及groupByKey)也提供了非强类型转换操作(如select和groupBy)。由于编译期的类型安全不是 Python 和 R 语言的一个特性,Dataset 的概念并不适用于这些语言的 API。相反,DataFrame仍然是最基本的编程抽象, 就类似于这些语言中单节点 data frame 的概念。

    Dataset 和 DataFrame API 中 unionAll 已经过时并且由union替代。

    Dataset 和 DataFrame API 中 explode 已经过时,作为选择,可以结合 select 或 flatMap 使用functions.explode()。

    Dataset 和 DataFrame API 中registerTempTable已经过时并且由createOrReplaceTempView替代。

    Hive tablesCREATE TABLE ... LOCATION行为的更改.

    Spark 2.0 开始,CREATE TABLE ... LOCATION与CREATE EXTERNAL TABLE ... LOCATION是相同的,以防止意外丢弃用户提供的 locations(位置)中的现有数据。这意味着,在用户指定位置的 Spark SQL 中创建的 Hive 表始终是 Hive 外部表。删除外部表将不会删除数据。 用户不能指定 Hive managed tables(管理表)的位置. 请注意,这与Hive行为不同。

    因此,这些表上的 “DROP TABLE” 语句不会删除数据。

    Spark SQL 1.5 升级到 1.6

    Spark 1.6 开始,默认情况下服务器在多 session(会话)模式下运行。这意味着每个 JDBC/ODBC 连接拥有一份自己的 SQL 配置和临时函数注册。缓存表仍在并共享。如果您希望以旧的单会话模式运行 Thrift server,请设置选项spark.sql.hive.thriftServer.singleSession为true。您既可以将此选项添加到spark-defaults.conf,或者通过--conf将它传递给start-thriftserver.sh。

    ./sbin/start-thriftserver.sh--conf spark.sql.hive.thriftServer.singleSession=true...

    1.6.1 开始,在 sparkR 中 withColumn 方法支持添加一个新列或更换 DataFrame 同名的现有列。

    Spark 1.6 开始,LongType 强制转换为 TimestampType 期望是秒,而不是微秒。这种更改是为了匹配 Hive 1.2 的行为,以便从 numeric(数值)类型进行更一致的类型转换到 TimestampType。更多详情请参阅SPARK-11724

    Spark SQL 1.4 升级到 1.5

    使用手动管理的内存优化执行,现在是默认启用的,以及代码生成表达式求值。这些功能既可以通过设置spark.sql.tungsten.enabled为false来禁止使用。

    Parquet 的模式合并默认情况下不再启用。它可以通过设置spark.sql.parquet.mergeSchema到true以重新启用。

    字符串在 Python 列的 columns(列)现在支持使用点(.)来限定列或访问嵌套值。例如df['table.column.nestedField']。但是,这意味着如果你的列名中包含任何圆点,你现在必须避免使用反引号(如table.column.with.dots.nested)。

    在内存中的列存储分区修剪默认是开启的。它可以通过设置spark.sql.inMemoryColumnarStorage.partitionPruning为false来禁用。

    无限精度的小数列不再支持,而不是 Spark SQL 最大精度为 38 。当从BigDecimal对象推断模式时,现在使用(38,18)。在 DDL 没有指定精度时,则默认保留Decimal(10, 0)。

    时间戳现在存储在 1 微秒的精度,而不是 1 纳秒的。

    sql 语句中,floating point(浮点数)现在解析为 decimal。HiveQL 解析保持不变。

    SQL / DataFrame 函数的规范名称现在是小写(例如 sum vs SUM)。

    JSON 数据源不会自动加载由其他应用程序(未通过 Spark SQL 插入到数据集的文件)创建的新文件。对于 JSON 持久表(即表的元数据存储在 Hive Metastore),用户可以使用REFRESH TABLESQL 命令或HiveContext的refreshTable方法,把那些新文件列入到表中。对于代表一个 JSON dataset 的 DataFrame,用户需要重新创建 DataFrame,同时 DataFrame 中将包括新的文件。

    PySpark 中 DataFrame 的 withColumn 方法支持添加新的列或替换现有的同名列。

    Spark SQL 1.3 升级到 1.4

    DataFrame data reader/writer interface

    基于用户反馈,我们创建了一个新的更流畅的 API,用于读取 (SQLContext.read) 中的数据并写入数据 (DataFrame.write), 并且旧的 API 将过时(例如,SQLContext.parquetFile,SQLContext.jsonFile).

    针对SQLContext.read(Scala,Java,Python) 和DataFrame.write(Scala,Java,Python) 的更多细节,请看 API 文档.

    DataFrame.groupBy 保留 grouping columns(分组的列)

    根据用户的反馈, 我们更改了DataFrame.groupBy().agg()的默认行为以保留DataFrame结果中的 grouping columns(分组列). 为了在 1.3 中保持该行为,请设置spark.sql.retainGroupColumns为false.

    Scala

    Java

    Python

    // In 1.3.x, in order for the grouping column "department" to show up,// it must be included explicitly as part of the agg function call.df.groupBy("department").agg($"department",max("age"),sum("expense"))// In 1.4+, grouping column "department" is included automatically.df.groupBy("department").agg(max("age"),sum("expense"))// Revert to 1.3 behavior (not retaining grouping column) by:sqlContext.setConf("spark.sql.retainGroupColumns","false")

    DataFrame.withColumn 上的行为更改

    之前 1.4 版本中,DataFrame.withColumn() 只支持添加列。该列将始终在 DateFrame 结果中被加入作为新的列,即使现有的列可能存在相同的名称。从 1.4 版本开始,DataFrame.withColumn() 支持添加与所有现有列的名称不同的列或替换现有的同名列。

    请注意,这一变化仅适用于 Scala API,并不适用于 PySpark 和 SparkR。

    Spark SQL 1.0-1.2 升级到 1.3

    Spark 1.3 中,我们从 Spark SQL 中删除了 “Alpha” 的标签,作为一部分已经清理过的可用的 API 。从 Spark 1.3 版本以上,Spark SQL 将提供在 1.X 系列的其他版本的二进制兼容性。这种兼容性保证不包括被明确标记为不稳定的(即 DeveloperApi 类或 Experimental) API。

    重命名 DataFrame 的 SchemaRDD

    升级到 Spark SQL 1.3 版本时,用户会发现最大的变化是,SchemaRDD已更名为DataFrame。这主要是因为 DataFrames 不再从 RDD 直接继承,而是由 RDDS 自己来实现这些功能。DataFrames 仍然可以通过调用.rdd方法转换为 RDDS 。

    Scala 中,有一个从SchemaRDD到DataFrame类型别名,可以为一些情况提供源代码兼容性。它仍然建议用户更新他们的代码以使用DataFrame来代替。Java 和 Python 用户需要更新他们的代码。

    Java 和 Scala APIs 的统一

    此前 Spark 1.3 有单独的Java兼容类(JavaSQLContext和JavaSchemaRDD),借鉴于 Scala API。在 Spark 1.3 中,Java API 和 Scala API 已经统一。两种语言的用户可以使用SQLContext和DataFrame。一般来说论文类尝试使用两种语言的共有类型(如Array替代了一些特定集合)。在某些情况下不通用的类型情况下,(例如,passing in closures 或 Maps)使用函数重载代替。

    此外,该 Java 的特定类型的 API 已被删除。Scala 和 Java 的用户可以使用存在于org.apache.spark.sql.types类来描述编程模式。

    隔离隐式转换和删除 dsl 包(仅Scala)

    许多 Spark 1.3 版本以前的代码示例都以import sqlContext._开始,这提供了从 sqlContext 范围的所有功能。在 Spark 1.3 中,我们移除了从RDDs 到DateFrame再到SQLContext内部对象的隐式转换。用户现在应该写成import sqlContext.implicits._.

    此外,隐式转换现在只能使用方法toDF来增加由Product(即 case classes or tuples)构成的RDD,而不是自动应用。

    当使用 DSL 内部的函数时(现在使用DataFrameAPI 来替换), 用户习惯导入org.apache.spark.sql.catalyst.dsl. 相反,应该使用公共的 dataframe 函数 API:import org.apache.spark.sql.functions._.

    针对 DataType 删除在 org.apache.spark.sql 包中的一些类型别名(仅限于 Scala)

    Spark 1.3 移除存在于基本 SQL 包的DataType类型别名。开发人员应改为导入类org.apache.spark.sql.types。

    UDF 注册迁移到sqlContext.udf中 (Java & Scala)

    用于注册 UDF 的函数,不管是 DataFrame DSL 还是 SQL 中用到的,都被迁移到SQLContext中的 udf 对象中。

    Scala

    Java

    sqlContext.udf.register("strLen",(s:String)=>s.length())

    Python UDF 注册保持不变。

    Python DataTypes 不再是 Singletons(单例的)

    Python 中使用 DataTypes 时,你需要先构造它们(如:StringType()),而不是引用一个单例对象。

    Apache Hive 的兼容

    Spark SQL 在设计时就考虑到了和 Hive metastore,SerDes 以及 UDF 之间的兼容性。目前 Hive SerDes 和 UDF 都是基于 Hive 1.2.1 版本,并且Spark SQL 可以连接到不同版本的Hive metastore(从 0.12.0 到 1.2.1,可以参考与不同版本的 Hive Metastore 交互

    在现有的 Hive Warehouses 中部署

    Spark SQL Thrift JDBC server 采用了开箱即用的设计以兼容已有的 Hive 安装版本。你不需要修改现有的 Hive Metastore , 或者改变数据的位置和表的分区。

    所支持的 Hive 特性

    Spark SQL 支持绝大部分的 Hive 功能,如:

    Hive query(查询)语句, 包括:

    SELECT

    GROUP BY

    ORDER BY

    CLUSTER BY

    SORT BY

    所有 Hive 操作, 包括:

    关系运算符 (=,⇔,==,<>,<,>,>=,<=, 等等)

    算术运算符 (+,-,*,/,%, 等等)

    逻辑运算符 (AND,&&,OR,||, 等等)

    复杂类型的构造

    数学函数 (sign,ln,cos, 等等)

    String 函数 (instr,length,printf, 等等)

    用户定义函数 (UDF)

    用户定义聚合函数 (UDAF)

    用户定义 serialization formats (SerDes)

    窗口函数

    Joins

    JOIN

    {LEFT|RIGHT|FULL} OUTER JOIN

    LEFT SEMI JOIN

    CROSS JOIN

    Unions

    Sub-queries(子查询)

    SELECT col FROM ( SELECT a + b AS col from t1) t2

    Sampling

    Explain

    Partitioned tables including dynamic partition insertion

    View

    所有的 Hive DDL 函数, 包括:

    CREATE TABLE

    CREATE TABLE AS SELECT

    ALTER TABLE

    大部分的 Hive Data types(数据类型), 包括:

    TINYINT

    SMALLINT

    INT

    BIGINT

    BOOLEAN

    FLOAT

    DOUBLE

    STRING

    BINARY

    TIMESTAMP

    DATE

    ARRAY<>

    MAP<>

    STRUCT<>

    未支持的 Hive 函数

    以下是目前还不支持的 Hive 函数列表。在 Hive 部署中这些功能大部分都用不到。

    主要的 Hive 功能

    Tables 使用 buckets 的 Tables: bucket 是 Hive table partition 中的 hash partitioning. Spark SQL 还不支持 buckets.

    Esoteric Hive 功能

    UNION类型

    Unique join

    Column 统计信息的收集: Spark SQL does not piggyback scans to collect column statistics at the moment and only supports populating the sizeInBytes field of the hive metastore.

    Hive Input/Output Formats

    File format for CLI: For results showing back to the CLI, Spark SQL only supports TextOutputFormat.

    Hadoop archive

    Hive 优化

    有少数 Hive 优化还没有包含在 Spark 中。其中一些(比如 indexes 索引)由于 Spark SQL 的这种内存计算模型而显得不那么重要。另外一些在 Spark SQL 未来的版本中会持续跟踪。

    Block 级别的 bitmap indexes 和虚拟 columns (用于构建 indexes)

    自动为 join 和 groupBy 计算 reducer 个数 : 目前在 Spark SQL 中, 你需要使用 “SET spark.sql.shuffle.partitions=[num_tasks];” 来控制 post-shuffle 的并行度.

    Meta-data 的 query: 对于只使用 metadata 就能回答的查询,Spark SQL 仍然会启动计算结果的任务.

    Skew data flag: Spark SQL 不遵循 Hive 中 skew 数据的标记.

    STREAMTABLEhint in join: Spark SQL 不遵循STREAMTABLEhint.

    对于查询结果合并多个小文件: 如果输出的结果包括多个小文件, Hive 可以可选的合并小文件到一些大文件中去,以避免溢出 HDFS metadata. Spark SQL 还不支持这样.

    参考

    数据类型

    Spark SQL 和 DataFrames 支持下面的数据类型:

    Numeric types

    ByteType: Represents 1-byte signed integer numbers. The range of numbers is from-128to127.

    ShortType: Represents 2-byte signed integer numbers. The range of numbers is from-32768to32767.

    IntegerType: Represents 4-byte signed integer numbers. The range of numbers is from-2147483648to2147483647.

    LongType: Represents 8-byte signed integer numbers. The range of numbers is from-9223372036854775808to9223372036854775807.

    FloatType: Represents 4-byte single-precision floating point numbers.

    DoubleType: Represents 8-byte double-precision floating point numbers.

    DecimalType: Represents arbitrary-precision signed decimal numbers. Backed internally byjava.math.BigDecimal. ABigDecimalconsists of an arbitrary precision integer unscaled value and a 32-bit integer scale.

    String type

    StringType: Represents character string values.

    Binary type

    BinaryType: Represents byte sequence values.

    Boolean type

    BooleanType: Represents boolean values.

    Datetime type

    TimestampType: Represents values comprising values of fields year, month, day, hour, minute, and second.

    DateType: Represents values comprising values of fields year, month, day.

    Complex types

    ArrayType(elementType, containsNull): Represents values comprising a sequence of elements with the type ofelementType.containsNullis used to indicate if elements in aArrayTypevalue can havenullvalues.

    MapType(keyType, valueType, valueContainsNull): Represents values comprising a set of key-value pairs. The data type of keys are described bykeyTypeand the data type of values are described byvalueType. For aMapTypevalue, keys are not allowed to havenullvalues.valueContainsNullis used to indicate if values of aMapTypevalue can havenullvalues.

    StructType(fields): Represents values with the structure described by a sequence ofStructFields (fields).

    StructField(name, dataType, nullable): Represents a field in aStructType. The name of a field is indicated byname. The data type of a field is indicated bydataType.nullableis used to indicate if values of this fields can havenullvalues.

    Scala

    Java

    Python

    R

    Spark SQL 的所有数据类型都在包org.apache.spark.sql.types中. 你可以用下示例示例来访问它们.

    importorg.apache.spark.sql.types._

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    Data type(数据类型)Scala 中的 Value 类型访问或创建数据类型的 API

    ByteTypeByteByteType

    ShortTypeShortShortType

    IntegerTypeIntIntegerType

    LongTypeLongLongType

    FloatTypeFloatFloatType

    DoubleTypeDoubleDoubleType

    DecimalTypejava.math.BigDecimalDecimalType

    StringTypeStringStringType

    BinaryTypeArray[Byte]BinaryType

    BooleanTypeBooleanBooleanType

    TimestampTypejava.sql.TimestampTimestampType

    DateTypejava.sql.DateDateType

    ArrayTypescala.collection.SeqArrayType(elementType, [containsNull])

    Note(注意):containsNull的默认值是true.

    MapTypescala.collection.MapMapType(keyType,valueType, [valueContainsNull])

    Note(注意):valueContainsNull的默认值是true.

    StructTypeorg.apache.spark.sql.RowStructType(fields)

    Note(注意):fields是 StructFields 的 Seq. 所有, 两个 fields 拥有相同的名称是不被允许的.

    StructField该 field(字段)数据类型的 Scala 中的 value 类型 (例如, 数据类型为 IntegerType 的 StructField 是 Int)StructField(name,dataType, [nullable])

    Note:nullable的默认值是true.

    NaN Semantics

    当处理一些不符合标准浮点数语义的float或double类型时,对于 Not-a-Number(NaN) 需要做一些特殊处理. 具体如下:

    NaN = NaN 返回 true.

    aggregations(聚合)操作中,所有的 NaN values 将被分到同一个组中.

    join key 中 NaN 可以当做一个普通的值.

    NaN 值在升序排序中排到最后,比任何其他数值都大.

    我们一直在努力

    apachecn/spark-doc-zh

     

     

    Spark SQL, DataFrames and Datasets Guide

    Overview

    SQL

    Datasets and DataFrames

    开始入门

    起始点: SparkSession

    创建 DataFrames

    无类型的Dataset操作 (aka DataFrame 操作)

    Running SQL Queries Programmatically

    全局临时视图

    创建Datasets

    RDD的互操作性

    使用反射推断Schema

    以编程的方式指定Schema

    Aggregations

    Untyped User-Defined Aggregate Functions

    Type-Safe User-Defined Aggregate Functions

    Data Sources (数据源)

    Generic Load/Save Functions (通用 加载/保存 功能)

    Manually Specifying Options (手动指定选项)

    Run SQL on files directly (直接在文件上运行 SQL)

    Save Modes (保存模式)

    Saving to Persistent Tables (保存到持久表)

    Bucketing, Sorting and Partitioning (分桶, 排序和分区)

    Parquet Files

    Loading Data Programmatically (以编程的方式加载数据)

    Partition Discovery (分区发现)

    Schema Merging (模式合并)

    Hive metastore Parquet table conversion (Hive metastore Parquet table 转换)

    Hive/Parquet Schema Reconciliation

    Metadata Refreshing (元数据刷新)

    Configuration (配置)

    JSON Datasets (JSON 数据集)

    Hive 表

    指定 Hive 表的存储格式

    与不同版本的 Hive Metastore 进行交互

    JDBC 连接其它数据库

    故障排除

    性能调优

    在内存中缓存数据

    其他配置选项

    分布式 SQL 引擎

    运行 Thrift JDBC/ODBC 服务器

    运行 Spark SQL CLI

    迁移指南

    Spark SQL 2.1 升级到 2.2

    Spark SQL 2.0 升级到 2.1

    Spark SQL 1.6 升级到 2.0

    Spark SQL 1.5 升级到 1.6

    Spark SQL 1.4 升级到 1.5

    Spark SQL 1.3 升级到 1.4

    DataFrame data reader/writer interface

    DataFrame.groupBy 保留 grouping columns(分组的列)

    DataFrame.withColumn 上的行为更改

    Spark SQL 1.0-1.2 升级到 1.3

    重命名 DataFrame 的 SchemaRDD

    Java 和 Scala APIs 的统一

    隔离隐式转换和删除 dsl 包(仅Scala)

    针对 DataType 删除在 org.apache.spark.sql 包中的一些类型别名(仅限于 Scala)

    UDF 注册迁移到sqlContext.udf中 (Java & Scala)

    Python DataTypes 不再是 Singletons(单例的)

    Apache Hive 的兼容

    在现有的 Hive Warehouses 中部署

    所支持的 Hive 特性

    未支持的 Hive 函数

    参考

    数据类型

    NaN Semantics

    Overview

    Spark SQL 是 Spark 处理结构化数据的一个模块.与基础的 Spark RDD API 不同, Spark SQL 提供了查询结构化数据及计算结果等信息的接口.在内部, Spark SQL 使用这个额外的信息去执行额外的优化.有几种方式可以跟 Spark SQL 进行交互, 包括 SQL 和 Dataset API.当使用相同执行引擎进行计算时, 无论使用哪种 API / 语言都可以快速的计算.这种统一意味着开发人员能够在基于提供最自然的方式来表达一个给定的 transformation API 之间实现轻松的来回切换不同的 .

    该页面所有例子使用的示例数据都包含在 Spark 的发布中, 并且可以使用spark-shell,pysparkshell, 或者sparkRshell来运行.

    SQL

    Spark SQL 的功能之一是执行 SQL 查询.Spark SQL 也能够被用于从已存在的 Hive 环境中读取数据.更多关于如何配置这个特性的信息, 请参考Hive 表这部分. 当以另外的编程语言运行SQL 时, 查询结果将以Dataset/DataFrame的形式返回.您也可以使用命令行或者通过JDBC/ODBC SQL 接口交互.

    Datasets and DataFrames

    一个 Dataset 是一个分布式的数据集合 Dataset 是在 Spark 1.6 中被添加的新接口, 它提供了 RDD 的优点(强类型化, 能够使用强大的 lambda 函数)与Spark SQL执行引擎的优点.一个 Dataset 可以从 JVM 对象来构造并且使用转换功能(map, flatMap, filter, 等等). Dataset API 在ScalaJava是可用的.Python 不支持 Dataset API.但是由于 Python 的动态特性, 许多 Dataset API 的优点已经可用了 (也就是说, 你可能通过 name 天生的row.columnName属性访问一行中的字段).这种情况和 R 相似.

    一个 DataFrame 是一个Dataset组成的指定列.它的概念与一个在关系型数据库或者在 R/Python 中的表是相等的, 但是有很多优化. DataFrames 可以从大量的sources中构造出来, 比如: 结构化的文本文件, Hive中的表, 外部数据库, 或者已经存在的 RDDs. DataFrame API 可以在 Scala, Java,Python, 和R中实现. 在 Scala 和 Java中, 一个 DataFrame 所代表的是一个多个Row(行)的的 Dataset(数据集合). 在the Scala API,DataFrame仅仅是一个Dataset[Row]类型的别名. 然而, 在Java API, 用户需要去使用Dataset去代表一个DataFrame.

    在此文档中, 我们将常常会引用 Scala/Java Datasets 的Rows 作为 DataFrames.

    开始入门

    起始点: SparkSession

    Scala

    Java

    Python

    R

    Spark SQL中所有功能的入口点是SparkSession. 要创建一个SparkSession, 仅使用SparkSession.builder()就可以了:

    importorg.apache.spark.sql.SparkSessionvalspark=SparkSession.builder().appName("Spark SQL basic example").config("spark.some.config.option","some-value").getOrCreate()// For implicit conversions like converting RDDs to DataFramesimportspark.implicits._

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    Spark 2.0 中的SparkSession为 Hive 特性提供了内嵌的支持, 包括使用 HiveQL 编写查询的能力, 访问 Hive UDF,以及从 Hive 表中读取数据的能力.为了使用这些特性, 你不需要去有一个已存在的 Hive 设置.

    创建 DataFrames

    Scala

    Java

    Python

    R

    在一个SparkSession中, 应用程序可以从一个已经存在的RDD, 从hive表, 或者从Spark数据源中创建一个DataFrames.

    举个例子, 下面就是基于一个JSON文件创建一个DataFrame:

    valdf=spark.read.json("examples/src/main/resources/people.json")// Displays the content of the DataFrame to stdoutdf.show()// +----+-------+// | age|  name|// +----+-------+// |null|Michael|// |  30|  Andy|// |  19| Justin|// +----+-------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    无类型的Dataset操作 (aka DataFrame 操作)

    DataFrames 提供了一个特定的语法用在Scala,Java,PythonandR中机构化数据的操作.

    正如上面提到的一样, Spark 2.0中, DataFrames在Scala 和 Java API中, 仅仅是多个Rows的Dataset. 这些操作也参考了与强类型的Scala/Java Datasets中的”类型转换” 对应的”无类型转换” .

    这里包括一些使用 Dataset 进行结构化数据处理的示例 :

    Scala

    Java

    Python

    R

    // This import is needed to use the $-notationimportspark.implicits._// Print the schema in a tree formatdf.printSchema()// root// |-- age: long (nullable = true)// |-- name: string (nullable = true)// Select only the "name" columndf.select("name").show()// +-------+// |  name|// +-------+// |Michael|// |  Andy|// | Justin|// +-------+// Select everybody, but increment the age by 1df.select($"name",$"age"+1).show()// +-------+---------+// |  name|(age + 1)|// +-------+---------+// |Michael|    null|// |  Andy|      31|// | Justin|      20|// +-------+---------+// Select people older than 21df.filter($"age">21).show()// +---+----+// |age|name|// +---+----+// | 30|Andy|// +---+----+// Count people by agedf.groupBy("age").count().show()// +----+-----+// | age|count|// +----+-----+// |  19|    1|// |null|    1|// |  30|    1|// +----+-----+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    能够在 DataFrame 上被执行的操作类型的完整列表请参考API 文档.

    除了简单的列引用和表达式之外, DataFrame 也有丰富的函数库, 包括 string 操作, date 算术, 常见的 math 操作以及更多.可用的完整列表请参考DataFrame 函数指南.

    Running SQL Queries Programmatically

    Scala

    Java

    Python

    R

    SparkSession的sql函数可以让应用程序以编程的方式运行 SQL 查询, 并将结果作为一个DataFrame返回.

    // Register the DataFrame as a SQL temporary viewdf.createOrReplaceTempView("people")valsqlDF=spark.sql("SELECT * FROM people")sqlDF.show()// +----+-------+// | age|  name|// +----+-------+// |null|Michael|// |  30|  Andy|// |  19| Justin|// +----+-------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    全局临时视图

    Spark SQL中的临时视图是session级别的, 也就是会随着session的消失而消失. 如果你想让一个临时视图在所有session中相互传递并且可用, 直到Spark 应用退出, 你可以建立一个全局的临时视图.全局的临时视图存在于系统数据库global_temp中, 我们必须加上库名去引用它, 比如.SELECT * FROM global_temp.view1.

    Scala

    Java

    Python

    Sql

    // Register the DataFrame as a global temporary viewdf.createGlobalTempView("people")// Global temporary view is tied to a system preserved database `global_temp`spark.sql("SELECT * FROM global_temp.people").show()// +----+-------+// | age|  name|// +----+-------+// |null|Michael|// |  30|  Andy|// |  19| Justin|// +----+-------+// Global temporary view is cross-sessionspark.newSession().sql("SELECT * FROM global_temp.people").show()// +----+-------+// | age|  name|// +----+-------+// |null|Michael|// |  30|  Andy|// |  19| Justin|// +----+-------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    创建Datasets

    Dataset 与 RDD 相似, 然而, 并不是使用 Java 序列化或者 Kryo编码器来序列化用于处理或者通过网络进行传输的对象. 虽然编码器和标准的序列化都负责将一个对象序列化成字节, 编码器是动态生成的代码, 并且使用了一种允许 Spark 去执行许多像 filtering, sorting 以及 hashing 这样的操作, 不需要将字节反序列化成对象的格式.

    Scala

    Java

    // Note: Case classes in Scala 2.10 can support only up to 22 fields. To work around this limit,// you can use custom classes that implement the Product interfacecaseclassPerson(name:String,age:Long)// Encoders are created for case classesvalcaseClassDS=Seq(Person("Andy",32)).toDS()caseClassDS.show()// +----+---+// |name|age|// +----+---+// |Andy| 32|// +----+---+// Encoders for most common types are automatically provided by importing spark.implicits._valprimitiveDS=Seq(1,2,3).toDS()primitiveDS.map(_+1).collect()// Returns: Array(2, 3, 4)// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by namevalpath="examples/src/main/resources/people.json"valpeopleDS=spark.read.json(path).as[Person]peopleDS.show()// +----+-------+// | age|  name|// +----+-------+// |null|Michael|// |  30|  Andy|// |  19| Justin|// +----+-------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    RDD的互操作性

    Spark SQL 支持两种不同的方法用于转换已存在的 RDD 成为 Dataset.第一种方法是使用反射去推断一个包含指定的对象类型的 RDD 的 Schema.在你的 Spark 应用程序中当你已知 Schema 时这个基于方法的反射可以让你的代码更简洁.

    第二种用于创建 Dataset 的方法是通过一个允许你构造一个 Schema 然后把它应用到一个已存在的 RDD 的编程接口.然而这种方法更繁琐, 当列和它们的类型知道运行时都是未知时它允许你去构造 Dataset.

    使用反射推断Schema

    Scala

    Java

    Python

    Spark SQL 的 Scala 接口支持自动转换一个包含 case classes 的 RDD 为 DataFrame.Case class 定义了表的 Schema.Case class 的参数名使用反射读取并且成为了列名.Case class 也可以是嵌套的或者包含像Seq或者Array这样的复杂类型.这个 RDD 能够被隐式转换成一个 DataFrame 然后被注册为一个表.表可以用于后续的 SQL 语句.

    // For implicit conversions from RDDs to DataFramesimportspark.implicits._// Create an RDD of Person objects from a text file, convert it to a DataframevalpeopleDF=spark.sparkContext.textFile("examples/src/main/resources/people.txt").map(_.split(",")).map(attributes=>Person(attributes(0),attributes(1).trim.toInt)).toDF()// Register the DataFrame as a temporary viewpeopleDF.createOrReplaceTempView("people")// SQL statements can be run by using the sql methods provided by SparkvalteenagersDF=spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")// The columns of a row in the result can be accessed by field indexteenagersDF.map(teenager=>"Name: "+teenager(0)).show()// +------------+// |      value|// +------------+// |Name: Justin|// +------------+// or by field nameteenagersDF.map(teenager=>"Name: "+teenager.getAs[String]("name")).show()// +------------+// |      value|// +------------+// |Name: Justin|// +------------+// No pre-defined encoders for Dataset[Map[K,V]], define explicitlyimplicitvalmapEncoder=org.apache.spark.sql.Encoders.kryo[Map[String,Any]]// Primitive types and case classes can be also defined as// implicit val stringIntMapEncoder: Encoder[Map[String, Any]] = ExpressionEncoder()// row.getValuesMap[T] retrieves multiple columns at once into a Map[String, T]teenagersDF.map(teenager=>teenager.getValuesMap[Any](List("name","age"))).collect()// Array(Map("name" -> "Justin", "age" -> 19))

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    以编程的方式指定Schema

    Scala

    Java

    Python

    case class 不能够在执行之前被定义(例如, records 记录的结构在一个 string 字符串中被编码了, 或者一个 text 文本 dataset 将被解析并且不同的用户投影的字段是不一样的).一个DataFrame可以使用下面的三步以编程的方式来创建.

    从原始的 RDD 创建 RDD 的Row(行);

    Step 1 被创建后, 创建 Schema 表示一个StructType匹配 RDD 中的Row(行)的结构.

    通过SparkSession提供的createDataFrame方法应用 Schema 到 RDD 的 RowS(行).

    例如:

    importorg.apache.spark.sql.types._// Create an RDDvalpeopleRDD=spark.sparkContext.textFile("examples/src/main/resources/people.txt")// The schema is encoded in a stringvalschemaString="name age"// Generate the schema based on the string of schemavalfields=schemaString.split(" ").map(fieldName=>StructField(fieldName,StringType,nullable=true))valschema=StructType(fields)// Convert records of the RDD (people) to RowsvalrowRDD=peopleRDD.map(_.split(",")).map(attributes=>Row(attributes(0),attributes(1).trim))// Apply the schema to the RDDvalpeopleDF=spark.createDataFrame(rowRDD,schema)// Creates a temporary view using the DataFramepeopleDF.createOrReplaceTempView("people")// SQL can be run over a temporary view created using DataFramesvalresults=spark.sql("SELECT name FROM people")// The results of SQL queries are DataFrames and support all the normal RDD operations// The columns of a row in the result can be accessed by field index or by field nameresults.map(attributes=>"Name: "+attributes(0)).show()// +-------------+// |        value|// +-------------+// |Name: Michael|// |  Name: Andy|// | Name: Justin|// +-------------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    Aggregations

    Thebuilt-in DataFrames functionsprovide common aggregations such ascount(),countDistinct(),avg(),max(),min(), etc. While those functions are designed for DataFrames, Spark SQL also has type-safe versions for some of them inScalaandJavato work with strongly typed Datasets. Moreover, users are not limited to the predefined aggregate functions and can create their own.

    Untyped User-Defined Aggregate Functions

    Scala

    Java

    Users have to extend theUserDefinedAggregateFunctionabstract class to implement a custom untyped aggregate function. For example, a user-defined average can look like:

    importorg.apache.spark.sql.expressions.MutableAggregationBufferimportorg.apache.spark.sql.expressions.UserDefinedAggregateFunctionimportorg.apache.spark.sql.types._importorg.apache.spark.sql.Rowimportorg.apache.spark.sql.SparkSessionobjectMyAverageextendsUserDefinedAggregateFunction{// Data types of input arguments of this aggregate functiondefinputSchema:StructType=StructType(StructField("inputColumn",LongType)::Nil)// Data types of values in the aggregation bufferdefbufferSchema:StructType={StructType(StructField("sum",LongType)::StructField("count",LongType)::Nil)}// The data type of the returned valuedefdataType:DataType=DoubleType// Whether this function always returns the same output on the identical inputdefdeterministic:Boolean=true// Initializes the given aggregation buffer. The buffer itself is a `Row` that in addition to// standard methods like retrieving a value at an index (e.g., get(), getBoolean()), provides// the opportunity to update its values. Note that arrays and maps inside the buffer are still// immutable.definitialize(buffer:MutableAggregationBuffer):Unit={buffer(0)=0Lbuffer(1)=0L}// Updates the given aggregation buffer `buffer` with new input data from `input`defupdate(buffer:MutableAggregationBuffer,input:Row):Unit={if(!input.isNullAt(0)){buffer(0)=buffer.getLong(0)+input.getLong(0)buffer(1)=buffer.getLong(1)+1}}// Merges two aggregation buffers and stores the updated buffer values back to `buffer1`defmerge(buffer1:MutableAggregationBuffer,buffer2:Row):Unit={buffer1(0)=buffer1.getLong(0)+buffer2.getLong(0)buffer1(1)=buffer1.getLong(1)+buffer2.getLong(1)}// Calculates the final resultdefevaluate(buffer:Row):Double=buffer.getLong(0).toDouble/buffer.getLong(1)}// Register the function to access itspark.udf.register("myAverage",MyAverage)valdf=spark.read.json("examples/src/main/resources/employees.json")df.createOrReplaceTempView("employees")df.show()// +-------+------+// |  name|salary|// +-------+------+// |Michael|  3000|// |  Andy|  4500|// | Justin|  3500|// |  Berta|  4000|// +-------+------+valresult=spark.sql("SELECT myAverage(salary) as average_salary FROM employees")result.show()// +--------------+// |average_salary|// +--------------+// |        3750.0|// +--------------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/UserDefinedUntypedAggregation.scala" in the Spark repo.

    Type-Safe User-Defined Aggregate Functions

    User-defined aggregations for strongly typed Datasets revolve around theAggregatorabstract class. For example, a type-safe user-defined average can look like:

    Scala

    Java

    importorg.apache.spark.sql.expressions.Aggregatorimportorg.apache.spark.sql.Encoderimportorg.apache.spark.sql.Encodersimportorg.apache.spark.sql.SparkSessioncaseclassEmployee(name:String,salary:Long)caseclassAverage(varsum:Long,varcount:Long)objectMyAverageextendsAggregator[Employee,Average,Double]{// A zero value for this aggregation. Should satisfy the property that any b + zero = bdefzero:Average=Average(0L,0L)// Combine two values to produce a new value. For performance, the function may modify `buffer`// and return it instead of constructing a new objectdefreduce(buffer:Average,employee:Employee):Average={buffer.sum+=employee.salarybuffer.count+=1buffer}// Merge two intermediate valuesdefmerge(b1:Average,b2:Average):Average={b1.sum+=b2.sumb1.count+=b2.countb1}// Transform the output of the reductiondeffinish(reduction:Average):Double=reduction.sum.toDouble/reduction.count// Specifies the Encoder for the intermediate value typedefbufferEncoder:Encoder[Average]=Encoders.product// Specifies the Encoder for the final output value typedefoutputEncoder:Encoder[Double]=Encoders.scalaDouble}valds=spark.read.json("examples/src/main/resources/employees.json").as[Employee]ds.show()// +-------+------+// |  name|salary|// +-------+------+// |Michael|  3000|// |  Andy|  4500|// | Justin|  3500|// |  Berta|  4000|// +-------+------+// Convert the function to a `TypedColumn` and give it a namevalaverageSalary=MyAverage.toColumn.name("average_salary")valresult=ds.select(averageSalary)result.show()// +--------------+// |average_salary|// +--------------+// |        3750.0|// +--------------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/UserDefinedTypedAggregation.scala" in the Spark repo.

    Data Sources (数据源)

    Spark SQL 支持通过 DataFrame 接口对各种 data sources (数据源)进行操作. DataFrame 可以使用 relational transformations (关系转换)操作, 也可用于创建 temporary view (临时视图). 将 DataFrame 注册为 temporary view (临时视图)允许您对其数据运行 SQL 查询. 本节 描述了使用 Spark Data Sources 加载和保存数据的一般方法, 然后涉及可用于 built-in data sources (内置数据源)的 specific options (特定选项).

    Generic Load/Save Functions (通用 加载/保存 功能)

    在最简单的形式中, 默认数据源(parquet, 除非另有配置spark.sql.sources.default)将用于所有操作.

    Scala

    Java

    Python

    R

    valusersDF=spark.read.load("examples/src/main/resources/users.parquet")usersDF.select("name","favorite_color").write.save("namesAndFavColors.parquet")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Manually Specifying Options (手动指定选项)

    您还可以 manually specify (手动指定)将与任何你想传递给 data source 的其他选项一起使用的 data source . Data sources 由其 fully qualified name (完全限定名称)(即org.apache.spark.sql.parquet), 但是对于 built-in sources (内置的源), 你也可以使用它们的 shortnames (短名称)(json,parquet,jdbc,orc,libsvm,csv,text).从任何 data source type (数据源类型)加载 DataFrames 可以使用此 syntax (语法)转换为其他类型.

    Scala

    Java

    Python

    R

    valpeopleDF=spark.read.format("json").load("examples/src/main/resources/people.json")peopleDF.select("name","age").write.format("parquet").save("namesAndAges.parquet")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Run SQL on files directly (直接在文件上运行 SQL)

    不使用读取 API 将文件加载到 DataFrame 并进行查询, 也可以直接用 SQL 查询该文件.

    Scala

    Java

    Python

    R

    valsqlDF=spark.sql("SELECT * FROM parquet.`examples/src/main/resources/users.parquet`")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Save Modes (保存模式)

    Save operations (保存操作)可以选择使用SaveMode, 它指定如何处理现有数据如果存在的话. 重要的是要意识到, 这些 save modes (保存模式)不使用任何 locking (锁定)并且不是 atomic (原子). 另外, 当执行Overwrite时, 数据将在新数据写出之前被删除.

    Scala/JavaAny LanguageMeaning

    SaveMode.ErrorIfExists(default)"error"(default)将 DataFrame 保存到 data source (数据源)时, 如果数据已经存在, 则会抛出异常.

    SaveMode.Append"append"将 DataFrame 保存到 data source (数据源)时, 如果 data/table 已存在, 则 DataFrame 的内容将被 append (附加)到现有数据中.

    SaveMode.Overwrite"overwrite"Overwrite mode (覆盖模式)意味着将 DataFrame 保存到 data source (数据源)时, 如果 data/table 已经存在, 则预期 DataFrame 的内容将 overwritten (覆盖)现有数据.

    SaveMode.Ignore"ignore"Ignore mode (忽略模式)意味着当将 DataFrame 保存到 data source (数据源)时, 如果数据已经存在, 则保存操作预期不会保存 DataFrame 的内容, 并且不更改现有数据. 这与 SQL 中的CREATE TABLE IF NOT EXISTS类似.

    Saving to Persistent Tables (保存到持久表)

    DataFrames也可以使用saveAsTable命令作为 persistent tables (持久表)保存到 Hive metastore 中. 请注意, existing Hive deployment (现有的 Hive 部署)不需要使用此功能. Spark 将为您创建默认的 local Hive metastore (本地 Hive metastore)(使用 Derby ). 与createOrReplaceTempView命令不同,saveAsTable将 materialize (实现) DataFrame 的内容, 并创建一个指向 Hive metastore 中数据的指针. 即使您的 Spark 程序重新启动, Persistent tables (持久性表)仍然存在, 因为您保持与同一个 metastore 的连接. 可以通过使用表的名称在SparkSession上调用table方法来创建 persistent tabl (持久表)的 DataFrame .

    对于 file-based (基于文件)的 data source (数据源), 例如 text, parquet, json等, 您可以通过path选项指定 custom table path (自定义表路径), 例如df.write.option("path", "/some/path").saveAsTable("t"). 当表被 dropped (删除)时, custom table path (自定义表路径)将不会被删除, 并且表数据仍然存在. 如果未指定自定义表路径, Spark 将把数据写入 warehouse directory (仓库目录)下的默认表路径. 当表被删除时, 默认的表路径也将被删除.

    Spark 2.1 开始, persistent datasource tables (持久性数据源表)将 per-partition metadata (每个分区元数据)存储在 Hive metastore 中. 这带来了几个好处:

    由于 metastore 只能返回查询的必要 partitions (分区), 因此不再需要将第一个查询上的所有 partitions discovering 到表中.

    Hive DDLs 如ALTER TABLE PARTITION ... SET LOCATION现在可用于使用 Datasource API 创建的表.

    请注意, 创建 external datasource tables (外部数据源表)(带有path选项)的表时, 默认情况下不会收集 partition information (分区信息). 要 sync (同步) metastore 中的分区信息, 可以调用MSCK REPAIR TABLE.

    Bucketing, Sorting and Partitioning (分桶, 排序和分区)

    对于 file-based data source (基于文件的数据源), 也可以对 output (输出)进行 bucket 和 sort 或者 partition . Bucketing 和 sorting 仅适用于 persistent tables :

    Scala

    Java

    Python

    Sql

    peopleDF.write.bucketBy(42,"name").sortBy("age").saveAsTable("people_bucketed")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    在使用 Dataset API 时, partitioning 可以同时与save和saveAsTable一起使用.

    Scala

    Java

    Python

    Sql

    usersDF.write.partitionBy("favorite_color").format("parquet").save("namesPartByColor.parquet")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    可以为 single table (单个表)使用 partitioning 和 bucketing:

    Scala

    Java

    Python

    Sql

    peopleDF.write.partitionBy("favorite_color").bucketBy(42,"name").saveAsTable("people_partitioned_bucketed")

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    partitionBy创建一个 directory structure (目录结构), 如Partition Discovery部分所述. 因此, 对 cardinality (基数)较高的 columns 的适用性有限. 相反,bucketBy可以在固定数量的 buckets 中分配数据, 并且可以在 a number of unique values is unbounded (多个唯一值无界时)使用数据.

    Parquet Files

    Parquet是许多其他数据处理系统支持的 columnar format (柱状格式). Spark SQL 支持读写 Parquet 文件, 可自动保留 schema of the original data (原始数据的模式). 当编写 Parquet 文件时, 出于兼容性原因, 所有 columns 都将自动转换为可空.

    Loading Data Programmatically (以编程的方式加载数据)

    使用上面例子中的数据:

    Scala

    Java

    Python

    R

    Sql

    // Encoders for most common types are automatically provided by importing spark.implicits._importspark.implicits._valpeopleDF=spark.read.json("examples/src/main/resources/people.json")// DataFrames can be saved as Parquet files, maintaining the schema informationpeopleDF.write.parquet("people.parquet")// Read in the parquet file created above// Parquet files are self-describing so the schema is preserved// The result of loading a Parquet file is also a DataFramevalparquetFileDF=spark.read.parquet("people.parquet")// Parquet files can also be used to create a temporary view and then used in SQL statementsparquetFileDF.createOrReplaceTempView("parquetFile")valnamesDF=spark.sql("SELECT name FROM parquetFile WHERE age BETWEEN 13 AND 19")namesDF.map(attributes=>"Name: "+attributes(0)).show()// +------------+// |      value|// +------------+// |Name: Justin|// +------------+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Partition Discovery (分区发现)

    Table partitioning (表分区)是在像 Hive 这样的系统中使用的常见的优化方法. 在 partitioned table (分区表)中, 数据通常存储在不同的目录中, partitioning column values encoded (分区列值编码)在每个 partition directory (分区目录)的路径中. Parquet data source (Parquet 数据源)现在可以自动 discover (发现)和 infer (推断)分区信息. 例如, 我们可以使用以下 directory structure (目录结构)将所有以前使用的 population data (人口数据)存储到 partitioned table (分区表)中, 其中有两个额外的列gender和country作为 partitioning columns (分区列):

    path└── to    └── table        ├── gender=male        │   ├── ...        │   │        │   ├── country=US        │   │   └── data.parquet        │   ├── country=CN        │   │   └── data.parquet        │   └── ...        └── gender=female           ├── ...           │           ├── country=US           │   └── data.parquet           ├── country=CN           │   └── data.parquet           └── ...

    通过将path/to/table传递给SparkSession.read.parquet或SparkSession.read.load, Spark SQL 将自动从路径中提取 partitioning information (分区信息). 现在返回的 DataFrame 的 schema (模式)变成:

    root|-- name: string (nullable = true)|-- age: long (nullable = true)|-- gender: string (nullable = true)|-- country: string (nullable = true)

    请注意, 会自动 inferred (推断) partitioning columns (分区列)的 data types (数据类型).目前, 支持 numeric data types (数字数据类型)和 string type (字符串类型).有些用户可能不想自动推断 partitioning columns (分区列)的数据类型.对于这些用例, automatic type inference (自动类型推断)可以由spark.sql.sources.partitionColumnTypeInference.enabled配置, 默认为true.当禁用 type inference (类型推断)时, string type (字符串类型)将用于 partitioning columns (分区列).

    Spark 1.6.0 开始, 默认情况下, partition discovery (分区发现)只能找到给定路径下的 partitions (分区).对于上述示例, 如果用户将path/to/table/gender=male传递给SparkSession.read.parquet或SparkSession.read.load, 则gender将不被视为 partitioning column (分区列).如果用户需要指定 partition discovery (分区发现)应该开始的基本路径, 则可以在数据源选项中设置basePath.例如, 当path/to/table/gender=male是数据的路径并且用户将basePath设置为path/to/table/,gender将是一个 partitioning column (分区列).

    Schema Merging (模式合并)

    ProtocolBuffer , Avro 和 Thrift 一样, Parquet 也支持 schema evolution (模式演进). 用户可以从一个 simple schema (简单的架构)开始, 并根据需要逐渐向 schema 添加更多的 columns (列). 以这种方式, 用户可能会使用不同但相互兼容的 schemas 的 multiple Parquet files (多个 Parquet 文件). Parquet data source (Parquet 数据源)现在能够自动检测这种情况并 merge (合并)所有这些文件的 schemas .

    由于 schema merging (模式合并)是一个 expensive operation (相对昂贵的操作), 并且在大多数情况下不是必需的, 所以默认情况下从 1.5.0 开始. 你可以按照如下的方式启用它:

    读取 Parquet 文件时, 将 data source option (数据源选项)mergeSchema设置为true(如下面的例子所示), 或

    global SQL option (全局 SQL 选项)spark.sql.parquet.mergeSchema设置为true.

    Scala

    Java

    Python

    R

    // This is used to implicitly convert an RDD to a DataFrame.importspark.implicits._// Create a simple DataFrame, store into a partition directoryvalsquaresDF=spark.sparkContext.makeRDD(1to5).map(i=>(i,i*i)).toDF("value","square")squaresDF.write.parquet("data/test_table/key=1")// Create another DataFrame in a new partition directory,// adding a new column and dropping an existing columnvalcubesDF=spark.sparkContext.makeRDD(6to10).map(i=>(i,i*i*i)).toDF("value","cube")cubesDF.write.parquet("data/test_table/key=2")// Read the partitioned tablevalmergedDF=spark.read.option("mergeSchema","true").parquet("data/test_table")mergedDF.printSchema()// The final schema consists of all 3 columns in the Parquet files together// with the partitioning column appeared in the partition directory paths// root//  |-- value: int (nullable = true)//  |-- square: int (nullable = true)//  |-- cube: int (nullable = true)//  |-- key: int (nullable = true)

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Hive metastore Parquet table conversion (Hive metastore Parquet table 转换)

    当读取和写入 Hive metastore Parquet 表时, Spark SQL 将尝试使用自己的 Parquet support (Parquet 支持), 而不是 Hive SerDe 来获得更好的性能. 此 behavior (行为)由spark.sql.hive.convertMetastoreParquet配置控制, 默认情况下 turned on (打开).

    Hive/Parquet Schema Reconciliation

    table schema processing (表格模式处理)的角度来说, Hive 和 Parquet 之间有两个关键的区别.

    Hive 不区分大小写, 而 Parquet 不是

    Hive 认为所有 columns (列)都可以为空, 而 Parquet 中的可空性是 significant (重要)的.

    由于这个原因, 当将 Hive metastore Parquet 表转换为 Spark SQL Parquet 表时, 我们必须调整 metastore schema 与 Parquet schema. reconciliation 规则是:

    在两个 schema 中具有 same name (相同名称)的 Fields (字段)必须具有 same data type (相同的数据类型), 而不管 nullability (可空性). reconciled field 应具有 Parquet 的数据类型, 以便 nullability (可空性)得到尊重.

    reconciled schema (调和模式)正好包含 Hive metastore schema 中定义的那些字段.

    只出现在 Parquet schema 中的任何字段将被 dropped (删除)在 reconciled schema 中.

    仅在 Hive metastore schema 中出现的任何字段在 reconciled schema 中作为 nullable field (可空字段)添加.

    Metadata Refreshing (元数据刷新)

    Spark SQL 缓存 Parquet metadata 以获得更好的性能. 当启用 Hive metastore Parquet table conversion (转换)时, 这些 converted tables (转换表)的 metadata (元数据)也被 cached (缓存). 如果这些表由 Hive 或其他外部工具更新, 则需要手动刷新以确保 consistent metadata (一致的元数据).

    Scala

    Java

    Python

    Sql

    // spark is an existing SparkSessionspark.catalog.refreshTable("my_table")

    Configuration (配置)

    可以使用SparkSession上的setConf方法或使用 SQL 运行SET key = value命令来完成 Parquet 的配置.

    Property Name (参数名称)Default(默认)Meaning(含义)

    spark.sql.parquet.binaryAsStringfalse一些其他 Parquet-producing systems (Parquet 生产系统), 特别是 Impala, Hive 和旧版本的 Spark SQL , 在 writing out (写出) Parquet schema 时, 不区分 binary data (二进制数据)和 strings (字符串). 该 flag 告诉 Spark SQL 将 binary data (二进制数据)解释为 string (字符串)以提供与这些系统的兼容性.

    spark.sql.parquet.int96AsTimestamptrue一些 Parquet-producing systems , 特别是 Impala 和 Hive , 将 Timestamp 存入INT96 . 该 flag 告诉 Spark SQL 将 INT96 数据解析为 timestamp 以提供与这些系统的兼容性.

    spark.sql.parquet.cacheMetadatatrue打开 Parquet schema metadata 的缓存. 可以加快查询静态数据.

    spark.sql.parquet.compression.codecsnappy在编写 Parquet 文件时设置 compression codec (压缩编解码器)的使用. 可接受的值包括: uncompressed, snappy, gzip, lzo .

    spark.sql.parquet.filterPushdowntrue设置为 true 时启用 Parquet filter push-down optimization .

    spark.sql.hive.convertMetastoreParquettrue当设置为 false 时, Spark SQL 将使用 Hive SerDe 作为 parquet tables , 而不是内置的支持.

    spark.sql.parquet.mergeSchemafalse当为 true 时, Parquet data source (Parquet 数据源) merges (合并)从所有 data files (数据文件)收集的 schemas , 否则如果没有可用的 summary file , 则从 summary file 或 random data file 中挑选 schema .

    spark.sql.optimizer.metadataOnlytrue如果为 true , 则启用使用表的 metadata 的 metadata-only query optimization 来生成 partition columns (分区列)而不是 table scans (表扫描). 当 scanned (扫描)的所有 columns (列)都是 partition columns (分区列)并且 query (查询)具有满足 distinct semantics (不同语义)的 aggregate operator (聚合运算符)时, 它将适用.

    JSON Datasets (JSON 数据集)

    Scala

    Java

    Python

    R

    Sql

    Spark SQL 可以 automatically infer (自动推断)JSON dataset 的 schema, 并将其作为Dataset[Row]加载. 这个 conversion (转换)可以在Dataset[String]上使用SparkSession.read.json()来完成, 或 JSON 文件.

    请注意, 以a json file提供的文件不是典型的 JSON 文件. 每行必须包含一个 separate (单独的), self-contained valid (独立的有效的)JSON 对象. 有关更多信息, 请参阅JSON Lines text format, also called newline-delimited JSON.

    对于 regular multi-line JSON file (常规的多行 JSON 文件), 将multiLine选项设置为true.

    // Primitive types (Int, String, etc) and Product types (case classes) encoders are// supported by importing this when creating a Dataset.importspark.implicits._// A JSON dataset is pointed to by path.// The path can be either a single text file or a directory storing text filesvalpath="examples/src/main/resources/people.json"valpeopleDF=spark.read.json(path)// The inferred schema can be visualized using the printSchema() methodpeopleDF.printSchema()// root//  |-- age: long (nullable = true)//  |-- name: string (nullable = true)// Creates a temporary view using the DataFramepeopleDF.createOrReplaceTempView("people")// SQL statements can be run by using the sql methods provided by sparkvalteenagerNamesDF=spark.sql("SELECT name FROM people WHERE age BETWEEN 13 AND 19")teenagerNamesDF.show()// +------+// |  name|// +------+// |Justin|// +------+// Alternatively, a DataFrame can be created for a JSON dataset represented by// a Dataset[String] storing one JSON object per stringvalotherPeopleDataset=spark.createDataset("""{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}"""::Nil)valotherPeople=spark.read.json(otherPeopleDataset)otherPeople.show()// +---------------+----+// |        address|name|// +---------------+----+// |[Columbus,Ohio]| Yin|// +---------------+----+

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    Hive 表

    Spark SQL 还支持读取和写入存储在Apache Hive中的数据。 但是,由于 Hive 具有大量依赖关系,因此这些依赖关系不包含在默认 Spark 分发中。 如果在类路径中找到 Hive 依赖项,Spark 将自动加载它们。 请注意,这些 Hive 依赖关系也必须存在于所有工作节点上,因为它们将需要访问 Hive 序列化和反序列化库 (SerDes),以访问存储在 Hive 中的数据。

    通过将hive-site.xml,core-site.xml(用于安全配置)和hdfs-site.xml(用于 HDFS 配置)文件放在conf/中来完成配置。

    当使用 Hive 时,必须用 Hive 支持实例化SparkSession,包括连接到持续的 Hive 转移,支持 Hive serdes 和 Hive 用户定义的功能。 没有现有 Hive 部署的用户仍然可以启用 Hive 支持。 当hive-site.xml未配置时,上下文会自动在当前目录中创建metastore_db,并创建由spark.sql.warehouse.dir配置的目录,该目录默认为Spark应用程序当前目录中的spark-warehouse目录 开始了 请注意,自从2.0.0以来,hive-site.xml中的hive.metastore.warehouse.dir属性已被弃用。 而是使用spark.sql.warehouse.dir来指定仓库中数据库的默认位置。 您可能需要向启动 Spark 应用程序的用户授予写权限。å

    Scala

    Java

    Python

    R

    importjava.io.Fileimportorg.apache.spark.sql.Rowimportorg.apache.spark.sql.SparkSessioncaseclassRecord(key:Int,value:String)// warehouseLocation points to the default location for managed databases and tablesvalwarehouseLocation=newFile("spark-warehouse").getAbsolutePathvalspark=SparkSession.builder().appName("Spark Hive Example").config("spark.sql.warehouse.dir",warehouseLocation).enableHiveSupport().getOrCreate()importspark.implicits._importspark.sqlsql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING) USING hive")sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")// Queries are expressed in HiveQLsql("SELECT * FROM src").show()// +---+-------+// |key|  value|// +---+-------+// |238|val_238|// | 86| val_86|// |311|val_311|// ...// Aggregation queries are also supported.sql("SELECT COUNT(*) FROM src").show()// +--------+// |count(1)|// +--------+// |    500 |// +--------+// The results of SQL queries are themselves DataFrames and support all normal functions.valsqlDF=sql("SELECT key, value FROM src WHERE key < 10 ORDER BY key")// The items in DataFrames are of type Row, which allows you to access each column by ordinal.valstringsDS=sqlDF.map{caseRow(key:Int,value:String)=>s"Key:$key, Value:$value"}stringsDS.show()// +--------------------+// |              value|// +--------------------+// |Key: 0, Value: val_0|// |Key: 0, Value: val_0|// |Key: 0, Value: val_0|// ...// You can also use DataFrames to create temporary views within a SparkSession.valrecordsDF=spark.createDataFrame((1to100).map(i=>Record(i,s"val_$i")))recordsDF.createOrReplaceTempView("records")// Queries can then join DataFrame data with data stored in Hive.sql("SELECT * FROM records r JOIN src s ON r.key = s.key").show()// +---+------+---+------+// |key| value|key| value|// +---+------+---+------+// |  2| val_2|  2| val_2|// |  4| val_4|  4| val_4|// |  5| val_5|  5| val_5|// ...

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/hive/SparkHiveExample.scala" in the Spark repo.

    指定 Hive 表的存储格式

    创建 Hive 表时,需要定义如何 从/向 文件系统 read/write 数据,即 “输入格式” 和 “输出格式”。 您还需要定义该表如何将数据反序列化为行,或将行序列化为数据,即 “serde”。 以下选项可用于指定存储格式 (“serde”, “input format”, “output format”),例如,CREATE TABLE src(id int) USING hive OPTIONS(fileFormat 'parquet')。 默认情况下,我们将以纯文本形式读取表格文件。 请注意,Hive 存储处理程序在创建表时不受支持,您可以使用 Hive 端的存储处理程序创建一个表,并使用 Spark SQL 来读取它。

    Property NameMeaning

    fileFormatfileFormat是一种存储格式规范的包,包括 "serde","input format" 和 "output format"。 目前我们支持6个文件格式:'sequencefile','rcfile','orc','parquet','textfile'和'avro'。

    inputFormat, outputFormat这两个选项将相应的 "InputFormat" 和 "OutputFormat" 类的名称指定为字符串文字,例如: `org.apache.hadoop.hive.ql.io.orc.OrcInputFormat`。 这两个选项必须成对出现,如果您已经指定了 "fileFormat" 选项,则无法指定它们。

    serde此选项指定 serde 类的名称。 当指定 `fileFormat` 选项时,如果给定的 `fileFormat` 已经包含 serde 的信息,那么不要指定这个选项。 目前的 "sequencefile", "textfile" 和 "rcfile" 不包含 serde 信息,你可以使用这3个文件格式的这个选项。

    fieldDelim, escapeDelim, collectionDelim, mapkeyDelim, lineDelim这些选项只能与 "textfile" 文件格式一起使用。它们定义如何将分隔的文件读入行。

    使用OPTIONS定义的所有其他属性将被视为 Hive serde 属性。

    与不同版本的 Hive Metastore 进行交互

    Spark SQL 的 Hive 支持的最重要的部分之一是与 Hive metastore 进行交互,这使得 Spark SQL 能够访问 Hive 表的元数据。 从 Spark 1.4.0 开始,使用 Spark SQL 的单一二进制构建可以使用下面所述的配置来查询不同版本的 Hive 转移。 请注意,独立于用于与转移点通信的 Hive 版本,内部 Spark SQL 将针对 Hive 1.2.1 进行编译,并使用这些类进行内部执行(serdes,UDF,UDAF等)。

    以下选项可用于配置用于检索元数据的 Hive 版本:

    属性名称默认值含义

    spark.sql.hive.metastore.version1.2.1Hive metastore 版本。 可用选项为0.12.0至1.2.1。

    spark.sql.hive.metastore.jarsbuiltin当启用-Phive时,使用 Hive 1.2.1,它与 Spark 程序集捆绑在一起。选择此选项时,spark.sql.hive.metastore.version 必须为1.2.1或未定义。 行家 使用从Maven存储库下载的指定版本的Hive jar。 通常不建议在生产部署中使用此配置。 ***** 应用于实例化 HiveMetastoreClient 的 jar 的位置。该属性可以是三个选项之一:

    builtin当启用-Phive时,使用 Hive 1.2.1,它与 Spark 程序集捆绑在一起。选择此选项时,spark.sql.hive.metastore.version必须为1.2.1或未定义。

    maven使用从 Maven 存储库下载的指定版本的 Hive jar。通常不建议在生产部署中使用此配置。

    JVM 的标准格式的 classpath。 该类路径必须包含所有 Hive 及其依赖项,包括正确版本的 Hadoop。这些罐只需要存在于 driver 程序中,但如果您正在运行在 yarn 集群模式,那么您必须确保它们与应用程序一起打包。

    spark.sql.hive.metastore.sharedPrefixescom.mysql.jdbc,

    org.postgresql,

    com.microsoft.sqlserver,

    oracle.jdbc使用逗号分隔的类前缀列表,应使用在 Spark SQL 和特定版本的 Hive 之间共享的类加载器来加载。 一个共享类的示例就是用来访问 Hive metastore 的 JDBC driver。 其它需要共享的类,是需要与已经共享的类进行交互的。 例如,log4j 使用的自定义 appender。

    spark.sql.hive.metastore.barrierPrefixes(empty)一个逗号分隔的类前缀列表,应该明确地为 Spark SQL 正在通信的 Hive 的每个版本重新加载。 例如,在通常将被共享的前缀中声明的 Hive UDF (即: �org.apache.spark.*)。

    JDBC 连接其它数据库

    Spark SQL 还包括可以使用 JDBC 从其他数据库读取数据的数据源。此功能应优于使用JdbcRDD 这是因为结果作为 DataFrame 返回,并且可以轻松地在 Spark SQL 中处理或与其他数据源连接。 JDBC 数据源也更容易从 Java 或 Python 使用,因为它不需要用户提供 ClassTag。(请注意,这不同于 Spark SQL JDBC 服务器,允许其他应用程序使用 Spark SQL 运行查询)。

    要开始使用,您需要在 Spark 类路径中包含特定数据库的 JDBC driver 程序。 例如,要从 Spark Shell 连接到 postgres,您将运行以下命令:

    bin/spark-shell --driver-class-path postgresql-9.4.1207.jar --jars postgresql-9.4.1207.jar

    可以使用 Data Sources API 将来自远程数据库的表作为 DataFrame 或 Spark SQL 临时视图进行加载。 用户可以在数据源选项中指定 JDBC 连接属性。用户和密码通常作为登录数据源的连接属性提供。 除了连接属性外,Spark 还支持以下不区分大小写的选项:

    �属性名称含义

    url要连接的JDBC URL。 源特定的连接属性可以在URL中指定。 例如jdbc:jdbc:postgresql://localhost/test?user=fred&password=secret

    dbtable应该读取的 JDBC 表。请注意,可以使用在SQL查询的FROM子句中有效的任何内容。 例如,您可以使用括号中的子查询代替完整表。

    driver用于连接到此 URL 的 JDBC driver 程序的类名。

    partitionColumn, lowerBound, upperBound如果指定了这些选项,则必须指定这些选项。 另外,必须指定numPartitions. 他们描述如何从多个 worker 并行读取数据时将表给分区。partitionColumn必须是有问题的表中的数字列。 请注意,lowerBound和upperBound仅用于决定分区的大小,而不是用于过滤表中的行。 因此,表中的所有行将被分区并返回。此选项仅适用于读操作。

    numPartitions在表读写中可以用于并行度的最大分区数。这也确定并发JDBC连接的最大数量。 如果要写入的分区数超过此限制,则在写入之前通过调用coalesce(numPartitions)将其减少到此限制。

    fetchsizeJDBC 抓取的大小,用于确定每次数据往返传递的行数。 这有利于提升 JDBC driver 的性能,它们的默认值较小(例如: Oracle 是 10 行)。 该选项仅适用于读取操作。

    batchsizeJDBC 批处理的大小,用于确定每次数据往返传递的行数。 这有利于提升 JDBC driver 的性能。 该选项仅适用于写操作。默认值为1000.

    isolationLevel事务隔离级别,适用于当前连接。 它可以是NONE,READ_COMMITTED,READ_UNCOMMITTED,REPEATABLE_READ, 或SERIALIZABLE之一,对应于 JDBC 连接对象定义的标准事务隔离级别,默认为READ_UNCOMMITTED。 此选项仅适用于写操作。请参考java.sql.Connection中的文档。

    truncate这是一个与 JDBC 相关的选项。 启用SaveMode.Overwrite时,此选项会导致 Spark 截断现有表,而不是删除并重新创建。 这可以更有效,并且防止表元数据(例如,索引)被移除。 但是,在某些情况下,例如当新数据具有不同的模式时,它将无法工作。 它默认为false。 此选项仅适用于写操作。

    createTableOptions这是一个与JDBC相关的选项。 如果指定,此选项允许在创建表时设置特定于数据库的表和分区选项(例如:CREATE TABLE t (name string) ENGINE=InnoDB.)。此选项仅适用于写操作。

    createTableColumnTypes使用数据库列数据类型而不是默认值,创建表时。 数据类型信息应以与 CREATE TABLE 列语法相同的格式指定(例如:"name CHAR(64), comments VARCHAR(1024)")。 指定的类型应该是有效的 spark sql 数据类型。此选项仅适用于写操作。

    Scala

    Java

    Python

    R

    Sql

    // Note: JDBC loading and saving can be achieved via either the load/save or jdbc methods// Loading data from a JDBC sourcevaljdbcDF=spark.read.format("jdbc").option("url","jdbc:postgresql:dbserver").option("dbtable","schema.tablename").option("user","username").option("password","password").load()valconnectionProperties=newProperties()connectionProperties.put("user","username")connectionProperties.put("password","password")valjdbcDF2=spark.read.jdbc("jdbc:postgresql:dbserver","schema.tablename",connectionProperties)// Saving data to a JDBC sourcejdbcDF.write.format("jdbc").option("url","jdbc:postgresql:dbserver").option("dbtable","schema.tablename").option("user","username").option("password","password").save()jdbcDF2.write.jdbc("jdbc:postgresql:dbserver","schema.tablename",connectionProperties)// Specifying create table column data types on writejdbcDF.write.option("createTableColumnTypes","name CHAR(64), comments VARCHAR(1024)").jdbc("jdbc:postgresql:dbserver","schema.tablename",connectionProperties)

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SQLDataSourceExample.scala" in the Spark repo.

    故障排除

    JDBC driver 程序类必须对客户端会话和所有执行程序上的原始类加载器可见。 这是因为 Java 的 DriverManager 类执行安全检查,导致它忽略原始类加载器不可见的所有 driver 程序,当打开连接时。一个方便的方法是修改所有工作节点上的compute_classpath.sh 以包含您的 driver 程序 JAR。

    一些数据库,例如 H2,将所有名称转换为大写。 您需要使用大写字母来引用 Spark SQL 中的这些名称。

    性能调优

    对于某些工作负载,可以通过缓存内存中的数据或打开一些实验选项来提高性能。

    在内存中缓存数据

    Spark SQL 可以通过调用spark.catalog.cacheTable("tableName")或dataFrame.cache()来使用内存中的列格式来缓存表。 然后,Spark SQL 将只扫描所需的列,并将自动调整压缩以最小化内存使用量和 GC 压力。 您可以调用spark.catalog.uncacheTable("tableName")从内存中删除该表。

    内存缓存的配置可以使用SparkSession上的setConf方法或使用 SQL 运行SET key=value命令来完成。

    属性名称默认含义

    spark.sql.inMemoryColumnarStorage.compressedtrue当设置为 true 时,Spark SQL 将根据数据的统计信息为每个列自动选择一个压缩编解码器。

    spark.sql.inMemoryColumnarStorage.batchSize10000控制批量的柱状缓存的大小。更大的批量大小可以提高内存利用率和压缩率,但是在缓存数据时会冒出 OOM 风险。

    其他配置选项

    以下选项也可用于调整查询执行的性能。这些选项可能会在将来的版本中被废弃,因为更多的优化是自动执行的。

    属性名称默认值含义

    spark.sql.files.maxPartitionBytes134217728 (128 MB)在读取文件时,将单个分区打包的最大字节数。

    spark.sql.files.openCostInBytes4194304 (4 MB)按照字节数来衡量的打开文件的估计费用可以在同一时间进行扫描。 将多个文件放入分区时使用。最好过度估计,那么具有小文件的分区将比具有较大文件的分区(首先计划的)更快。

    spark.sql.broadcastTimeout300广播连接中的广播等待时间超时(秒)

    spark.sql.autoBroadcastJoinThreshold10485760 (10 MB)配置执行连接时将广播给所有工作节点的表的最大大小(以字节为单位)。 通过将此值设置为-1可以禁用广播。 请注意,目前的统计信息仅支持 Hive Metastore 表,其中已运行命令ANALYZE TABLE COMPUTE STATISTICS noscan。

    spark.sql.shuffle.partitions200Configures the number of partitions to use when shuffling data for joins or aggregations.

    分布式 SQL 引擎

    Spark SQL 也可以充当使用其 JDBC/ODBC 或命令行界面的分布式查询引擎。 在这种模式下,最终用户或应用程序可以直接与 Spark SQL 交互运行 SQL 查询,而不需要编写任何代码。

    运行 Thrift JDBC/ODBC 服务器

    这里实现的 Thrift JDBC/ODBC 服务器对应于 Hive 1.2 中的HiveServer2 您可以使用 Spark 或 Hive 1.2.1 附带的直线脚本测试 JDBC 服务器。

    要启动 JDBC/ODBC 服务器,请在 Spark 目录中运行以下命令:

    ./sbin/start-thriftserver.sh

    此脚本接受所有bin/spark-submit命令行选项,以及--hiveconf选项来指定 Hive 属性。 您可以运行./sbin/start-thriftserver.sh --help查看所有可用选项的完整列表。 默认情况下,服务器监听 localhost:10000. 您可以通过环境变量覆盖此行为,即:

    exportHIVE_SERVER2_THRIFT_PORT=exportHIVE_SERVER2_THRIFT_BIND_HOST=./sbin/start-thriftserver.sh--master ...

    or system properties:

    ./sbin/start-thriftserver.sh--hiveconf hive.server2.thrift.port=--hiveconf hive.server2.thrift.bind.host=--master   ...

    现在,您可以使用 beeline 来测试 Thrift JDBC/ODBC 服务器:

    ./bin/beeline

    使用 beeline 方式连接到 JDBC/ODBC 服务器:

    beeline> !connect jdbc:hive2://localhost:10000

    Beeline 将要求您输入用户名和密码。 在非安全模式下,只需输入机器上的用户名和空白密码即可。 对于安全模式,请按照beeline 文档中的说明进行操作。

    配置Hive是通过将hive-site.xml,core-site.xml和hdfs-site.xml文件放在conf/中完成的。

    您也可以使用 Hive 附带的 beeline 脚本。

    Thrift JDBC 服务器还支持通过 HTTP 传输发送 thrift RPC 消息。 使用以下设置启用 HTTP 模式作为系统属性或在conf/中的hive-site.xml文件中启用:

    hive.server2.transport.mode - Set this to value: http

    hive.server2.thrift.http.port - HTTP port number to listen on; default is 10001

    hive.server2.http.endpoint - HTTP endpoint; default is cliservice

    要测试,请使用 beeline 以 http 模式连接到 JDBC/ODBC 服务器:

    beeline> !connect jdbc:hive2://:/?hive.server2.transport.mode=http;hive.server2.thrift.http.path=

    运行 Spark SQL CLI

    Spark SQL CLI 是在本地模式下运行 Hive 转移服务并执行从命令行输入的查询的方便工具。 请注意,Spark SQL CLI 不能与 Thrift JDBC 服务器通信。

    要启动 Spark SQL CLI,请在 Spark 目录中运行以下命令:

    ./bin/spark-sql

    配置 Hive 是通过将hive-site.xml,core-site.xml和hdfs-site.xml文件放在conf/中完成的。 您可以运行./bin/spark-sql --help获取所有可用选项的完整列表。

    迁移指南

    Spark SQL 2.1 升级到 2.2

    Spark 2.1.1 介绍了一个新的配置 key:spark.sql.hive.caseSensitiveInferenceMode. 它的默认设置是NEVER_INFER, 其行为与 2.1.0 保持一致. 但是,Spark 2.2.0 将此设置的默认值更改为 “INFER_AND_SAVE”,以恢复与底层文件 schema(模式)具有大小写混合的列名称的 Hive metastore 表的兼容性。使用INFER_AND_SAVE配置的 value, 在第一次访问 Spark 将对其尚未保存推测 schema(模式)的任何 Hive metastore 表执行 schema inference(模式推断). 请注意,对于具有数千个 partitions(分区)的表,模式推断可能是非常耗时的操作。如果不兼容大小写混合的列名,您可以安全地将spark.sql.hive.caseSensitiveInferenceMode设置为NEVER_INFER,以避免模式推断的初始开销。请注意,使用新的默认INFER_AND_SAVE设置,模式推理的结果被保存为 metastore key 以供将来使用。因此,初始模式推断仅发生在表的第一次访问。

    Spark SQL 2.0 升级到 2.1

    Datasource tables(数据源表)现在存储了 Hive metastore 中的 partition metadata(分区元数据). 这意味着诸如ALTER TABLE PARTITION ... SET LOCATION这样的 Hive DDLs 现在使用 Datasource API 可用于创建 tables(表).

    遗留的数据源表可以通过MSCK REPAIR TABLE命令迁移到这种格式。建议迁移遗留表利用 Hive DDL 的支持和提供的计划性能。

    要确定表是否已迁移,当在表上发出DESCRIBE FORMATTED命令时请查找PartitionProvider: Catalog属性.

    Datasource tables(数据源表)的INSERT OVERWRITE TABLE ... PARTITION ...行为的更改。

    在以前的 Spark 版本中,INSERT OVERWRITE覆盖了整个 Datasource table,即使给出一个指定的 partition. 现在只有匹配规范的 partition 被覆盖。

    请注意,这仍然与 Hive 表的行为不同,Hive 表仅覆盖与新插入数据重叠的分区。

    Spark SQL 1.6 升级到 2.0

    SparkSession现在是 Spark 新的切入点, 它替代了老的SQLContext和HiveContext。注意 : 为了向下兼容,老的 SQLContext 和 HiveContext 仍然保留。可以从SparkSession获取一个新的catalog接口 — 现有的访问数据库和表的 API,如listTables,createExternalTable,dropTempView,cacheTable都被移到该接口。

    Dataset API 和 DataFrame API 进行了统一。在 Scala 中,DataFrame变成了Dataset[Row]类型的一个别名,而 Java API 使用者必须将DataFrame替换成Dataset。Dataset 类既提供了强类型转换操作(如map,filter以及groupByKey)也提供了非强类型转换操作(如select和groupBy)。由于编译期的类型安全不是 Python 和 R 语言的一个特性,Dataset 的概念并不适用于这些语言的 API。相反,DataFrame仍然是最基本的编程抽象, 就类似于这些语言中单节点 data frame 的概念。

    Dataset 和 DataFrame API 中 unionAll 已经过时并且由union替代。

    Dataset 和 DataFrame API 中 explode 已经过时,作为选择,可以结合 select 或 flatMap 使用functions.explode()。

    Dataset 和 DataFrame API 中registerTempTable已经过时并且由createOrReplaceTempView替代。

    Hive tablesCREATE TABLE ... LOCATION行为的更改.

    Spark 2.0 开始,CREATE TABLE ... LOCATION与CREATE EXTERNAL TABLE ... LOCATION是相同的,以防止意外丢弃用户提供的 locations(位置)中的现有数据。这意味着,在用户指定位置的 Spark SQL 中创建的 Hive 表始终是 Hive 外部表。删除外部表将不会删除数据。 用户不能指定 Hive managed tables(管理表)的位置. 请注意,这与Hive行为不同。

    因此,这些表上的 “DROP TABLE” 语句不会删除数据。

    Spark SQL 1.5 升级到 1.6

    Spark 1.6 开始,默认情况下服务器在多 session(会话)模式下运行。这意味着每个 JDBC/ODBC 连接拥有一份自己的 SQL 配置和临时函数注册。缓存表仍在并共享。如果您希望以旧的单会话模式运行 Thrift server,请设置选项spark.sql.hive.thriftServer.singleSession为true。您既可以将此选项添加到spark-defaults.conf,或者通过--conf将它传递给start-thriftserver.sh。

    ./sbin/start-thriftserver.sh--conf spark.sql.hive.thriftServer.singleSession=true...

    1.6.1 开始,在 sparkR 中 withColumn 方法支持添加一个新列或更换 DataFrame 同名的现有列。

    Spark 1.6 开始,LongType 强制转换为 TimestampType 期望是秒,而不是微秒。这种更改是为了匹配 Hive 1.2 的行为,以便从 numeric(数值)类型进行更一致的类型转换到 TimestampType。更多详情请参阅SPARK-11724

    Spark SQL 1.4 升级到 1.5

    使用手动管理的内存优化执行,现在是默认启用的,以及代码生成表达式求值。这些功能既可以通过设置spark.sql.tungsten.enabled为false来禁止使用。

    Parquet 的模式合并默认情况下不再启用。它可以通过设置spark.sql.parquet.mergeSchema到true以重新启用。

    字符串在 Python 列的 columns(列)现在支持使用点(.)来限定列或访问嵌套值。例如df['table.column.nestedField']。但是,这意味着如果你的列名中包含任何圆点,你现在必须避免使用反引号(如table.column.with.dots.nested)。

    在内存中的列存储分区修剪默认是开启的。它可以通过设置spark.sql.inMemoryColumnarStorage.partitionPruning为false来禁用。

    无限精度的小数列不再支持,而不是 Spark SQL 最大精度为 38 。当从BigDecimal对象推断模式时,现在使用(38,18)。在 DDL 没有指定精度时,则默认保留Decimal(10, 0)。

    时间戳现在存储在 1 微秒的精度,而不是 1 纳秒的。

    sql 语句中,floating point(浮点数)现在解析为 decimal。HiveQL 解析保持不变。

    SQL / DataFrame 函数的规范名称现在是小写(例如 sum vs SUM)。

    JSON 数据源不会自动加载由其他应用程序(未通过 Spark SQL 插入到数据集的文件)创建的新文件。对于 JSON 持久表(即表的元数据存储在 Hive Metastore),用户可以使用REFRESH TABLESQL 命令或HiveContext的refreshTable方法,把那些新文件列入到表中。对于代表一个 JSON dataset 的 DataFrame,用户需要重新创建 DataFrame,同时 DataFrame 中将包括新的文件。

    PySpark 中 DataFrame 的 withColumn 方法支持添加新的列或替换现有的同名列。

    Spark SQL 1.3 升级到 1.4

    DataFrame data reader/writer interface

    基于用户反馈,我们创建了一个新的更流畅的 API,用于读取 (SQLContext.read) 中的数据并写入数据 (DataFrame.write), 并且旧的 API 将过时(例如,SQLContext.parquetFile,SQLContext.jsonFile).

    针对SQLContext.read(Scala,Java,Python) 和DataFrame.write(Scala,Java,Python) 的更多细节,请看 API 文档.

    DataFrame.groupBy 保留 grouping columns(分组的列)

    根据用户的反馈, 我们更改了DataFrame.groupBy().agg()的默认行为以保留DataFrame结果中的 grouping columns(分组列). 为了在 1.3 中保持该行为,请设置spark.sql.retainGroupColumns为false.

    Scala

    Java

    Python

    // In 1.3.x, in order for the grouping column "department" to show up,// it must be included explicitly as part of the agg function call.df.groupBy("department").agg($"department",max("age"),sum("expense"))// In 1.4+, grouping column "department" is included automatically.df.groupBy("department").agg(max("age"),sum("expense"))// Revert to 1.3 behavior (not retaining grouping column) by:sqlContext.setConf("spark.sql.retainGroupColumns","false")

    DataFrame.withColumn 上的行为更改

    之前 1.4 版本中,DataFrame.withColumn() 只支持添加列。该列将始终在 DateFrame 结果中被加入作为新的列,即使现有的列可能存在相同的名称。从 1.4 版本开始,DataFrame.withColumn() 支持添加与所有现有列的名称不同的列或替换现有的同名列。

    请注意,这一变化仅适用于 Scala API,并不适用于 PySpark 和 SparkR。

    Spark SQL 1.0-1.2 升级到 1.3

    Spark 1.3 中,我们从 Spark SQL 中删除了 “Alpha” 的标签,作为一部分已经清理过的可用的 API 。从 Spark 1.3 版本以上,Spark SQL 将提供在 1.X 系列的其他版本的二进制兼容性。这种兼容性保证不包括被明确标记为不稳定的(即 DeveloperApi 类或 Experimental) API。

    重命名 DataFrame 的 SchemaRDD

    升级到 Spark SQL 1.3 版本时,用户会发现最大的变化是,SchemaRDD已更名为DataFrame。这主要是因为 DataFrames 不再从 RDD 直接继承,而是由 RDDS 自己来实现这些功能。DataFrames 仍然可以通过调用.rdd方法转换为 RDDS 。

    Scala 中,有一个从SchemaRDD到DataFrame类型别名,可以为一些情况提供源代码兼容性。它仍然建议用户更新他们的代码以使用DataFrame来代替。Java 和 Python 用户需要更新他们的代码。

    Java 和 Scala APIs 的统一

    此前 Spark 1.3 有单独的Java兼容类(JavaSQLContext和JavaSchemaRDD),借鉴于 Scala API。在 Spark 1.3 中,Java API 和 Scala API 已经统一。两种语言的用户可以使用SQLContext和DataFrame。一般来说论文类尝试使用两种语言的共有类型(如Array替代了一些特定集合)。在某些情况下不通用的类型情况下,(例如,passing in closures 或 Maps)使用函数重载代替。

    此外,该 Java 的特定类型的 API 已被删除。Scala 和 Java 的用户可以使用存在于org.apache.spark.sql.types类来描述编程模式。

    隔离隐式转换和删除 dsl 包(仅Scala)

    许多 Spark 1.3 版本以前的代码示例都以import sqlContext._开始,这提供了从 sqlContext 范围的所有功能。在 Spark 1.3 中,我们移除了从RDDs 到DateFrame再到SQLContext内部对象的隐式转换。用户现在应该写成import sqlContext.implicits._.

    此外,隐式转换现在只能使用方法toDF来增加由Product(即 case classes or tuples)构成的RDD,而不是自动应用。

    当使用 DSL 内部的函数时(现在使用DataFrameAPI 来替换), 用户习惯导入org.apache.spark.sql.catalyst.dsl. 相反,应该使用公共的 dataframe 函数 API:import org.apache.spark.sql.functions._.

    针对 DataType 删除在 org.apache.spark.sql 包中的一些类型别名(仅限于 Scala)

    Spark 1.3 移除存在于基本 SQL 包的DataType类型别名。开发人员应改为导入类org.apache.spark.sql.types。

    UDF 注册迁移到sqlContext.udf中 (Java & Scala)

    用于注册 UDF 的函数,不管是 DataFrame DSL 还是 SQL 中用到的,都被迁移到SQLContext中的 udf 对象中。

    Scala

    Java

    sqlContext.udf.register("strLen",(s:String)=>s.length())

    Python UDF 注册保持不变。

    Python DataTypes 不再是 Singletons(单例的)

    Python 中使用 DataTypes 时,你需要先构造它们(如:StringType()),而不是引用一个单例对象。

    Apache Hive 的兼容

    Spark SQL 在设计时就考虑到了和 Hive metastore,SerDes 以及 UDF 之间的兼容性。目前 Hive SerDes 和 UDF 都是基于 Hive 1.2.1 版本,并且Spark SQL 可以连接到不同版本的Hive metastore(从 0.12.0 到 1.2.1,可以参考与不同版本的 Hive Metastore 交互

    在现有的 Hive Warehouses 中部署

    Spark SQL Thrift JDBC server 采用了开箱即用的设计以兼容已有的 Hive 安装版本。你不需要修改现有的 Hive Metastore , 或者改变数据的位置和表的分区。

    所支持的 Hive 特性

    Spark SQL 支持绝大部分的 Hive 功能,如:

    Hive query(查询)语句, 包括:

    SELECT

    GROUP BY

    ORDER BY

    CLUSTER BY

    SORT BY

    所有 Hive 操作, 包括:

    关系运算符 (=,⇔,==,<>,<,>,>=,<=, 等等)

    算术运算符 (+,-,*,/,%, 等等)

    逻辑运算符 (AND,&&,OR,||, 等等)

    复杂类型的构造

    数学函数 (sign,ln,cos, 等等)

    String 函数 (instr,length,printf, 等等)

    用户定义函数 (UDF)

    用户定义聚合函数 (UDAF)

    用户定义 serialization formats (SerDes)

    窗口函数

    Joins

    JOIN

    {LEFT|RIGHT|FULL} OUTER JOIN

    LEFT SEMI JOIN

    CROSS JOIN

    Unions

    Sub-queries(子查询)

    SELECT col FROM ( SELECT a + b AS col from t1) t2

    Sampling

    Explain

    Partitioned tables including dynamic partition insertion

    View

    所有的 Hive DDL 函数, 包括:

    CREATE TABLE

    CREATE TABLE AS SELECT

    ALTER TABLE

    大部分的 Hive Data types(数据类型), 包括:

    TINYINT

    SMALLINT

    INT

    BIGINT

    BOOLEAN

    FLOAT

    DOUBLE

    STRING

    BINARY

    TIMESTAMP

    DATE

    ARRAY<>

    MAP<>

    STRUCT<>

    未支持的 Hive 函数

    以下是目前还不支持的 Hive 函数列表。在 Hive 部署中这些功能大部分都用不到。

    主要的 Hive 功能

    Tables 使用 buckets 的 Tables: bucket 是 Hive table partition 中的 hash partitioning. Spark SQL 还不支持 buckets.

    Esoteric Hive 功能

    UNION类型

    Unique join

    Column 统计信息的收集: Spark SQL does not piggyback scans to collect column statistics at the moment and only supports populating the sizeInBytes field of the hive metastore.

    Hive Input/Output Formats

    File format for CLI: For results showing back to the CLI, Spark SQL only supports TextOutputFormat.

    Hadoop archive

    Hive 优化

    有少数 Hive 优化还没有包含在 Spark 中。其中一些(比如 indexes 索引)由于 Spark SQL 的这种内存计算模型而显得不那么重要。另外一些在 Spark SQL 未来的版本中会持续跟踪。

    Block 级别的 bitmap indexes 和虚拟 columns (用于构建 indexes)

    自动为 join 和 groupBy 计算 reducer 个数 : 目前在 Spark SQL 中, 你需要使用 “SET spark.sql.shuffle.partitions=[num_tasks];” 来控制 post-shuffle 的并行度.

    Meta-data 的 query: 对于只使用 metadata 就能回答的查询,Spark SQL 仍然会启动计算结果的任务.

    Skew data flag: Spark SQL 不遵循 Hive 中 skew 数据的标记.

    STREAMTABLEhint in join: Spark SQL 不遵循STREAMTABLEhint.

    对于查询结果合并多个小文件: 如果输出的结果包括多个小文件, Hive 可以可选的合并小文件到一些大文件中去,以避免溢出 HDFS metadata. Spark SQL 还不支持这样.

    参考

    数据类型

    Spark SQL 和 DataFrames 支持下面的数据类型:

    Numeric types

    ByteType: Represents 1-byte signed integer numbers. The range of numbers is from-128to127.

    ShortType: Represents 2-byte signed integer numbers. The range of numbers is from-32768to32767.

    IntegerType: Represents 4-byte signed integer numbers. The range of numbers is from-2147483648to2147483647.

    LongType: Represents 8-byte signed integer numbers. The range of numbers is from-9223372036854775808to9223372036854775807.

    FloatType: Represents 4-byte single-precision floating point numbers.

    DoubleType: Represents 8-byte double-precision floating point numbers.

    DecimalType: Represents arbitrary-precision signed decimal numbers. Backed internally byjava.math.BigDecimal. ABigDecimalconsists of an arbitrary precision integer unscaled value and a 32-bit integer scale.

    String type

    StringType: Represents character string values.

    Binary type

    BinaryType: Represents byte sequence values.

    Boolean type

    BooleanType: Represents boolean values.

    Datetime type

    TimestampType: Represents values comprising values of fields year, month, day, hour, minute, and second.

    DateType: Represents values comprising values of fields year, month, day.

    Complex types

    ArrayType(elementType, containsNull): Represents values comprising a sequence of elements with the type ofelementType.containsNullis used to indicate if elements in aArrayTypevalue can havenullvalues.

    MapType(keyType, valueType, valueContainsNull): Represents values comprising a set of key-value pairs. The data type of keys are described bykeyTypeand the data type of values are described byvalueType. For aMapTypevalue, keys are not allowed to havenullvalues.valueContainsNullis used to indicate if values of aMapTypevalue can havenullvalues.

    StructType(fields): Represents values with the structure described by a sequence ofStructFields (fields).

    StructField(name, dataType, nullable): Represents a field in aStructType. The name of a field is indicated byname. The data type of a field is indicated bydataType.nullableis used to indicate if values of this fields can havenullvalues.

    Scala

    Java

    Python

    R

    Spark SQL 的所有数据类型都在包org.apache.spark.sql.types中. 你可以用下示例示例来访问它们.

    importorg.apache.spark.sql.types._

    Find full example code at "examples/src/main/scala/org/apache/spark/examples/sql/SparkSQLExample.scala" in the Spark repo.

    Data type(数据类型)Scala 中的 Value 类型访问或创建数据类型的 API

    ByteTypeByteByteType

    ShortTypeShortShortType

    IntegerTypeIntIntegerType

    LongTypeLongLongType

    FloatTypeFloatFloatType

    DoubleTypeDoubleDoubleType

    DecimalTypejava.math.BigDecimalDecimalType

    StringTypeStringStringType

    BinaryTypeArray[Byte]BinaryType

    BooleanTypeBooleanBooleanType

    TimestampTypejava.sql.TimestampTimestampType

    DateTypejava.sql.DateDateType

    ArrayTypescala.collection.SeqArrayType(elementType, [containsNull])

    Note(注意):containsNull的默认值是true.

    MapTypescala.collection.MapMapType(keyType,valueType, [valueContainsNull])

    Note(注意):valueContainsNull的默认值是true.

    StructTypeorg.apache.spark.sql.RowStructType(fields)

    Note(注意):fields是 StructFields 的 Seq. 所有, 两个 fields 拥有相同的名称是不被允许的.

    StructField该 field(字段)数据类型的 Scala 中的 value 类型 (例如, 数据类型为 IntegerType 的 StructField 是 Int)StructField(name,dataType, [nullable])

    Note:nullable的默认值是true.

    NaN Semantics

    当处理一些不符合标准浮点数语义的float或double类型时,对于 Not-a-Number(NaN) 需要做一些特殊处理. 具体如下:

    NaN = NaN 返回 true.

    aggregations(聚合)操作中,所有的 NaN values 将被分到同一个组中.

    join key 中 NaN 可以当做一个普通的值.

    NaN 值在升序排序中排到最后,比任何其他数值都大.

    我们一直在努力

    apachecn/spark-doc-zh

     

     

    原文地址: http://spark.apachecn.org/docs/cn/2.2.0/sql-programming-guide.html

    网页地址: http://spark.apachecn.org/

    github: https://github.com/apachecn/spark-doc-zh(觉得不错麻烦给个 Star,谢谢!~)

    Apache Spark 2.2.0 中文文档 - Spark Streaming 编程指南 | ApacheCN

    Joyyx 关注

    2017.09.27 10:44* 字数 18441 阅读 254评论 0喜欢 5

    Spark Streaming 编程指南

    概述

    一个入门示例

    基础概念

    依赖

    初始化 StreamingContext

    Discretized Streams (DStreams)(离散化流)

    Input DStreams 和 Receivers(接收器)

    DStreams 上的 Transformations(转换)

    DStreams 上的输出操作

    DataFrame 和 SQL 操作

    MLlib 操作

    缓存 / 持久性

    Checkpointing

    Accumulators, Broadcast 变量, 和 Checkpoint

    应用程序部署

    Monitoring Applications (监控应用程序)

    Performance Tuning (性能调优)

    Reducing the Batch Processing Times (减少批处理时间)

    Setting the Right Batch Interval (设置正确的批次间隔)

    Memory Tuning (内存调优)

    Fault-tolerance Semantics (容错语义)

    快速链接

    概述

    Spark Streaming 是 Spark Core API 的扩展, 它支持弹性的, 高吞吐的, 容错的实时数据流的处理. 数据可以通过多种数据源获取, 例如 Kafka, Flume, Kinesis 以及 TCP sockets, 也可以通过例如map,reduce,join,window等的高级函数组成的复杂算法处理. 最终, 处理后的数据可以输出到文件系统, 数据库以及实时仪表盘中. 事实上, 你还可以在 data streams(数据流)上使用机器学习以及图形处理算法.

     

    在内部, 它工作原理如下, Spark Streaming 接收实时输入数据流并将数据切分成多个 batch(批)数据, 然后由 Spark 引擎处理它们以生成最终的 stream of results in batches(分批流结果).

     

    Spark Streaming 提供了一个名为discretized stream或DStream的高级抽象, 它代表一个连续的数据流. DStream 可以从数据源的输入数据流创建, 例如 Kafka, Flume 以及 Kinesis, 或者在其他 DStream 上进行高层次的操作以创建. 在内部, 一个 DStream 是通过一系列的RDDs来表示.

    本指南告诉你如何使用 DStream 来编写一个 Spark Streaming 程序. 你可以使用 Scala , Java 或者 Python(Spark 1.2 版本后引进)来编写 Spark Streaming 程序. 所有这些都在本指南中介绍. 您可以在本指南中找到标签, 让您可以选择不同语言的代码段.

    Note(注意):在 Python 有些 API 可能会有不同或不可用. 在本指南, 您将找到Python API的标签来高亮显示不同的地方.

    一个入门示例

    在我们详细介绍如何编写你自己的 Spark Streaming 程序的细节之前, 让我们先来看一看一个简单的 Spark Streaming 程序的样子. 比方说, 我们想要计算从一个监听 TCP socket 的数据服务器接收到的文本数据(text data)中的字数. 你需要做的就是照着下面的步骤做.

    Scala

    Java

    Python

    首先, 我们导入了 Spark Streaming 类和部分从 StreamingContext 隐式转换到我们的环境的名称, 目的是添加有用的方法到我们需要的其他类(如 DStream).StreamingContext是所有流功能的主要入口点. 我们创建了一个带有 2 个执行线程和间歇时间为 1 秒的本地 StreamingContext.

    importorg.apache.spark._importorg.apache.spark.streaming._importorg.apache.spark.streaming.StreamingContext._// 自从 Spark 1.3 开始, 不再是必要的了// 创建一个具有两个工作线程(working thread)并且批次间隔为 1 秒的本地 StreamingContext .// master 需要 2 个核, 以防止饥饿情况(starvation scenario).valconf=newSparkConf().setMaster("local[2]").setAppName("NetworkWordCount")valssc=newStreamingContext(conf,Seconds(1))

    Using this context, we can create a DStream that represents streaming data from a TCP source, specified as hostname (e.g.localhost) and port (e.g.9999). 使用该 context, 我们可以创建一个代表从 TCP 源流数据的离散流(DStream), 指定主机名(hostname)(例如 localhost)和端口(例如 9999).

    // 创建一个将要连接到 hostname:port 的 DStream,如 localhost:9999vallines=ssc.socketTextStream("localhost",9999)

    上一步的这个linesDStream 表示将要从数据服务器接收到的数据流. 在这个离散流(DStream)中的每一条记录都是一行文本(text). 接下来,我们想要通过空格字符(space characters)拆分这些数据行(lines)成单词(words).

    // 将每一行拆分成 words(单词)valwords=lines.flatMap(_.split(" "))

    flatMap是一种 one-to-many(一对多)的离散流(DStream)操作,它会通过在源离散流(source DStream)中根据每个记录(record)生成多个新纪录的形式创建一个新的离散流(DStream). 在这种情况下,在这种情况下,每一行(each line)都将被拆分成多个单词(words)和代表单词离散流(words DStream)的单词流. 接下来,我们想要计算这些单词.

    importorg.apache.spark.streaming.StreamingContext._// not necessary since Spark 1.3// 计算每一个 batch(批次)中的每一个 word(单词)valpairs=words.map(word=>(word,1))valwordCounts=pairs.reduceByKey(_+_)// 在控制台打印出在这个离散流(DStream)中生成的每个 RDD 的前十个元素// 注意: 必需要触发 action(很多初学者会忘记触发 action 操作,导致报错:No output operations registered, so nothing to execute)wordCounts.print()

    上一步的wordsDStream 进行了进一步的映射(一对一的转换)为一个 (word, 1) paris 的离散流(DStream),这个 DStream 然后被规约(reduce)来获得数据中每个批次(batch)的单词频率. 最后,wordCounts.print()将会打印一些每秒生成的计数.

    Note that when these lines are executed, Spark Streaming only sets up the computation it will perform when it is started, and no real processing has started yet. To start the processing after all the transformations have been setup, we finally call

    请注意,当这些行(lines)被执行的时候, Spark Streaming 仅仅设置了计算, 只有在启动时才会执行,并没有开始真正地处理. 为了在所有的转换都已经设置好之后开始处理,我们在最后调用:

    ssc.start()// 开始计算ssc.awaitTermination()// 等待计算被中断

    该部分完整的代码可以在 Spark Streaming 示例NetworkWordCount中找到.

    如果你已经下载并且构建Spark, 您可以使用如下方式来运行该示例. 你首先需要运行 Netcat(一个在大多数类 Unix 系统中的小工具)作为我们使用的数据服务器.

    $ nc -lk9999

    然后,在另一个不同的终端,你可以通过执行如下命令来运行该示例:

    Scala

    Java

    Python

    $ ./bin/run-example streaming.NetworkWordCount localhost9999

    然后,在运行在 netcat 服务器上的终端输入的任何行(lines),都将被计算,并且每一秒都显示在屏幕上,它看起来就像下面这样:

    # TERMINAL 1:# Running Netcat$ nc -lk9999hello world...

    Scala

    Java

    Python

    # TERMINAL 2: RUNNING NetworkWordCount$ ./bin/run-example streaming.NetworkWordCount localhost9999...-------------------------------------------Time:1357008430000ms-------------------------------------------(hello,1)(world,1)...

    基础概念

    接下来,我们了解完了简单的例子,开始阐述 Spark Streaming 的基本知识。

    依赖

    Spark 类似,Spark Streaming 可以通过 Maven 来管理依赖. 为了编写你自己的 Spark Streaming 程序,你必须添加以下的依赖到你的 SBT 或者 Maven 项目中.

    Maven

    SBT

    org.apache.spark

    spark-streaming_2.11

    2.2.0

    针对从 Spark Streaming Core API 中不存在的数据源中获取数据,如 Kafka, Flume,Kinesis ,你必须添加相应的坐标spark-streaming-xyz_2.11到依赖中. 例如,有一些常见的依赖如下.

    Source(数据源)Artifact(坐标)

    Kafkaspark-streaming-kafka-0-8_2.11

    Flumespark-streaming-flume_2.11

    Kinesis

    spark-streaming-kinesis-asl_2.11 [Amazon Software License]

    想要查看一个实时更新的列表,请参阅Maven repository来了解支持的 sources(数据源)和 artifacts(坐标)的完整列表。

    初始化 StreamingContext

    为了初始化一个 Spark Streaming 程序, 一个StreamingContext对象必须要被创建出来,它是所有的 Spark Streaming 功能的主入口点。

    Scala

    Java

    Python

    一个StreamingContext对象可以从一个SparkConf对象中来创建.

    importorg.apache.spark._importorg.apache.spark.streaming._valconf=newSparkConf().setAppName(appName).setMaster(master)valssc=newStreamingContext(conf,Seconds(1))

    这个appName参数是展示在集群 UI 界面上的应用程序的名称.master是一个Spark, Mesos or YARN cluster URL, 或者一个特殊的“local[*]”字符串以使用 local mode(本地模式)来运行. 在实践中,当在集群上运行时,你不会想在应用程序中硬编码master,而是使用spark-submit来启动应用程序, 并且接受该参数. 然而,对于本地测试和单元测试,你可以传递 “local[*]” 来运行 Spark Streaming 进程(检测本地系统中内核的个数). 请注意,做个内部创建了一个SparkContext(所有 Spark 功能的出发点),它可以像 ssc.sparkContext 这样被访问.

    这个 batch interval(批间隔)必须根据您的应用程序和可用的集群资源的等待时间要求进行设置. 更多详情请参阅优化指南部分.

    一个StreamingContext对象也可以从一个现有的SparkContext对象来创建.

    importorg.apache.spark.streaming._valsc=...// 已存在的 SparkContextvalssc=newStreamingContext(sc,Seconds(1))

    在定义一个 context 之后,您必须执行以下操作.

    通过创建输入 DStreams 来定义输入源.

    通过应用转换和输出操作 DStreams 定义流计算(streaming computations).

    开始接收输入并且使用streamingContext.start()来处理数据.

    使用streamingContext.awaitTermination()等待处理被终止(手动或者由于任何错误).

    使用streamingContext.stop()来手动的停止处理.

    需要记住的几点:

    一旦一个 context 已经启动,将不会有新的数据流的计算可以被创建或者添加到它。.

    一旦一个 context 已经停止,它不会被重新启动.

    同一时间内在 JVM 中只有一个 StreamingContext 可以被激活.

    StreamingContext 上的 stop() 同样也停止了 SparkContext 。为了只停止 StreamingContext ,设置stop()的可选参数,名叫stopSparkContext为 false.

    一个 SparkContext 就可以被重用以创建多个 StreamingContexts,只要前一个 StreamingContext 在下一个StreamingContext 被创建之前停止(不停止 SparkContext).

    Discretized Streams (DStreams)(离散化流)

    Discretized StreamorDStream是 Spark Streaming 提供的基本抽象. 它代表了一个连续的数据流, 无论是从 source(数据源)接收到的输入数据流, 还是通过转换输入流所产生的处理过的数据流. 在内部, 一个 DStream 被表示为一系列连续的 RDDs, 它是 Spark 中一个不可改变的抽象, distributed dataset (的更多细节请看Spark 编程指南. 在一个 DStream 中的每个 RDD 包含来自一定的时间间隔的数据,如下图所示.

     

    应用于 DStream 的任何操作转化为对于底层的 RDDs 的操作. 例如,在先前的示例,转换一个行(lines)流成为单词(words)中,flatMap 操作被应用于在行离散流(lines DStream)中的每个 RDD 来生成单词离散流(words DStream)的 RDDs . 如下所示.

     

    这些底层的 RDD 变换由 Spark 引擎(engine)计算。 DStream 操作隐藏了大多数这些细节并为了方便起见,提供给了开发者一个更高级别的 API 。这些操作细节会在后边的章节中讨论。

    Input DStreams 和 Receivers(接收器)

    输入 DStreams 是代表输入数据是从流的源数据(streaming sources)接收到的流的 DStream. 在一个入门示例,lines是一个 input DStream, 因为它代表着从 netcat 服务器接收到的数据的流. 每一个 input DStream(除了 file stream 之外, 会在本章的后面来讨论)与一个Receiver(Scala doc,Java doc) 对象关联, 它从 source(数据源)中获取数据,并且存储它到 Sparl 的内存中用于处理.

    Spark Streaming 提供了两种内置的 streaming source(流的数据源).

    Basic sources(基础的数据源): 在 StreamingContext API 中直接可以使用的数据源. 例如: file systems 和 socket connections.

    Advanced sources(高级的数据源): 像 Kafka, Flume, Kinesis, 等等这样的数据源. 可以通过额外的 utility classes 来使用. 像在依赖中讨论的一样, 这些都需要额外的外部依赖.

    在本节的后边,我们将讨论每种类别中的现有的一些数据源.

    请注意, 如果你想要在你的流处理程序中并行的接收多个数据流, 你可以创建多个 input DStreams(在性能优化部分进一步讨论). 这将创建同时接收多个数据流的多个 receivers(接收器). 但需要注意,一个 Spark 的 worker/executor 是一个长期运行的任务(task),因此它将占用分配给 Spark Streaming 的应用程序的所有核中的一个核(core). 因此,要记住,一个 Spark Streaming 应用需要分配足够的核(core)(或线程(threads),如果本地运行的话)来处理所接收的数据,以及来运行接收器(receiver(s)).

    要记住的几点

    当在本地运行一个 Spark Streaming 程序的时候,不要使用 “local” 或者 “local[1]” 作为 master 的 URL. 这两种方法中的任何一个都意味着只有一个线程将用于运行本地任务. 如果你正在使用一个基于接收器(receiver)的输入离散流(input DStream)(例如, sockets ,Kafka ,Flume 等),则该单独的线程将用于运行接收器(receiver),而没有留下任何的线程用于处理接收到的数据. 因此,在本地运行时,总是用 “local[n]” 作为 master URL ,其中的 n > 运行接收器的数量(查看Spark 属性来了解怎样去设置 master 的信息).

    将逻辑扩展到集群上去运行,分配给 Spark Streaming 应用程序的内核(core)的内核数必须大于接收器(receiver)的数量。否则系统将接收数据,但是无法处理它.

    基础的 Sources(数据源)

    我们已经简单地了解过了在入门示例ssc.socketTextStream(...)的例子,例子中是通过从一个 TCP socket 连接接收到的文本数据来创建了一个离散流(DStream). 除了 sockets 之外,StreamingContext API 也提供了根据文件作为输入来源创建离散流(DStreams)的方法。

    File Streams:用于从文件中读取数据,在任何与 HDFS API 兼容的文件系统中(即,HDFS,S3,NFS 等),一个 DStream 可以像下面这样创建:

    Scala

    Java

    Python

    streamingContext.fileStream[KeyClass, ValueClass, InputFormatClass](dataDirectory)

    Spark Streaming 将监控dataDirectory目录并且该目录中任何新建的文件 (写在嵌套目录中的文件是不支持的). 注意

    文件必须具有相同的数据格式.

    文件必须被创建在dataDirectory目录中, 通过 atomically(院子的)moving(移动)或renaming(重命名)它们到数据目录.

    一旦移动,这些文件必须不能再更改,因此如果文件被连续地追加,新的数据将不会被读取.

    对于简单的文本文件,还有一个更加简单的方法streamingContext.textFileStream(dataDirectory). 并且文件流(file streams)不需要运行一个接收器(receiver),因此,不需要分配内核(core)。

    Python API在 Python API 中fileStream是不可用的, 只有textFileStream是可用的.

    Streams based on Custom Receivers(基于自定义的接收器的流):DStreams 可以使用通过自定义的 receiver(接收器)接收到的数据来创建. 更多细节请参阅自定义 Receiver 指南.

    Queue of RDDs as a Stream(RDDs 队列作为一个流):为了使用测试数据测试 Spark Streaming 应用程序,还可以使用streamingContext.queueStream(queueOfRDDs)创建一个基于 RDDs 队列的 DStream,每个进入队列的 RDD 都将被视为 DStream 中的一个批次数据,并且就像一个流进行处理.

    想要了解更多的关于从 sockets 和文件(files)创建的流的细节, 请参阅相关函数的 API文档, 它们在StreamingContextfor Scala,JavaStreamingContextfor Java 以及StreamingContextfor Python 中.

    高级 Sources(数据源)

    Python API从 Spark 2.2.0 开始, 在 Python API 中的 Kafka, Kinesis 和 Flume 这样的外部数据源都是可用的.

    这一类别的 sources(数据源)需要使用非 Spark 库中的外部接口,它们中的其中一些还需要比较复杂的依赖关系(例如, Kafka 和 Flume). 因此,为了最小化有关的依赖关系的版本冲突的问题,这些资源本身不能创建 DStream 的功能,它是通过依赖单独的类库实现创建 DStream 的功能.

    请注意, 这些高级 sources(数据源)不能再 Spark shell 中使用, 因此,基于这些高级 sources(数据源)的应用程序不能在 shell 中被测试. 如果你真的想要在 Spark shell 中使用它们,你必须下载带有它的依赖的相应的 Maven 组件的 JAR ,并且将其添加到 classpath.

    一些高级的 sources(数据源)如下.

    Kafka:Spark Streaming 2.2.0 与 Kafka broker 版本 0.8.2.1 或更高是兼容的. 更多细节请参阅Kafka 集成指南.

    Flume:Spark Streaming 2.2.0 与 Flume 1.6.0 相兼容. 更多细节请参阅Flume 集成指南.

    Kinesis:Spark Streaming 2.2.0 与 Kinesis Client Library 1.2.1 相兼容. 更多细节请参阅Kinesis 集成指南.

    自定义 Sources(数据源)

    Python API在 Python 中还不支持这一功能.

    Input DStreams 也可以从自定义数据源中创建. 如果您想这样做, 需要实现一个用户自定义的receiver(看下一节以了解它是什么), 它可以从自定义的 sources(数据源)中接收数据并且推送它到 Spark. 更多细节请参阅自定义 Receiver 指南.

    Receiver Reliability(接收器的可靠性)

    可以有两种基于他们的reliability可靠性的数据源. 数据源(如 Kafka 和 Flume)允许传输的数据被确认. 如果系统从这些可靠的数据来源接收数据,并且被确认(acknowledges)正确地接收数据,它可以确保数据不会因为任何类型的失败而导致数据丢失. 这样就出现了 2 种接收器(receivers):

    Reliable Receiver(可靠的接收器)- 当数据被接收并存储在 Spark 中并带有备份副本时,一个可靠的接收器(reliable receiver)正确地发送确认(acknowledgment)给一个可靠的数据源(reliable source).

    Unreliable Receiver(不可靠的接收器)- 一个不可靠的接收器( unreliable receiver )不发送确认(acknowledgment)到数据源。这可以用于不支持确认的数据源,或者甚至是可靠的数据源当你不想或者不需要进行复杂的确认的时候.

    自定义 Receiver 指南中描述了关于如何去编写一个 reliable receiver(可靠的接收器)的细节.

    DStreams 上的 Transformations(转换)

    RDD 类似,transformation 允许从 input DStream 输入的数据做修改. DStreams 支持很多在 RDD 中可用的 transformation 算子。一些常用的如下所示 :

    RDD类似,类似,transformation 允许修改来自 input DStream 的数据. DStreams 支持标准的 Spark RDD 上可用的许多转换. 一些常见的如下.

    Transformation(转换)Meaning(含义)

    map(func)利用函数func处理原 DStream 的每个元素,返回一个新的 DStream.

    flatMap(func)与 map 相似,但是每个输入项可用被映射为 0 个或者多个输出项。.

    filter(func)返回一个新的 DStream,它仅仅包含原 DStream 中函数func返回值为 true 的项.

    repartition(numPartitions)通过创建更多或者更少的 partition 以改变这个 DStream 的并行级别(level of parallelism).

    union(otherStream)返回一个新的 DStream,它包含源 DStream 和otherDStream的所有元素.

    count()通过 count 源 DStream 中每个 RDD 的元素数量,返回一个包含单元素(single-element)RDDs 的新 DStream.

    reduce(func)利用函数func聚集源 DStream 中每个 RDD 的元素,返回一个包含单元素(single-element)RDDs 的新 DStream。函数应该是相关联的,以使计算可以并行化.

    countByValue()在元素类型为 K 的 DStream上,返回一个(K,long)pair 的新的 DStream,每个 key 的值是在原 DStream 的每个 RDD 中的次数.

    reduceByKey(func, [numTasks])当在一个由 (K,V) pairs 组成的 DStream 上调用这个算子时,返回一个新的, 由 (K,V) pairs 组成的 DStream,每一个 key 的值均由给定的 reduce 函数聚合起来.注意:在默认情况下,这个算子利用了 Spark 默认的并发任务数去分组。你可以用 numTasks 参数设置不同的任务数。

    join(otherStream, [numTasks])当应用于两个 DStream(一个包含(K,V)对,一个包含 (K,W) 对),返回一个包含 (K, (V, W)) 对的新 DStream.

    cogroup(otherStream, [numTasks])当应用于两个 DStream(一个包含(K,V)对,一个包含 (K,W) 对),返回一个包含 (K, Seq[V], Seq[W]) 的 tuples(元组).

    transform(func)通过对源 DStream 的每个 RDD 应用 RDD-to-RDD 函数,创建一个新的 DStream. 这个可以在 DStream 中的任何 RDD 操作中使用.

    updateStateByKey(func)返回一个新的 "状态" 的 DStream,其中每个 key 的状态通过在 key 的先前状态应用给定的函数和 key 的新 valyes 来更新. 这可以用于维护每个 key 的任意状态数据.

    其中一些转换值得深入讨论.

    UpdateStateByKey 操作

    updateStateByKey操作允许您维护任意状态,同时不断更新新信息. 你需要通过两步来使用它.

    定义 state - state 可以是任何的数据类型.

    定义 state update function(状态更新函数) - 使用函数指定如何使用先前状态来更新状态,并从输入流中指定新值.

    在每个 batch 中,Spark 会使用状态更新函数为所有已有的 key 更新状态,不管在 batch 中是否含有新的数据。如果这个更新函数返回一个 none,这个 key-value pair 也会被消除.

    让我们举个例子来说明. 在例子中,假设你想保持在文本数据流中看到的每个单词的运行计数,运行次数用一个 state 表示,它的类型是整数, 我们可以使用如下方式来定义 update 函数:

    Scala

    Java

    Python

    defupdateFunction(newValues:Seq[Int],runningCount:Option[Int]):Option[Int]={valnewCount=...// add the new values with the previous running count to get the new countSome(newCount)}

    这里是一个应用于包含 words(单词)的 DStream 上(也就是说,在先前的示例中,该pairsDStream 包含了 (word, 1) pair).

    valrunningCounts=pairs.updateStateByKey[Int](updateFunction_)

    update 函数将会被每个单词调用,newValues拥有一系列的 1(来自 (word, 1) pairs),runningCount 拥有之前的次数.

    请注意, 使用updateStateByKey需要配置的checkpoint(检查点)的目录,这里是更详细关于讨论checkpointing的部分.

    Transform Operation*(转换操作)

    transform 操作(以及它的变化形式如transformWith)允许在 DStream 运行任何 RDD-to-RDD 函数. 它能够被用来应用任何没在 DStream API 中提供的 RDD 操作. 例如,连接数据流中的每个批(batch)和另外一个数据集的功能并没有在 DStream API 中提供,然而你可以简单的利用transform方法做到. 这使得有非常强大的可能性. 例如,可以通过将输入数据流与预先计算的垃圾邮件信息(也可以使用 Spark 一起生成)进行实时数据清理,然后根据它进行过滤.

    Scala

    Java

    Python

    valspamInfoRDD=ssc.sparkContext.newAPIHadoopRDD(...)// RDD containing spam informationvalcleanedDStream=wordCounts.transform{rdd=>rdd.join(spamInfoRDD).filter(...)// join data stream with spam information to do data cleaning...}

    请注意,每个 batch interval(批间隔)提供的函数被调用. 这允许你做随时间变动的 RDD 操作, 即 RDD 操作, 分区的数量,广播变量,等等. batch 之间等可以改变。

    Window Operations(窗口操作)

    Spark Streaming 也支持windowed computations(窗口计算),它允许你在数据的一个滑动窗口上应用 transformation(转换). 下图说明了这个滑动窗口.

     

    如上图显示,窗口在源 DStream 上slides(滑动),合并和操作落入窗内的源 RDDs,产生窗口化的 DStream 的 RDDs。在这个具体的例子中,程序在三个时间单元的数据上进行窗口操作,并且每两个时间单元滑动一次。 这说明,任何一个窗口操作都需要指定两个参数.

    window length(窗口长度)- 窗口的持续时间(图 3).

    sliding interval(滑动间隔)- 执行窗口操作的间隔(图 2).

    这两个参数必须是 source DStream 的 batch interval(批间隔)的倍数(图 1).

    让我们举例以说明窗口操作. 例如,你想扩展前面的例子用来计算过去 30 秒的词频,间隔时间是 10 秒. 为了达到这个目的,我们必须在过去 30 秒的(wrod, 1)pairs 的pairsDStream 上应用reduceByKey操作. 用方法reduceByKeyAndWindow实现.

    Scala

    Java

    Python

    // Reduce last 30 seconds of data, every 10 secondsvalwindowedWordCounts=pairs.reduceByKeyAndWindow((a:Int,b:Int)=>(a+b),Seconds(30),Seconds(10))

    一些常用的窗口操作如下所示,这些操作都需要用到上文提到的两个参数 -windowLength(窗口长度)和slideInterval(滑动的时间间隔).

    Transformation(转换)Meaning(含义)

    window(windowLength,slideInterval)返回一个新的 DStream, 它是基于 source DStream 的窗口 batch 进行计算的.

    countByWindow(windowLength,slideInterval)返回 stream(流)中滑动窗口元素的数

    reduceByWindow(func,windowLength,slideInterval)返回一个新的单元素 stream(流),它通过在一个滑动间隔的 stream 中使用func来聚合以创建. 该函数应该是 associative(关联的)且 commutative(可交换的),以便它可以并行计算

    reduceByKeyAndWindow(func,windowLength,slideInterval, [numTasks])在一个 (K, V) pairs 的 DStream 上调用时, 返回一个新的 (K, V) pairs 的 Stream, 其中的每个 key 的 values 是在滑动窗口上的 batch 使用给定的函数func来聚合产生的.Note(注意):默认情况下, 该操作使用 Spark 的默认并行任务数量(local model 是 2, 在 cluster mode 中的数量通过spark.default.parallelism来确定)来做 grouping. 您可以通过一个可选的numTasks参数来设置一个不同的 tasks(任务)数量.

    reduceByKeyAndWindow(func,invFunc,windowLength,slideInterval, [numTasks])上述reduceByKeyAndWindow()的更有效的一个版本,其中使用前一窗口的 reduce 值逐渐计算每个窗口的 reduce值. 这是通过减少进入滑动窗口的新数据,以及 “inverse reducing(逆减)” 离开窗口的旧数据来完成的. 一个例子是当窗口滑动时”添加” 和 “减” keys 的数量. 然而,它仅适用于 “invertible reduce functions(可逆减少函数)”,即具有相应 “inverse reduce(反向减少)” 函数的 reduce 函数(作为参数invFunc ). 像在reduceByKeyAndWindow中的那样, reduce 任务的数量可以通过可选参数进行配置. 请注意, 针对该操作的使用必须启用checkpointing.

    countByValueAndWindow(windowLength,slideInterval, [numTasks])在一个 (K, V) pairs 的 DStream 上调用时, 返回一个新的 (K, Long) pairs 的 DStream, 其中每个 key 的 value 是它在一个滑动窗口之内的频次. 像 code>reduceByKeyAndWindow 中的那样, reduce 任务的数量可以通过可选参数进行配置.

    Join 操作

    最后,它值得强调的是,您可以轻松地在 Spark Streaming 中执行不同类型的 join.

    Stream-stream joins

    Streams(流)可以非常容易地与其他流进行 join.

    Scala

    Java

    Python

    valstream1:DStream[String,String]=...valstream2:DStream[String,String]=...valjoinedStream=stream1.join(stream2)

    这里,在每个 batch interval(批间隔)中,由stream1生成的 RDD 将与stream2生成的 RDD 进行 jion. 你也可以做leftOuterJoin,rightOuterJoin,fullOuterJoin. 此外,在 stream(流)的窗口上进行 join 通常是非常有用的. 这也很容易做到.

    Scala

    Java

    Python

    valwindowedStream1=stream1.window(Seconds(20))valwindowedStream2=stream2.window(Minutes(1))valjoinedStream=windowedStream1.join(windowedStream2)

    Stream-dataset joins

    这在解释DStream.transform操作时已经在前面演示过了. 这是另一个 join window stream(窗口流)与 dataset 的例子.

    Scala

    Java

    Python

    valdataset:RDD[String,String]=...valwindowedStream=stream.window(Seconds(20))...valjoinedStream=windowedStream.transform{rdd=>rdd.join(dataset)}

    实际上,您也可以动态更改要加入的 dataset. 提供给transform的函数是每个 batch interval(批次间隔)进行评估,因此将使用dataset引用指向当前的 dataset.

    DStream 转换的完整列表可在 API 文档中找到. 针对 Scala API,请看DStreamPairDStreamFunctions. 针对 Java API,请看JavaDStreamJavaPairDStream. 针对 Python API,请看DStream.

    DStreams 上的输出操作

    输出操作允许将 DStream 的数据推送到外部系统, 如数据库或文件系统. 由于输出操作实际上允许外部系统使用变换后的数据, 所以它们触发所有 DStream 变换的实际执行(类似于RDD的动作). 目前, 定义了以下输出操作:

    Output OperationMeaning

    print()在运行流应用程序的 driver 节点上的DStream中打印每批数据的前十个元素. 这对于开发和调试很有用.

    Python API这在 Python API 中称为pprint().

    saveAsTextFiles(prefix, [suffix])将此 DStream 的内容另存为文本文件. 每个批处理间隔的文件名是根据前缀和后缀:"prefix-TIME_IN_MS[.suffix]"生成的.

    saveAsObjectFiles(prefix, [suffix])将此 DStream 的内容另存为序列化 Java 对象的SequenceFiles. 每个批处理间隔的文件名是根据前缀和后缀:"prefix-TIME_IN_MS[.suffix]"生成的.

    Python API这在Python API中是不可用的.

    saveAsHadoopFiles(prefix, [suffix])将此 DStream 的内容另存为 Hadoop 文件. 每个批处理间隔的文件名是根据前缀和后缀:"prefix-TIME_IN_MS[.suffix]"生成的.

    Python API这在Python API中是不可用的.

    foreachRDD(func)对从流中生成的每个 RDD 应用函数func的最通用的输出运算符. 此功能应将每个 RDD 中的数据推送到外部系统, 例如将 RDD 保存到文件, 或将其通过网络写入数据库. 请注意, 函数func在运行流应用程序的 driver 进程中执行, 通常会在其中具有 RDD 动作, 这将强制流式传输 RDD 的计算.

    foreachRDD 设计模式的使用

    dstream.foreachRDD是一个强大的原语, 允许将数据发送到外部系统.但是, 了解如何正确有效地使用这个原语很重要. 避免一些常见的错误如下.

    通常向外部系统写入数据需要创建连接对象(例如与远程服务器的 TCP 连接), 并使用它将数据发送到远程系统.为此, 开发人员可能会无意中尝试在Spark driver 中创建连接对象, 然后尝试在Spark工作人员中使用它来在RDD中保存记录.例如(在 Scala 中):

    Scala

    Java

    Python

    dstream.foreachRDD{rdd=>valconnection=createNewConnection()// executed at the driverrdd.foreach{record=>connection.send(record)// executed at the worker}}

    这是不正确的, 因为这需要将连接对象序列化并从 driver 发送到 worker. 这种连接对象很少能跨机器转移. 此错误可能会显示为序列化错误(连接对象不可序列化), 初始化错误(连接对象需要在 worker 初始化)等. 正确的解决方案是在 worker 创建连接对象.

    但是, 这可能会导致另一个常见的错误 - 为每个记录创建一个新的连接. 例如:

    Scala

    Java

    Python

    dstream.foreachRDD{rdd=>rdd.foreach{record=>valconnection=createNewConnection()connection.send(record)connection.close()}}

    通常, 创建连接对象具有时间和资源开销. 因此, 创建和销毁每个记录的连接对象可能会引起不必要的高开销, 并可显着降低系统的总体吞吐量. 一个更好的解决方案是使用rdd.foreachPartition- 创建一个连接对象, 并使用该连接在 RDD 分区中发送所有记录.

    Scala

    Java

    Python

    dstream.foreachRDD{rdd=>rdd.foreachPartition{partitionOfRecords=>valconnection=createNewConnection()partitionOfRecords.foreach(record=>connection.send(record))connection.close()}}

    这样可以在多个记录上分摊连接创建开销.

    最后, 可以通过跨多个RDD /批次重用连接对象来进一步优化. 可以维护连接对象的静态池, 而不是将多个批次的 RDD 推送到外部系统时重新使用, 从而进一步减少开销.

    Scala

    Java

    Python

    dstream.foreachRDD{rdd=>rdd.foreachPartition{partitionOfRecords=>// ConnectionPool is a static, lazily initialized pool of connectionsvalconnection=ConnectionPool.getConnection()partitionOfRecords.foreach(record=>connection.send(record))ConnectionPool.returnConnection(connection)// return to the pool for future reuse}}

    请注意, 池中的连接应根据需要懒惰创建, 如果不使用一段时间, 则会超时. 这实现了最有效地将数据发送到外部系统.

    其他要记住的要点:

    DStreams 通过输出操作进行延迟执行, 就像 RDD 由 RDD 操作懒惰地执行. 具体来说, DStream 输出操作中的 RDD 动作强制处理接收到的数据.因此, 如果您的应用程序没有任何输出操作, 或者具有dstream.foreachRDD()等输出操作, 而在其中没有任何 RDD 操作, 则不会执行任何操作.系统将简单地接收数据并将其丢弃.

    默认情况下, 输出操作是 one-at-a-time 执行的. 它们按照它们在应用程序中定义的顺序执行.

    DataFrame 和 SQL 操作

    您可以轻松地在流数据上使用DataFrames and SQL SQL 操作. 您必须使用 StreamingContext 正在使用的 SparkContext 创建一个 SparkSession.此外, 必须这样做, 以便可以在 driver 故障时重新启动. 这是通过创建一个简单实例化的 SparkSession 单例实例来实现的.这在下面的示例中显示.它使用 DataFrames 和 SQL 来修改早期的字数示例以生成单词计数.将每个 RDD 转换为 DataFrame, 注册为临时表, 然后使用 SQL 进行查询.

    Scala

    Java

    Python

    /** DataFrame operations inside your streaming program */valwords:DStream[String]=...words.foreachRDD{rdd=>// Get the singleton instance of SparkSessionvalspark=SparkSession.builder.config(rdd.sparkContext.getConf).getOrCreate()importspark.implicits._// Convert RDD[String] to DataFramevalwordsDataFrame=rdd.toDF("word")// Create a temporary viewwordsDataFrame.createOrReplaceTempView("words")// Do word count on DataFrame using SQL and print itvalwordCountsDataFrame=spark.sql("select word, count(*) as total from words group by word")wordCountsDataFrame.show()}

    请参阅完整的源代码.

    您还可以对来自不同线程的流数据(即异步运行的 StreamingContext )上定义的表运行 SQL 查询. 只需确保您将 StreamingContext 设置为记住足够数量的流数据, 以便查询可以运行. 否则, 不知道任何异步 SQL 查询的 StreamingContext 将在查询完成之前删除旧的流数据. 例如, 如果要查询最后一个批次, 但是您的查询可能需要5分钟才能运行, 则可以调用streamingContext.remember(Minutes(5))(以 Scala 或其他语言的等价物).

    有关DataFrames的更多信息, 请参阅DataFrames 和 SQL 指南.

    MLlib 操作

    您还可以轻松使用MLlib提供的机器学习算法. 首先, 有 streaming 机器学习算法(例如:Streaming 线性回归,Streaming KMeans等), 其可以同时从 streaming 数据中学习, 并将该模型应用于 streaming 数据. 除此之外, 对于更大类的机器学习算法, 您可以离线学习一个学习模型(即使用历史数据), 然后将该模型在线应用于流数据.有关详细信息, 请参阅MLlib指南.

    缓存 / 持久性

    RDD 类似, DStreams 还允许开发人员将流的数据保留在内存中. 也就是说, 在 DStream 上使用persist()方法会自动将该 DStream 的每个 RDD 保留在内存中. 如果 DStream 中的数据将被多次计算(例如, 相同数据上的多个操作), 这将非常有用. 对于基于窗口的操作, 如reduceByWindow和reduceByKeyAndWindow以及基于状态的操作, 如updateStateByKey, 这是隐含的.因此, 基于窗口的操作生成的 DStream 会自动保存在内存中, 而不需要开发人员调用persist().

    对于通过网络接收数据(例如: Kafka, Flume, sockets 等)的输入流, 默认持久性级别被设置为将数据复制到两个节点进行容错.

    请注意, 与 RDD 不同, DStreams 的默认持久性级别将数据序列化在内存中. 这在性能调优部分进一步讨论. 有关不同持久性级别的更多信息, 请参见Spark编程指南.

    Checkpointing

    streaming 应用程序必须 24/7 运行, 因此必须对应用逻辑无关的故障(例如, 系统故障, JVM 崩溃等)具有弹性. 为了可以这样做, Spark Streaming 需要checkpoint足够的信息到容错存储系统, 以便可以从故障中恢复.checkpoint有两种类型的数据.

    Metadata checkpointing- 将定义 streaming 计算的信息保存到容错存储(如 HDFS)中.这用于从运行 streaming 应用程序的 driver 的节点的故障中恢复(稍后详细讨论). 元数据包括:

    Configuration- 用于创建流应用程序的配置.

    DStream operations- 定义 streaming 应用程序的 DStream 操作集.

    Incomplete batches- 批量的job 排队但尚未完成.

    Data checkpointing- 将生成的 RDD 保存到可靠的存储.这在一些将多个批次之间的数据进行组合的状态变换中是必需的.在这种转换中, 生成的 RDD 依赖于先前批次的 RDD, 这导致依赖链的长度随时间而增加.为了避免恢复时间的这种无限增加(与依赖关系链成比例), 有状态转换的中间 RDD 会定期checkpoint到可靠的存储(例如 HDFS)以切断依赖关系链.

    总而言之, 元数据 checkpoint 主要用于从 driver 故障中恢复, 而数据或 RDD checkpoint 对于基本功能(如果使用有状态转换)则是必需的.

    何时启用 checkpoint

    对于具有以下任一要求的应用程序, 必须启用 checkpoint:

    使用状态转换- 如果在应用程序中使用updateStateByKey或reduceByKeyAndWindow(具有反向功能), 则必须提供 checkpoint 目录以允许定期的 RDD checkpoint.

    从运行应用程序的 driver 的故障中恢复- 元数据 checkpoint 用于使用进度信息进行恢复.

    请注意, 无需进行上述有状态转换的简单 streaming 应用程序即可运行, 无需启用 checkpoint. 在这种情况下, 驱动器故障的恢复也将是部分的(一些接收但未处理的数据可能会丢失). 这通常是可以接受的, 许多运行 Spark Streaming 应用程序. 未来对非 Hadoop 环境的支持预计会有所改善.

    如何配置 checkpoint

    可以通过在保存 checkpoint 信息的容错, 可靠的文件系统(例如, HDFS, S3等)中设置目录来启用 checkpoint. 这是通过使用streamingContext.checkpoint(checkpointDirectory)完成的. 这将允许您使用上述有状态转换. 另外, 如果要使应用程序从 driver 故障中恢复, 您应该重写 streaming 应用程序以具有以下行为.

    当程序第一次启动时, 它将创建一个新的 StreamingContext, 设置所有流, 然后调用 start().

    当程序在失败后重新启动时, 它将从 checkpoint 目录中的 checkpoint 数据重新创建一个 StreamingContext.

    Scala

    Java

    Python

    使用StreamingContext.getOrCreate可以简化此行为. 这样使用如下.

    // Function to create and setup a new StreamingContextdeffunctionToCreateContext():StreamingContext={valssc=newStreamingContext(...)// new contextvallines=ssc.socketTextStream(...)// create DStreams...ssc.checkpoint(checkpointDirectory)// set checkpoint directoryssc}// Get StreamingContext from checkpoint data or create a new onevalcontext=StreamingContext.getOrCreate(checkpointDirectory,functionToCreateContext_)// Do additional setup on context that needs to be done,// irrespective of whether it is being started or restartedcontext....// Start the contextcontext.start()context.awaitTermination()

    If thecheckpointDirectoryexists, then the context will be recreated from the checkpoint data. If the directory does not exist (i.e., running for the first time), then the functionfunctionToCreateContextwill be called to create a new context and set up the DStreams. See the Scala exampleRecoverableNetworkWordCount. This example appends the word counts of network data into a file.

    除了使用getOrCreate之外, 还需要确保在失败时自动重新启动 driver 进程. 这只能由用于运行应用程序的部署基础架构完成. 这在部署部分进一步讨论.

    请注意, RDD 的 checkpoint 会导致保存到可靠存储的成本. 这可能会导致 RDD 得到 checkpoint 的批次的处理时间增加. 因此, 需要仔细设置 checkpoint 的间隔. 在小批量大小(例如: 1秒), 检查每个批次可能会显着降低操作吞吐量. 相反, checkpoint 太少会导致谱系和任务大小增长, 这可能会产生不利影响. 对于需要 RDD checkpoint 的状态转换, 默认间隔是至少10秒的批间隔的倍数. 它可以通过使用dstream.checkpoint(checkpointInterval)进行设置. 通常, DStream 的5到10个滑动间隔的 checkpoint 间隔是一个很好的设置.

    Accumulators, Broadcast 变量, 和 Checkpoint

    Spark Streaming中, 无法从 checkpoint 恢复AccumulatorsBroadcast 变量. 如果启用 checkpoint 并使用AccumulatorsBroadcast 变量, 则必须为AccumulatorsBroadcast 变量创建延迟实例化的单例实例, 以便在 driver 重新启动失败后重新实例化. 这在下面的示例中显示:

    Scala

    Java

    Python

    objectWordBlacklist{@volatileprivatevarinstance:Broadcast[Seq[String]]=nulldefgetInstance(sc:SparkContext):Broadcast[Seq[String]]={if(instance==null){synchronized{if(instance==null){valwordBlacklist=Seq("a","b","c")instance=sc.broadcast(wordBlacklist)}}}instance}}objectDroppedWordsCounter{@volatileprivatevarinstance:LongAccumulator=nulldefgetInstance(sc:SparkContext):LongAccumulator={if(instance==null){synchronized{if(instance==null){instance=sc.longAccumulator("WordsInBlacklistCounter")}}}instance}}wordCounts.foreachRDD{(rdd:RDD[(String,Int)],time:Time)=>// Get or register the blacklist Broadcastvalblacklist=WordBlacklist.getInstance(rdd.sparkContext)// Get or register the droppedWordsCounter AccumulatorvaldroppedWordsCounter=DroppedWordsCounter.getInstance(rdd.sparkContext)// Use blacklist to drop words and use droppedWordsCounter to count themvalcounts=rdd.filter{case(word,count)=>if(blacklist.value.contains(word)){droppedWordsCounter.add(count)false}else{true}}.collect().mkString("[",", ","]")valoutput="Counts at time "+time+" "+counts})

    请参阅完整的源代码.

    应用程序部署

    本节讨论部署 Spark Streaming 应用程序的步骤.

    要求

    要运行 Spark Streaming 应用程序, 您需要具备以下功能.

    集群管理器集群- 这是任何 Spark 应用程序的一般要求, 并在部署指南中详细讨论.

    打包应用程序 JAR- 您必须将 streaming 应用程序编译为 JAR. 如果您正在使用spark-submit启动应用程序, 则不需要在 JAR 中提供 Spark 和 Spark Streaming.但是, 如果您的应用程序使用高级资源(例如: Kafka, Flume), 那么您将必须将他们链接的额外工件及其依赖项打包在用于部署应用程序的 JAR 中.例如, 使用KafkaUtils的应用程序必须在应用程序 JAR 中包含spark-streaming-kafka-0-8_2.11及其所有传递依赖关系.

    executor 配置足够的内存- 由于接收到的数据必须存储在内存中, 所以 executor 必须配置足够的内存来保存接收到的数据. 请注意, 如果您正在进行10分钟的窗口操作, 系统必须至少保留最近10分钟的内存中的数据. 因此, 应用程序的内存要求取决于其中使用的操作.

    配置 checkpoint- 如果 streaming 应用程序需要它, 则 Hadoop API 兼容容错存储(例如:HDFS, S3等)中的目录必须配置为 checkpoint 目录, 并且流程应用程序以 checkpoint 信息的方式编写 用于故障恢复. 有关详细信息, 请参阅checkpoint部分.

    配置应用程序 driver 的自动重新启动- 要从 driver 故障自动恢复, 用于运行流应用程序的部署基础架构必须监视 driver 进程, 并在 driver 发生故障时重新启动 driver.不同的集群管理者有不同的工具来实现这一点.

    Spark Standalone- 可以提交 Spark 应用程序 driver 以在Spark Standalone集群中运行(请参阅集群部署模式, 即应用程序 driver 本身在其中一个工作节点上运行. 此外, 可以指示独立的群集管理器来监督 driver, 如果由于非零退出代码而导致 driver 发生故障, 或由于运行 driver 的节点发生故障, 则可以重新启动它. 有关详细信息, 请参阅 [Spark Standalone 指南]](spark-standalone.html) 中的群集模式和监督.

    YARN- Yarn 支持类似的机制来自动重新启动应用程序.有关详细信息, 请参阅 YARN文档.

    Mesos-Marathon已被用来实现这一点与Mesos.

    配置预写日志- 自 Spark 1.2 以来, 我们引入了写入日志来实现强大的容错保证.如果启用, 则从 receiver 接收的所有数据都将写入配置 checkpoint 目录中的写入日志.这可以防止 driver 恢复时的数据丢失, 从而确保零数据丢失(在容错语义部分中详细讨论).可以通过将配置参数spark.streaming.receiver.writeAheadLog.enable设置为true来启用此功能.然而, 这些更强的语义可能以单个 receiver 的接收吞吐量为代价.通过并行运行更多的 receiver可以纠正这一点, 以增加总吞吐量.另外, 建议在启用写入日志时, 在日志已经存储在复制的存储系统中时, 禁用在 Spark 中接收到的数据的复制.这可以通过将输入流的存储级别设置为StorageLevel.MEMORY_AND_DISK_SER来完成.使用 S3(或任何不支持刷新的文件系统)写入日志时, 请记住启用spark.streaming.driver.writeAheadLog.closeFileAfterWrite和spark.streaming.receiver.writeAheadLog.closeFileAfterWrite.有关详细信息, 请参阅Spark Streaming配.请注意, 启用 I/O 加密时, Spark 不会将写入写入日志的数据加密.如果需要对提前记录数据进行加密, 则应将其存储在本地支持加密的文件系统中.

    设置最大接收速率- 如果集群资源不够大, streaming 应用程序能够像接收到的那样快速处理数据, 则可以通过设置 记录/秒 的最大速率限制来对 receiver 进行速率限制. 请参阅 receiver 的spark.streaming.receiver.maxRate和用于 Direct Kafka 方法的spark.streaming.kafka.maxRatePerPartition的配置参数. 在Spark 1.5中, 我们引入了一个称为背压的功能, 无需设置此速率限制, 因为Spark Streaming会自动计算速率限制, 并在处理条件发生变化时动态调整速率限制. 可以通过将配置参数spark.streaming.backpressure.enabled设置为true来启用此 backpressure.

    升级应用程序代码

    如果运行的 Spark Streaming 应用程序需要使用新的应用程序代码进行升级, 则有两种可能的机制.

    升级后的 Spark Streaming 应用程序与现有应用程序并行启动并运行.一旦新的(接收与旧的数据相同的数据)已经升温并准备好黄金时段, 旧的可以被关掉.请注意, 这可以用于支持将数据发送到两个目的地(即较早和已升级的应用程序)的数据源.

    现有应用程序正常关闭(请参阅StreamingContext.stop(...)JavaStreamingContext.stop(...)以获取正常的关闭选项), 以确保已关闭的数据在关闭之前被完全处理.然后可以启动升级的应用程序, 这将从较早的应用程序停止的同一点开始处理.请注意, 只有在支持源端缓冲的输入源(如: Kafka 和 Flume)时才可以进行此操作, 因为数据需要在先前的应用程序关闭并且升级的应用程序尚未启动时进行缓冲.从升级前代码的早期 checkpoint 信息重新启动不能完成.checkpoint 信息基本上包含序列化的 Scala/Java/Python 对象, 并尝试使用新的修改的类反序列化对象可能会导致错误.在这种情况下, 可以使用不同的 checkpoint 目录启动升级的应用程序, 也可以删除以前的 checkpoint 目录.

    Monitoring Applications (监控应用程序)

    除了 Spark 的monitoring capabilities(监控功能), 还有其他功能特定于 Spark Streaming .当使用 StreamingContext 时,Spark web UI显示一个额外的Streaming选项卡, 显示 running receivers (运行接收器)的统计信息(无论是 receivers (接收器)是否处于 active (活动状态), 接收到的 records (记录)数, receiver error (接收器错误)等)并完成 batches (批次)(batch processing times (批处理时间), queueing delays (排队延迟)等).这可以用来监视 streaming application (流应用程序)的进度.

    web UI 中的以下两个 metrics (指标)特别重要:

    Processing Time (处理时间)- 处理每 batch (批)数据的时间 .

    Scheduling Delay (调度延迟)- batch (批处理)在 queue (队列)中等待处理 previous batches (以前批次)完成的时间.

    如果 batch processing time (批处理时间)始终 more than (超过) batch interval (批间隔) and/or queueing delay (排队延迟)不断增加, 表示系统是无法快速 process the batches (处理批次), 并且正在 falling behind (落后). 在这种情况下, 请考虑reducing (减少)batch processing time (批处理时间).

    Spark Streaming 程序的进展也可以使用StreamingListener接口, 这允许您获得 receiver status (接收器状态)和 processing times (处理时间).请注意, 这是一个开发人员 API 并且将来可能会改善(即, 更多的信息报告).

    Performance Tuning (性能调优)

    在集群上的 Spark Streaming application 中获得最佳性能需要一些调整.本节介绍了可调整的多个 parameters (参数)和 configurations (配置)提高你的应用程序性能.在高层次上, 你需要考虑两件事情:

    通过有效利用集群资源, Reducing the processing time of each batch of data (减少每批数据的处理时间).

    设置正确的 batch size (批量大小), 以便 batches of data (批量的数据)可以像 received (被接收)处理一样快(即 data processing (数据处理)与 data ingestion (数据摄取)保持一致).

    Reducing the Batch Processing Times (减少批处理时间)

    Spark 中可以进行一些优化, 以 minimize the processing time of each batch (最小化每批处理时间).这些已在Tuning Guide (调优指南)中详细讨论过.本节突出了一些最重要的.

    Level of Parallelism in Data Receiving (数据接收中的并行级别)

    通过网络接收数据(如Kafka, Flume, socket 等)需要 deserialized (反序列化)数据并存储在 Spark 中.如果数据接收成为系统的瓶颈, 那么考虑一下 parallelizing the data receiving (并行化数据接收).注意每个 input DStream 创建接收 single stream of data (单个数据流)的 single receiver (单个接收器)(在 work machine 上运行). 因此, 可以通过创建多个 input DStreams 来实现 Receiving multiple data streams (接收多个数据流)并配置它们以从 source(s) 接收 data stream (数据流)的 different partitions (不同分区).例如, 接收 two topics of data (两个数据主题)的单个Kafka input DStream 可以分为两个 Kafka input streams (输入流), 每个只接收一个 topic (主题).这将运行两个 receivers (接收器), 允许 in parallel (并行)接收数据, 从而提高 overall throughput (总体吞吐量).这些 multiple DStreams 可以 unioned (联合起来)创建一个 single DStream .然后 transformations (转化)为应用于 single input DStream 可以应用于 unified stream .如下这样做.

    Scala

    Java

    Python

    valnumStreams=5valkafkaStreams=(1tonumStreams).map{i=>KafkaUtils.createStream(...)}valunifiedStream=streamingContext.union(kafkaStreams)unifiedStream.print()

    应考虑的另一个参数是 receiver’s block interval (接收器的块间隔), 这由configuration parameter (配置参数)spark.streaming.blockInterval决定.对于大多数 receivers (接收器), 接收到的数据 coalesced (合并)在一起存储在 Spark 内存之前的 blocks of data (数据块).每个 batch (批次)中的 blocks (块)数确定将用于处理接收到的数据以 map-like (类似与 map 形式的) transformation (转换)的 task (任务)的数量.每个 receiver (接收器)每 batch (批次)的任务数量将是大约( batch interval (批间隔)/ block interval (块间隔)).例如, 200 ms的 block interval (块间隔)每 2 秒 batches (批次)创建 10 个 tasks (任务).如果 tasks (任务)数量太少(即少于每个机器的内核数量), 那么它将无效, 因为所有可用的内核都不会被使用处理数据.要增加 given batch interval (给定批间隔)的 tasks (任务)数量, 请减少 block interval (块间​​隔).但是, 推荐的 block interval (块间隔)最小值约为 50ms , 低于此任务启动开销可能是一个问题.

    使用 multiple input streams (多个输入流)/ receivers (接收器)接收数据的替代方法是明确 repartition (重新分配) input data stream (输入数据流)(使用inputStream.repartition()). 这会在 further processing (进一步处理)之前将 received batches of data (收到的批次数据) distributes (分发)到集群中指定数量的计算机.

    Level of Parallelism in Data Processing (数据处理中的并行度水平)

    如果在任何 computation (计算)阶段中使用 number of parallel tasks (并行任务的数量), 则 Cluster resources (集群资源)可能未得到充分利用. 例如, 对于 distributed reduce (分布式 reduce)操作, 如reduceByKey和reduceByKeyAndWindow, 默认并行任务的数量由spark.default.parallelismconfiguration property控制. 您 可以通过 parallelism (并行度)作为参数(见PairDStreamFunctions文档 , 或设置spark.default.parallelismconfiguration property更改默认值.

    Data Serialization (数据序列化)

    可以通过调优 serialization formats (序列化格式)来减少数据 serialization (序列化)的开销.在 streaming 的情况下, 有两种类型的数据被 serialized (序列化).

    Input data (输入数据): 默认情况下, 通过 Receivers 接收的 input data (输入数据)通过StorageLevel.MEMORY_AND_DISK_SER_2存储在 executors 的内存中.也就是说, 将数据 serialized (序列化)为 bytes (字节)以减少 GC 开销, 并复制以容忍 executor failures (执行器故障).此外, 数据首先保留在内存中, 并且只有在内存不足以容纳 streaming computation (流计算)所需的所有输入数据时才会 spilled over (溢出)到磁盘.这个 serialization (序列化)显然具有开销 - receiver (接收器)必须使接收的数据 deserialize (反序列化), 并使用 Spark 的 serialization format (序列化格式)重新序列化它.

    Persisted RDDs generated by Streaming Operations (流式操作生成的持久 RDDs): 通过 streaming computations (流式计算)生成的 RDD 可能会持久存储在内存中.例如, window operations (窗口操作)会将数据保留在内存中, 因为它们将被处理多次.但是, 与StorageLevel.MEMORY_ONLY Spark Core 默认情况不同, 通过流式计算生成的持久化 RDD 将以StorageLevel.MEMORY_ONLY_SER(即序列化), 以最小化 GC 开销.

    在这两种情况下, 使用 Kryo serialization (Kryo 序列化)可以减少 CPU 和内存开销.有关详细信息, 请参阅Spark Tuning Guide.对于 Kryo , 请考虑 registering custom classes , 并禁用对象引用跟踪(请参阅Configuration Guide中的 Kryo 相关配置).

    streaming application 需要保留的数据量不大的特定情况下, 可以将数据(两种类型)作为 deserialized objects (反序列化对象)持久化, 而不会导致过多的 GC 开销.例如, 如果您使用几秒钟的 batch intervals (批次间隔)并且没有 window operations (窗口操作), 那么可以通过明确地相应地设置 storage level (存储级别)来尝试禁用 serialization in persisted data (持久化数据中的序列化).这将减少由于序列化造成的 CPU 开销, 潜在地提高性能, 而不需要太多的 GC 开销.

    Task Launching Overheads (任务启动开销)

    如果每秒启动的任务数量很高(比如每秒 50 个或更多), 那么这个开销向 slaves 发送任务可能是重要的, 并且将难以实现 sub-second latencies (次要的延迟).可以通过以下更改减少开销:

    Execution mode (执行模式): 以 Standalone mode (独立模式)或 coarse-grained Mesos 模式运行 Spark 比 fine-grained Mesos 模式更好的任务启动时间.有关详细信息, 请参阅Running on Mesos guide.

    这些更改可能会将 batch processing time (批处理时间)缩短 100 毫秒, 从而允许 sub-second batch size (次秒批次大小)是可行的.

    Setting the Right Batch Interval (设置正确的批次间隔)

    对于在集群上稳定地运行的 Spark Streaming application, 该系统应该能够处理数据尽可能快地被接收.换句话说, 应该处理批次的数据就像生成它们一样快.这是否适用于 application 可以在monitoringstreaming web UI 中的 processing times 中被找到, processing time (批处理处理时间)应小于 batch interval (批间隔).

    取决于 streaming computation (流式计算)的性质, 使用的 batch interval (批次间隔)可能对处理由应用程序持续一组固定的 cluster resources (集群资源)的数据速率有重大的影响.例如, 让我们考虑早期的 WordCountNetwork 示例.对于特定的 data rate (数据速率), 系统可能能够跟踪每 2 秒报告 word counts (即 2 秒的 batch interval (批次间隔)), 但不能每 500 毫秒.因此, 需要设置 batch interval (批次间隔), 使预期的数据速率在生产可以持续.

    为您的应用程序找出正确的 batch size (批量大小)的一个好方法是使用进行测试 conservative batch interval (保守的批次间隔)(例如 5-10 秒)和 low data rate (低数据速率).验证是否系统能够跟上 data rate (数据速率), 可以检查遇到的 end-to-end delay (端到端延迟)的值通过每个 processed batch (处理的批次)(在 Spark driver log4j 日志中查找 “Total delay” , 或使用StreamingListener接口). 如果 delay (延迟)保持与 batch size (批量大小)相当, 那么系统是稳定的.除此以外, 如果延迟不断增加, 则意味着系统无法跟上, 因此不稳定.一旦你有一个 stable configuration (稳定的配置)的想法, 你可以尝试增加 data rate and/or 减少 batch size .请注意, momentary increase (瞬时增加)由于延迟暂时增加只要延迟降低到 low value (低值), 临时数据速率增加就可以很好(即, 小于 batch size (批量大小)).

    Memory Tuning (内存调优)

    调整 Spark 应用程序的内存使用情况和 GC behavior 已经有很多的讨论在Tuning Guide.我们强烈建议您阅读一下.在本节中, 我们将在 Spark Streaming applications 的上下文中讨论一些 tuning parameters (调优参数).

    Spark Streaming application 所需的集群内存量在很大程度上取决于所使用的 transformations 类型.例如, 如果要在最近 10 分钟的数据中使用 window operation (窗口操作), 那么您的集群应该有足够的内存来容纳内存中 10 分钟的数据.或者如果要使用大量 keys 的updateStateByKey, 那么必要的内存将会很高.相反, 如果你想做一个简单的 map-filter-store 操作, 那么所需的内存就会很低.

    一般来说, 由于通过 receivers (接收器)接收的数据与 StorageLevel.MEMORY_AND_DISK_SER_2 一起存储, 所以不适合内存的数据将会 spill over (溢出)到磁盘上.这可能会降低 streaming application (流式应用程序)的性能, 因此建议您提供足够的 streaming application (流量应用程序)所需的内存.最好仔细查看内存使用量并相应地进行估算.

    memory tuning (内存调优)的另一个方面是 garbage collection (垃圾收集).对于需要低延迟的 streaming application , 由 JVM Garbage Collection 引起的大量暂停是不希望的.

    有几个 parameters (参数)可以帮助您调整 memory usage (内存使用量)和 GC 开销:

    Persistence Level of DStreams (DStreams 的持久性级别): 如前面在Data Serialization部分中所述, input data 和 RDD 默认保持为 serialized bytes (序列化字节).与 deserialized persistence (反序列化持久性)相比, 这减少了内存使用量和 GC 开销.启用 Kryo serialization 进一步减少了 serialized sizes (序列化大小)和 memory usage (内存使用).可以通过 compression (压缩)来实现内存使用的进一步减少(参见Spark配置spark.rdd.compress), 代价是 CPU 时间.

    Clearing old data (清除旧数据): 默认情况下, DStream 转换生成的所有 input data 和 persisted RDDs 将自动清除. Spark Streaming 决定何时根据所使用的 transformations (转换)来清除数据.例如, 如果您使用 10 分钟的 window operation (窗口操作), 则 Spark Streaming 将保留最近 10 分钟的数据, 并主动丢弃旧数据. 数据可以通过设置streamingContext.remember保持更长的持续时间(例如交互式查询旧数据).

    CMS Garbage Collector (CMS垃圾收集器): 强烈建议使用 concurrent mark-and-sweep GC , 以保持 GC 相关的暂停始终如一.即使 concurrent GC 已知可以减少 系统的整体处理吞吐量, 其使用仍然建议实现更多一致的 batch processing times (批处理时间).确保在 driver (使用--driver-java-options在spark-submit中 )和 executors (使用Spark configurationspark.executor.extraJavaOptions)中设置 CMS GC.

    Other tips (其他提示): 为了进一步降低 GC 开销, 以下是一些更多的提示.

    使用OFF_HEAP存储级别的保持 RDDs .在Spark Programming Guide中查看更多详细信息.

    使用更小的 heap sizes 的 executors.这将降低每个 JVM heap 内的 GC 压力.

    Important points to remember(要记住的要点):

    DStream 与 single receiver (单个接收器)相关联.为了获得读取并行性, 需要创建多个 receivers , 即 multiple DStreams .receiver 在一个 executor 中运行.它占据一个 core (内核).确保在 receiver slots are booked 后有足够的内核进行处理, 即spark.cores.max应该考虑 receiver slots . receivers 以循环方式分配给 executors .

    当从 stream source 接收到数据时, receiver 创建数据 blocks (块).每个 blockInterval 毫秒生成一个新的数据块.在 N = batchInterval/blockInterval 的 batchInterval 期间创建 N 个数据块.这些块由当前 executor 的 BlockManager 分发给其他执行程序的 block managers .之后, 在驱动程序上运行的 Network Input Tracker (网络输入跟踪器)通知有关进一步处理的块位置

    在驱动程序中为在 batchInterval 期间创建的块创建一个 RDD .在 batchInterval 期间生成的块是 RDD 的 partitions .每个分区都是一个 spark 中的 task. blockInterval == batchinterval 意味着创建 single partition (单个分区), 并且可能在本地进行处理.

    除非 non-local scheduling (非本地调度)进行, 否则块上的 map tasks (映射任务)将在 executors (接收 block, 复制块的另一个块)中进行处理.具有更大的 block interval (块间隔)意味着更大的块.spark.locality.wait的高值增加了处理 local node (本地节点)上的块的机会.需要在这两个参数之间找到平衡, 以确保在本地处理较大的块.

    而不是依赖于 batchInterval 和 blockInterval , 您可以通过调用inputDstream.repartition(n)来定义 number of partitions (分区数).这样可以随机重新组合 RDD 中的数据, 创建 n 个分区.是的, 为了更大的 parallelism (并行性).虽然是 shuffle 的代价. RDD 的处理由 driver’s jobscheduler 作为一项工作安排.在给定的时间点, 只有一个 job 是 active 的.因此, 如果一个作业正在执行, 则其他作业将排队.

    如果您有两个 dstream , 将会有两个 RDD 形成, 并且将创建两个将被安排在另一个之后的作业.为了避免这种情况, 你可以联合两个 dstream .这将确保为 dstream 的两个 RDD 形成一个 unionRDD .这个 unionRDD 然后被认为是一个 single job (单一的工作).但 RDD 的 partitioning (分区)不受影响.

    如果 batch processing time (批处理时间)超过 batchinterval (批次间隔), 那么显然 receiver 的内存将会开始填满, 最终会抛出 exceptions (最可能是 BlockNotFoundException ).目前没有办法暂停 receiver .使用 SparkConf 配置spark.streaming.receiver.maxRate, receiver 的 rate 可以受到限制.

    Fault-tolerance Semantics (容错语义)

    在本节中, 我们将讨论 Spark Streaming applications 在该 event 中的行为的失败.

    Background(背景)

    要了解 Spark Streaming 提供的语义, 请记住 Spark 的 RDD 的基本 fault-tolerance semantics (容错语义).

    RDD 是一个不可变的, 确定性地可重新计算的分布式数据集.每个RDD 记住在容错输入中使用的确定性操作的 lineage 数据集创建它.

    如果 RDD 的任何 partition 由于工作节点故障而丢失, 则该分区可以是 从 original fault-tolerant dataset (原始容错数据集)中使用业务流程重新计算.

    假设所有的 RDD transformations 都是确定性的, 最后的数据被转换, 无论 Spark 集群中的故障如何, RDD 始终是一样的.

    Spark 运行在容错文件系统(如 HDFS 或 S3 )中的数据上.因此, 从容错数据生成的所有 RDD 也都是容错的.但是, 这不是在大多数情况下, Spark Streaming 作为数据的情况通过网络接收(除非fileStream被使用).为了为所有生成的 RDD 实现相同的 fault-tolerance properties (容错属性), 接收的数据在集群中的工作节点中的多个 Spark executors 之间进行复制(默认 replication factor (备份因子)为 2).这导致了发生故障时需要恢复的系统中的两种数据:

    Data received and replicated (数据接收和复制)- 这个数据在单个工作节点作为副本的故障中幸存下来, 它存在于其他节点之一上.

    Data received but buffered for replication (接收数据但缓冲进行复制)- 由于不复制, 恢复此数据的唯一方法是从 source 重新获取.

    此外, 我们应该关注的有两种 failures:

    Failure of a Worker Node (工作节点的故障)- 运行 executors 的任何工作节点都可能会故障, 并且这些节点上的所有内存中数据将丢失.如果任何 receivers 运行在失败节点, 则它们的 buffered (缓冲)数据将丢失.

    Failure of the Driver Node (Driver 节点的故障)- 如果运行 Spark Streaming application 的 driver node 发生了故障, 那么显然 SparkContext 丢失了, 所有的 executors 和其内存中的数据也一起丢失了.

    有了这个基础知识, 让我们了解 Spark Streaming 的 fault-tolerance semantics (容错语义).

    Definitions (定义)

    streaming systems (流系统)的语义通常是通过系统可以处理每个记录的次数来捕获的.系统可以在所有可能的操作条件下提供三种类型的保证(尽管有故障等).

    At most once (最多一次): 每个 record (记录)将被处理一次或根本不处理.

    At least once (至少一次): 每个 record (记录)将被处理一次或多次.这比at-most once, 因为它确保没有数据将丢失.但可能有重复.

    Exactly once(有且仅一次): 每个 record (记录) 将被精确处理一次 - 没有数据丢失, 数据不会被多次处理.这显然是三者的最强保证.

    Basic Semantics (基本语义)

    在任何 stream processing system (流处理系统)中, 广义上说, 处理数据有三个步骤.

    Receiving the data (接收数据): 使用 Receivers 或其他方式从数据源接收数据.

    Transforming the data (转换数据): 使用 DStream 和 RDD transformations 来 transformed (转换)接收到的数据.

    Pushing out the data (推出数据): 最终的转换数据被推出到 external systems (外部系统), 如 file systems (文件系统), databases (数据库), dashboards (仪表板)等.

    如果 streaming application 必须实现 end-to-end exactly-once guarantees (端到端的一次且仅一次性保证), 那么每个步骤都必须提供 exactly-once guarantee .也就是说, 每个记录必须被精确地接收一次, 转换完成一次, 并被推送到下游系统一次.让我们在 Spark Streaming 的上下文中了解这些步骤的语义.

    Receiving the data (接收数据): 不同的 input sources 提供不同的保证.这将在下一小节中详细讨论.

    Transforming the data (转换数据): 所有已收到的数据都将被处理exactly once, 这得益于 RDD 提供的保证.即使存在故障, 只要接收到的输入数据可访问, 最终变换的 RDD 将始终具有相同的内容.

    Pushing out the data (推出数据): 默认情况下的输出操作确保at-least once语义, 因为它取决于输出操作的类型( idempotent (幂等))或 downstream system (下游系统)的语义(是否支持 transactions (事务)).但用户可以实现自己的事务机制来实现exactly-once语义.这将在本节后面的更多细节中讨论.

    Semantics of Received Data (接收数据的语义)

    不同的 input sources (输入源)提供不同的保证, 范围从at-least once到exactly once.

    With Files

    如果所有的 input data (输入数据)都已经存在于 fault-tolerant file system (容错文件系统)中 HDFS , Spark Streaming 可以随时从任何故障中恢复并处理所有数据.这给了exactly-once语义, 意味着无论什么故障, 所有的数据将被精确处理一次.

    With Receiver-based Sources (使用基于接收器的数据源)

    对于基于 receivers (接收器)的 input sources (输入源), 容错语义取决于故障场景和接收器的类型. 正如我们earlier讨论的, 有两种类型的 receivers (接收器):

    Reliable Receiver (可靠的接收器)- 这些 receivers (接收机)只有在确认收到的数据已被复制之后确认 reliable sources (可靠的源).如果这样的接收器出现故障, source 将不会被接收对于 buffered (unreplicated) data (缓冲(未复制)数据)的确认.因此, 如果 receiver 是重新启动, source 将重新发送数据, 并且不会由于故障而丢失数据.

    Unreliable Receiver (不可靠的接收器)- 这样的接收器不会发送确认, 因此可能丢失数据, 由于 worker 或 driver 故障.

    根据使用的 receivers 类型, 我们实现以下语义. 如果 worker node 出现故障, 则 reliable receivers 没有数据丢失.unreliable receivers , 收到但未复制的数据可能会丢失.如果 driver node 失败, 那么除了这些损失之外, 在内存中接收和复制的所有过去的数据将丢失.这将影响 stateful transformations (有状态转换)的结果.

    为避免过去收到的数据丢失, Spark 1.2 引入了_write ahead logs_ 将接收到的数据保存到 fault-tolerant storage (容错存储).用write ahead logs enabled reliable receivers, 数据没有丢失.在语义方面, 它提供 at-least once guarantee (至少一次保证).

    下表总结了失败的语义:

    Deployment Scenario (部署场景)Worker Failure (Worker 故障)Driver Failure (Driver 故障)

    Spark 1.1 或更早版本,或者

    Spark 1.2 或者没有 write ahead logs 的更高的版本Buffered data lost with unreliable receivers(unreliable receivers 的缓冲数据丢失)

    Zero data loss with reliable receivers (reliable receivers 的零数据丢失)

    At-least once semantics (至少一次性语义)Buffered data lost with unreliable receivers (unreliable receivers 的缓冲数据丢失)

    Past data lost with all receivers (所有的 receivers 的过去的数据丢失)

    Undefined semantics (未定义语义)

    Spark 1.2 或者带有 write ahead logs 的更高版本Zero data loss with reliable receivers(reliable receivers 的零数据丢失)

    At-least once semantics (至少一次性语义)Zero data loss with reliable receivers and files (reliable receivers 和 files 的零数据丢失)

    At-least once semantics (至少一次性语义)

    With Kafka Direct API (使用 Kafka Direct API)

    Spark 1.3 中, 我们引入了一个新的 Kafka Direct API , 可以确保所有的 Kafka 数据都被 Spark Streaming exactly once (一次)接收.与此同时, 如果您实现 exactly-once output operation (一次性输出操作), 您可以实现 end-to-end exactly-once guarantees (端到端的一次且仅一次性保证).在Kafka Integration Guide中进一步讨论了这种方法.

    Semantics of output operations (输出操作的语义)

    Output operations (输出操作)(如foreachRDD)具有at-least once语义, 也就是说, transformed data (变换后的数据)可能会不止一次写入 external entity (外部实体)在一个 worker 故障事件中.虽然这是可以接受的使用saveAs***Files操作(因为文件将被相同的数据简单地覆盖) 保存到文件系统, 可能需要额外的努力来实现 exactly-once (一次且仅一次)语义.有两种方法.

    Idempotent updates (幂等更新): 多次尝试总是写入相同的数据.例如,saveAs***Files总是将相同的数据写入生成的文件.

    Transactional updates (事务更新): 所有更新都是事务性的, 以便更新完全按原子进行.这样做的一个方法如下.

    使用批处理时间(在foreachRDD中可用)和 RDD 的 partition index (分区索引)来创建 identifier (标识符).该标识符唯一地标识 streaming application 中的 blob 数据.

    使用该 identifier (标识符)blob transactionally (blob 事务地)更新 external system (外部系统)(即, exactly once, atomically (一次且仅一次, 原子性地)).也就是说, 如果 identifier (标识符)尚未提交, 则以 atomically (原子方式)提交 partition data (分区数据)和 identifier (标识符).否则, 如果已经提交, 请跳过更新.

    dstream.foreachRDD { (rdd, time) =>

    rdd.foreachPartition { partitionIterator =>

    val partitionId = TaskContext.get.partitionId()

    val uniqueId = generateUniqueId(time.milliseconds, partitionId)

    // use this uniqueId to transactionally commit the data in partitionIterator

    }

    }

    快速链接

    附加指南

    Kafka 集成指南

    Kinesis 集成指南

    自定义 Receiver(接收器)指南

    第三方 DStream 数据源可以在第三方项目上查看.

    API 文档

    Scala 文档

    StreamingContextDStream

    KafkaUtils,FlumeUtils,KinesisUtils,

    Java 文档

    JavaStreamingContext,JavaDStreamJavaPairDStream

    KafkaUtils,FlumeUtils,KinesisUtils

    Python 文档

    StreamingContextDStream

    KafkaUtils

    更多的示例在ScalaJavaPython

    描述 Spark Streaming 的Papervideo.

    我们一直在努力

    apachecn/spark-doc-zh

     

     

    原文地址: http://spark.apachecn.org/docs/cn/2.2.0/streaming-programming-guide.html

    网页地址: http://spark.apachecn.org/

    github: https://github.com/apachecn/spark-doc-zh(觉得不错麻烦给个 Star,谢谢!~)

     

     

     

     

     

    Apache Spark 2.2.0 中文文档 - Structured Streaming 编程指南 | ApacheCN

    Joyyx 关注

    2017.09.27 10:48* 字数 10941 阅读 33评论 0喜欢 0

    Structured Streaming 编程指南

    概述

    快速示例

    Programming Model (编程模型)

    基本概念

    处理 Event-time 和延迟数据

    容错语义

    API 使用 Datasets 和 DataFrames

    创建 streaming DataFrames 和 streaming Datasets

    Input Sources (输入源)

    streaming DataFrames/Datasets 的模式接口和分区

    streaming DataFrames/Datasets 上的操作

    基础操作 - Selection, Projection, Aggregation

    Window Operations on Event Time (事件时间窗口操作)

    处理 Late Data (迟到数据)和 Watermarking (水印)

    Join 操作

    Streaming Deduplication (Streaming 去重)

    Arbitrary Stateful Operations (任意有状态的操作)

    不支持的操作

    开始 Streaming Queries

    Output Modes (输出模式)

    Output Sinks (输出接收器)

    使用 Foreach

    管理 Streaming Queries

    监控 Streaming Queries

    Interactive APIs

    Asynchronous API

    Recovering from Failures with Checkpointing (从检查点恢复故障)

    从这里去哪儿

    概述

    Structured Streaming (结构化流)是一种基于 Spark SQL 引擎构建的可扩展且容错的 stream processing engine (流处理引擎)。您可以以静态数据表示批量计算的方式来表达 streaming computation (流式计算)。 Spark SQL 引擎将随着 streaming data 持续到达而增量地持续地运行,并更新最终结果。您可以使用 Scala , Java , Python 或 R 中的Dataset/DataFrame API来表示 streaming aggregations (流聚合), event-time windows (事件时间窗口), stream-to-batch joins (流到批处理连接) 等。在同一个 optimized Spark SQL engine (优化的 Spark SQL 引擎)上执行计算。最后,系统通过 checkpointing (检查点) 和 Write Ahead Logs (预写日志)来确保 end-to-end exactly-once (端到端的完全一次性) 容错保证。简而言之,Structured Streaming 提供快速,可扩展,容错,end-to-end exactly-once stream processing (端到端的完全一次性流处理),而无需用户理解 streaming 。

    在本指南中,我们将向您介绍 programming model (编程模型) 和 APIs 。首先,我们从一个简单的例子开始 - 一个 streaming word count 。

    快速示例

    假设您想要保持从监听 TCP socket 的 data server (数据服务器) 接收的 text data (文本数据)的运行的 word count 。 让我们看看如何使用 Structured Streaming 表达这一点。你可以在Scala/Java/Python/R之中看到完整的代码。 Let’s say you want to maintain a running word count of text data received from a data server listening on a TCP socket. Let’s see how you can express this using Structured Streaming. You can see the full code inScala/Java/Python/R 并且如果您下载 Spark,您可以直接运行这个例子。在任何情况下,让我们逐步了解示例并了解它的工作原理。首先,我们必须导入必要的 classes 并创建一个本地的 SparkSession ,这是与 Spark 相关的所有功能的起点。

    Scala

    Java

    Python

    R

    importorg.apache.spark.sql.functions._importorg.apache.spark.sql.SparkSessionvalspark=SparkSession.builder.appName("StructuredNetworkWordCount").getOrCreate()importspark.implicits._

    接下来,我们创建一个 streaming DataFrame ,它表示从监听 localhost:9999 的服务器上接收的 text data (文本数据),并且将 DataFrame 转换以计算 word counts 。

    Scala

    Java

    Python

    R

    // 创建表示从连接到 localhost:9999 的输入行 stream 的 DataFramevallines=spark.readStream.format("socket").option("host","localhost").option("port",9999).load()// 将 lines 切分为 wordsvalwords=lines.as[String].flatMap(_.split(" "))// 生成正在运行的 word countvalwordCounts=words.groupBy("value").count()

    这个linesDataFrame 表示一个包含包含 streaming text data (流文本数据) 的无边界表。此表包含了一列名为 “value” 的 strings ,并且 streaming text data 中的每一 line (行)都将成为表中的一 row (行)。请注意,这并不是正在接收的任何数据,因为我们只是设置 transformation (转换),还没有开始。接下来,我们使用.as[String]将 DataFrame 转换为 String 的 Dataset ,以便我们可以应用flatMap操作将每 line (行)切分成多个 words 。所得到的wordsDataset 包含所有的 words 。最后,我们通过将 Dataset 中 unique values (唯一的值)进行分组并对它们进行计数来定义wordCountsDataFrame 。请注意,这是一个 streaming DataFrame ,它表示 stream 的正在运行的 word counts 。

    我们现在已经设置了关于 streaming data (流数据)的 query (查询)。剩下的就是实际开始接收数据并计算 counts (计数)。为此,我们将其设置为在每次更新时将完整地计数(由outputMode("complete")指定)发送到控制台。然后使用start()启动 streaming computation (流式计算)。

    Scala

    Java

    Python

    R

    // 开始运行将 running counts 打印到控制台的查询valquery=wordCounts.writeStream.outputMode("complete").format("console").start()query.awaitTermination()

    执行此代码之后, streaming computation (流式计算) 将在后台启动。query对象是该 active streaming query (活动流查询)的 handle (句柄),并且我们决定使用awaitTermination()来等待查询的终止,以防止查询处于 active (活动)状态时退出。

    要实际执行此示例代码,您可以在您自己的Spark 应用程序编译代码,或者简单地运行示例一旦您下载了 Spark 。我们正在展示的是后者。您将首先需要运行 Netcat (大多数类 Unix 系统中的一个小型应用程序)作为 data server 通过使用

    $ nc -lk 9999

    然后,在一个不同的终端,您可以启动示例通过使用

    Scala

    Java

    Python

    R

    $ ./bin/run-example org.apache.spark.examples.sql.streaming.StructuredNetworkWordCount localhost9999

    然后,在运行 netcat 服务器的终端中输入的任何 lines 将每秒计数并打印在屏幕上。它看起来像下面这样。

    # 终端 1:# 运行 Netcat$ nc -lk9999apache sparkapache hadoop...

    Scala

    Java

    Python

    R

    # 终端 2: 运行 StructuredNetworkWordCount$ ./bin/run-example org.apache.spark.examples.sql.streaming.StructuredNetworkWordCount localhost9999-------------------------------------------Batch:0-------------------------------------------+------+-----+|value|count|+------+-----+|apache|1||spark|1|+------+-----+-------------------------------------------Batch:1-------------------------------------------+------+-----+|value|count|+------+-----+|apache|2||spark|1||hadoop|1|+------+-----+...

    Programming Model (编程模型)

    Structured Streaming 的关键思想是将 live data stream (实时数据流)视为一种正在不断 appended (附加)的表。这形成了一个与 batch processing model (批处理模型)非常相似的新的 stream processing model (流处理模型)。您会将您的 streaming computation (流式计算)表示为在一个静态表上的 standard batch-like query (标准类批次查询),并且 Spark 在unbounded(无界)输入表上运行它作为incremental(增量)查询。让我们更加详细地了解这个模型。

    基本概念

    input data stream (输入数据流) 视为 “Input Table”(输入表)。每个在 stream 上到达的 data item (数据项)就像是一个被 appended 到 Input Table 的新的 row 。

     

    对输入的查询将生成 “Result Table” (结果表)。每个 trigger interval (触发间隔)(例如,每 1 秒),新 row (行)将附加到 Input Table ,最终更新 Result Table 。无论何时更新 result table ,我们都希望将 changed result rows (更改的结果行)写入 external sink (外部接收器)。

     

    “Output(输出)” 被定义为写入 external storage (外部存储器)的内容。可以以不同的模式定义 output :

    Complete Mode(完全模式)- 整个更新的 Result Table 将被写入外部存储。由 storage connector (存储连接器)决定如何处理整个表的写入。

    Append Mode(附加模式)- 只有 Result Table 中自上次触发后附加的新 rows(行) 将被写入 external storage (外部存储)。这仅适用于不期望更改 Result Table 中现有行的查询。

    Update Mode(更新模式)- 只有自上次触发后 Result Table 中更新的 rows (行)将被写入 external storage (外部存储)(从 Spark 2.1.1 之后可用)。请注意,这与 Complete Mode (完全模式),因为此模式仅输出自上次触发以来更改的 rows (行)。如果查询不包含 aggregations (聚合),它将等同于 Append mode 。

    请注意,每种模式适用于特定模型的查询。这将在later详细讨论。

    为了说明这个模型的使用,我们来了解一下上面章节的快速示例。第一个linesDataFrame 是 input table ,并且最后的wordCountsDataFrame 是 result table 。请注意,streaminglinesDataFrame 上的查询生成wordCounts是exactly the same(完全一样的)因为它将是一个 static DataFrame (静态 DataFrame )。但是,当这个查询启动时, Spark 将从 socket 连接中持续检查新数据。如果有新数据,Spark 将运行一个 “incremental(增量)” 查询,它会结合以前的 running counts (运行计数)与新数据计算更新的 counts ,如下所示。

     

    这种模式与许多其他 stream processing engines (流处理引擎)有着显著不同。许多 streaming systems (流系统)要求用户本身保持运行 aggregations (聚合),因此必须要考虑容错,和数据一致性(at-least-once(至少一次), at-most-once (最多一次),exactly-once (完全一次))。在这个模型中,当有新数据时, Spark 负责更新 Result Table ,从而减轻用户对它的考虑。举个例子,我们来看一下这个模型如何处理对于基于 event-time 的处理和 late arriving (迟到)的数据。

    处理 Event-time 和延迟数据

    Event-time 是数据本身 embedded (嵌入)的时间。对于很多应用程序,您可能需要在此 event-time 进行操作。例如,如果要每分钟获取 IoT devices (设备)生成的 events 数,则可能希望使用数据生成的时间(即数据中的 event-time ),而不是 Spark 接收到它们的时间。这个 event-time 在这个模型中非常自然地表现出来 – 来自 devices (设备)的每个 event 都是表中的一 row(行),并且 event-time 是 row (行)中的 column value (列值)。这允许 window-based aggregations (基于窗口的聚合)(例如每分钟的 events 数)仅仅是 event-time 列上的特殊类型的 group (分组)和 aggregation (聚合) – 每个 time window 是一个组,并且每一 row (行)可以属于多个 windows/groups 。因此,可以在 static dataset (静态数据集)(例如来自 collected device events logs (收集的设备事件日志))以及 data stream 上一致地定义 event-time-window-based aggregation queries (基于事件时间窗口的聚合查询),从而使用户的使用寿命更加容易。

    此外,这个模型自然地处理了比预计将根据它的 event-time 到达的数据晚到的数据。由于 Spark 正在更新 Result Table , Spark 有完整的控制对当有迟到的数据时 updating old aggregates (更新旧的聚合),以及清理 old aggregates (旧聚合) 以限制 intermediate state data (中间体状态数据)的大小。自 Spark 2.1 以来,我们对于 watermarking 进行了支持,允许用户指定 late data 的阈值,并允许引擎相应地清理旧状态。这些将在后面的Window Operations部分解释。

    容错语义

    提供 end-to-end exactly-once semantics (端到端的完全一次性语义)是 Structured Streaming 设计背后的关键目标之一。为了实现这一点,我们设计了 Structured Streaming sources , sinks 和 execution engine (执行引擎),以可靠的跟踪处理确切进度,以便它可以通过 restarting and/or reprocessing (重新启动和/或重新处理)来处理任何类型的故障。假设每个 streaming source 都具有 offsets (偏移量)(类似于 Kafka offsets 或 Kinesis sequence numbers (Kafka 偏移量或 Kinesis 序列号))来跟踪 stream 中的 read position (读取位置)。引擎使用 checkpointing (检查点)并 write ahead logs (预写日志)记录每个 trigger (触发器)中正在处理的数据的 offset range (偏移范围)。 streaming sinks 设计为处理后处理的 idempotent (幂等)。一起使用 replayable sources (可重放源)和 idempotent sinks (幂等接收器), Structured Streaming 可以确保在任何故障下end-to-end exactly-once semantics(端对端完全一次性语义)。

    API 使用 Datasets 和 DataFrames

    自从 Spark 2.0 , DataFrame 和 Datasets 可以表示 static (静态), bounded data(有界数据),以及 streaming , unbounded data (无界数据)。类似于 static Datasets/DataFrames ,您可以使用常用的 entry point (入口点)SparkSession(Scala/Java/Python/R文档) 来从 streaming sources 中创建 streaming DataFrames/Datasets ,并将其作为 static DataFrames/Datasets 应用相同的操作。如果您不熟悉 Datasets/DataFrames ,强烈建议您使用DataFrame/Dataset 编程指南来熟悉它们。

    创建 streaming DataFrames 和 streaming Datasets

    可以通过DataStreamReader的接口 (Scala/Java/Python文档 )来创建 Streaming DataFrames 并由SparkSession.readStream()返回。在R中,使用read.stream()方法。与创建 static DataFrame 的 read interface (读取接口)类似,您可以指定 source - data format (数据格式), schema (模式), options (选项)等的详细信息。

    Input Sources (输入源)

    Spark 2.0 中,有一些内置的 sources 。

    File source(文件源)- 以文件流的形式读取目录中写入的文件。支持的文件格式为 text , csv , json , parquet 。有关更多的 up-to-date 列表,以及每种文件格式的支持选项,请参阅 DataStreamReader interface 的文档。请注意,文件必须以 atomically (原子方式)放置在给定的目录中,这在大多数文件系统中可以通过文件移动操作实现。

    Kafka source(Kafka 源)- 来自 Kafka 的 Poll 数据。它与 Kafka broker 的 0.10.0 或者更高的版本兼容。有关详细信息,请参阅Kafka Integration 指南

    Socket source (for testing) (Socket 源(用于测试))- 从一个 socket 连接中读取 UTF8 文本数据。 listening server socket (监听服务器 socket)位于 driver 。请注意,这只能用于测试,因为它不提供 end-to-end fault-tolerance (端到端的容错)保证。

    某些 sources 是不容错的,因为它们不能保证数据在使用 checkpointed offsets (检查点偏移量)故障之后可以被重新使用。参见前面的部分fault-tolerance semantics。以下是 Spark 中所有 sources 的详细信息。

    SourceOptions(选项)Fault-tolerant(容错)Notes(说明)

    File source(文件源)path: 输入路径的目录,并且与所有文件格式通用。

    maxFilesPerTrigger: 每个 trigger (触发器)中要考虑的最大新文件数(默认是: 无最大值)

    latestFirst: 是否先处理最新的新文件,当有大量积压的文件时有用(默认: false)

    fileNameOnly: 是否仅根据文件名而不是完整路径检查新文件(默认值: false)。将此设置为 `true` ,以下文件将被视为相同的文件,因为它们的文件名 "dataset.txt" 是相同的:

    · "file:///dataset.txt"

    · "s3://a/dataset.txt"

    · "s3n://a/b/dataset.txt"

    · "s3a://a/b/c/dataset.txt"

    有关特定于 file-format-specific (文件格式)的选项,请参阅DataStreamReader(Scala/Java/Python/R) 中的相关方法。例如,对于 "parquet" 格式选项请参阅DataStreamReader.parquet()Yes支持 glob 路径,但是不支持多个逗号分隔的 paths/globs 。

    Socket Source(Socket 源)host: 连接到的 host ,必须指定

    port: 连接的 port (端口),必须指定No

    Kafka Source(Kafka 源)请查看Kafka Integration 指南.Yes

    这里有一些例子。

    Scala

    Java

    Python

    R

    valspark:SparkSession=...// 从 socket 读取 textvalsocketDF=spark.readStream.format("socket").option("host","localhost").option("port",9999).load()socketDF.isStreaming// 对于有 streaming sources 的 DataFrame 返回 TruesocketDF.printSchema// 读取目录内原子写入的所有 csv 文件valuserSchema=newStructType().add("name","string").add("age","integer")valcsvDF=spark.readStream.option("sep",";").schema(userSchema)// 指定 csv 文件的模式.csv("/path/to/directory")// 等同于 format("csv").load("/path/to/directory")

    这些示例生成无类型的 streaming DataFrames ,这意味着在编译时不会检查 DataFrame 的模式,仅在运行时在 query is submitted (查询提交)的时候进行检查。像map,flatMap等这样的操作需要在编译时知道这个类型。要做到这一点,您可以使用与 static DataFrame 相同的方法将这些 untyped (无类型)的 streaming DataFrames 转换为 typed streaming Datasets (类型化的 streaming Datasets )。有关详细信息,请参阅SQL 编程指南。此外,有关支持的 streaming sources 的更多详细信息将在文档后面讨论。

    streaming DataFrames/Datasets 的模式接口和分区

    默认情况下,基于文件的 sources 的 Structured Streaming 需要您指定 schema (模式),而不是依靠 Spark 自动 infer 。这种 restriction 确保了 consistent schema (一致的模式)将被用于 streaming query (流式查询),即使在出现故障的情况下也是如此。对于 ad-hoc use cases (特殊用例),您可以通过将spark.sql.streaming.schemaInference设置为true来重新启用 schema inference (模式接口)。

    当存在名为/key=value/的子目录并且列表将自动递归到这些目录中时,会发生 Partition discovery (分区发现)。如果这些 columns (列)显示在用户提供的 schema 中,则它们将根据正在读取的文件路径由 Spark 进行填充。 构成 partitioning scheme (分区方案)的目录 must be present when the query starts (必须在查询开始时是存在的),并且必须保持 static 。例如,当/data/year=2015/存在时,可以添加/data/year=2016/,但是更改 partitioning column (分区列)是无效的(即通过创建目录/data/date=2016-04-17/)。

    streaming DataFrames/Datasets 上的操作

    您可以对 streaming DataFrames/Datasets 应用各种操作 - 从 untyped (无类型), SQL-like operations (类似 SQL 的操作)(例如select,where,groupBy) 到 typed RDD-like operations (类型化的类似 RDD 的操作)(例如map,filter,flatMap)。有关详细信息,请参阅SQL 编程指南。让我们来看看可以使用的几个示例操作。

    基础操作 - Selection, Projection, Aggregation

    streaming 支持 DataFrame/Dataset 上的大多数常见操作。不支持的少数操作discussed later将在本节中讨论(稍后讨论)。

    Scala

    Java

    Python

    R

    caseclassDeviceData(device:String,deviceType:String,signal:Double,time:DateTime)valdf:DataFrame=...// streaming DataFrame with IOT device data with schema { device: string, deviceType: string, signal: double, time: string }valds:Dataset[DeviceData]=df.as[DeviceData]// streaming Dataset with IOT device data// Select the devices which have signal more than 10df.select("device").where("signal > 10")// using untyped APIsds.filter(_.signal>10).map(_.device)// using typed APIs// Running count of the number of updates for each device typedf.groupBy("deviceType").count()// using untyped API// Running average signal for each device typeimportorg.apache.spark.sql.expressions.scalalang.typedds.groupByKey(_.deviceType).agg(typed.avg(_.signal))// using typed API

    Window Operations on Event Time (事件时间窗口操作)

    通过 Structured Streaming , sliding event-time window (滑动事件时间窗口)的 Aggregations (聚合)很简单,与 grouped aggregations (分组聚合)非常相似。在 grouped aggregation (分组聚合)中,为 user-specified grouping column (用户指定的分组列)中的每个唯一值维护 aggregate values (聚合值)(例如 counts )。在 window-based aggregations (基于窗口的聚合)的情况下,针对每个窗口的 event-time 维持 aggregate values (聚合值)。让我们用一个例子来理解这一点。

    想象一下,我们的快速示例被修改,并且 stream 现在包含生成 line 的时间的 line 。不运行 word counts ,我们想 count words within 10 minute windows (在 10 分钟内的窗口计数单词),每 5 分钟更新一次。也就是说,在 10 minute windows (10 分钟的窗口之间)收到的 word counts 12:00 - 12:10, 12:05 - 12:15, 12:10 - 12:20 等。请注意, 12:00 - 12:10 表示数据在 12:00 之后但在 12:10 之前抵达。现在,考虑在 12:07 收到一个 word 。这个 word 应该增加对应于两个窗口的计数 12:00 - 12:10 和 12:05 - 12:15 。因此, counts 将被二者分组, grouping key (分组秘钥)(即 word)和 window (窗口)(可以从 event-time 计算)来 indexed (索引)。

    result tables 将如下所示。

     

    由于这个 windowing (窗口)类似于 grouping (分组),在代码中,您可以使用groupBy()和window()操作来表示 windowed aggregations (窗口化的聚合)。您可以看到以下示例Scala/Java/Python的完整代码。

    Scala

    Java

    Python

    importspark.implicits._valwords=...// streaming DataFrame of schema { timestamp: Timestamp, word: String }// Group the data by window and word and compute the count of each groupvalwindowedCounts=words.groupBy(window($"timestamp","10 minutes","5 minutes"),$"word").count()

    处理 Late Data (迟到数据)和 Watermarking (水印)

    现在考虑以下如果其中一个 event 迟到应用程序会发生什么。例如,想象一下,在 12:04 (即 event time )生成的 word 可以在 12:11 被接收申请。应用程序应该使用 12:04 而不是 12:11 来更新 window12:00 - 12:10的较旧 counts 。发生这种情况自然就是在我们 window-based grouping (基于窗口的分组中) - Structured Streaming 可以保持intermediate state 对于部分 aggregates (聚合)长时间,以便后期数据可以 update aggregates of old windows correctly (更新聚合)旧窗口正确,如下图所示。

     

    但是,要运行此查询几天,系统必须绑定 the amount of intermediate in-memory state it accumulates (中间状态累积的数量)。这意味着系统需要知道什么时候 old aggregate (老聚合)可以从内存中的状态丢失,因为这个应用程序不会在继续接收 aggregate (该聚合)的更多late data (后期的数据)。为了实现这一点,在 Spark 2.1 中,我们介绍了watermarking(水印),让引擎自动跟踪数据中的 current event time (当前事件时间)并试图相应地清理旧状态。您可以定义查询的 watermark 指定 event time column (事件时间列)和数据预期的延迟阈值 event time (事件时间)。对于从T时间开始的特定窗口,引擎将保持状态并允许 late data (延迟数据)更新状态直到(max event time seen by the engine - late threshold > T)。换句话说, threshold (阈值)内的 late data (晚期数据)将被 aggregated ,但数据晚于阈值将被丢弃。让我们以一个例子来理解这一点。我们可以使用withWatermark()可以轻松地定义上一个例子的 watermarking (水印),如下所示。

    Scala

    Java

    Python

    importspark.implicits._valwords=...// streaming DataFrame of schema { timestamp: Timestamp, word: String }// Group the data by window and word and compute the count of each groupvalwindowedCounts=words.withWatermark("timestamp","10 minutes").groupBy(window($"timestamp","10 minutes","5 minutes"),$"word").count()

    在这个例子中,我们正在定义查询的 watermark 对 “timestamp” 列的值,并将 “10 minutes” 定义为允许数据延迟的阈值。如果这个查询以 Update output mode (更新输出模式)运行(稍后在Output Modes部分中讨论),引擎将不断更新 Result Table 中窗口的 counts ,直到 window is older than the watermark (窗口比水印较旧),它滞后于 current event time (当前事件时间)列 “timestamp” 10分钟。这是一个例子。

     

    如图所示,maximum event time tracked (引擎跟踪的最大事件时间)是蓝色虚线,watermark 设置为(max event time - '10 mins')在每个触发的开始处是红线。例如,当引擎观察数据(12:14, dog)时,它为下一个触发器设置 watermark 为12:04。该 watermark 允许 engine 保持 intermediate state (中间状态)另外 10 分钟以允许延迟 late data to be counted (要计数的数据)。例如,数据(12:09, cat)是 out of order and late (不正常的,而且延迟了),它落在了 windows12:05 - 12:15和12:10 - 12:20。因为它仍然在 watermark12:04之前的触发器,引擎仍然将 intermediate counts (中间计数)保持为状态并正确 updates the counts of the related windows (更新相关窗口的计数)。然而,当 watermark 更新为12:11时,window(12:00 - 12:10)的中间状态被清除,所有 subsequent data (后续数据)(例如(12:04, donkey))被认为是 “too late” ,因此被忽视。请注意,每次触发后,写入 updated counts (更新的计数)(即紫色行)作为 trigger output 进行 sink ,如下 Update mode 所示。

    某些 sinks (接收器)(例如 文件)可能不支持更新模式所需的 fine-grained updates (细粒度更新)。 与他们一起工作,我们也支持 Append Mode (附加模式),只有final counts(最终计数)被写入 sink 。这如下所示。

    请注意,在 non-streaming Dataset (非流数据集)上使用withWatermark是不可行的。 由于 watermark 不应该以任何方式影响任何批处理查询,我们将直接忽略它。

     

    与之前的 Update Mode 类似,引擎维护 intermediate counts for each window (每个窗口的中间计数)。但是,partial counts (部分计数)不会更新到 Result Table ,也不是写入 sink 。 引擎等待迟到的 “10 mins” 计数,然后删除 window < watermark 的 intermediate state (中间状态),并追加最终 计数到 Result Table/sink 。 例如, window12:00 - 12:10的最终计数是仅在水印更新为12:11之后附加到 Result Table 。

    Conditions for watermarking to clean aggregation state(watermarking 清理聚合状态的条件)重要的是要注意,watermarking 必须满足以下清理聚合查询中的状态的条件(从 Spark 2.1.1 开始,将来会更改)。

    Output mode must be Append or Update.(输出模式必须是追加或者更新)Complete mode 要求保留所有 aggregate data (聚合数据),因此不能使用 watermarking 去掉 intermediate state (中间状态)。参见Output Modes部分,详细说明每种输出模式的语义。

    aggregation (聚合)必须具有 event-time column (事件时间列)或 event-time column 上的window。

    withWatermark必须被调用与聚合中使用的 timestamp column (时间戳列)相同的列。例如,df.withWatermark("time", "1 min").groupBy("time2").count()在 Append output mode 是无效的,因为 watermark 是从聚合列在不同的列上定义的。

    在使用 watermark details 的 aggregation (聚合)之前必须调用withWatermark。例如,df.groupBy("time").count().withWatermark("time", "1 min")在 Append output mode 中是无效的。

    Join 操作

    Streaming DataFrames 可以与 static DataFrames 连接,以创建新的 streaming DataFrames 。 这里有几个例子。

    Scala

    Java

    Python

    valstaticDf=spark.read....valstreamingDf=spark.readStream....streamingDf.join(staticDf,"type")// inner equi-join with a static DFstreamingDf.join(staticDf,"type","right_join")// right outer join with a static DF

    Streaming Deduplication (Streaming 去重)

    您可以使用 events 中的 unique identifier (唯一标识符)对 data streams 中的记录进行重复数据删除。 这与使用唯一标识符列的 static 重复数据消除完全相同。 该查询将存储先前记录所需的数据量,以便可以过滤重复的记录。 与 aggregations (聚合)类似,您可以使用带有或不带有 watermarking 的重复数据删除功能。

    With watermark(使用 watermark )- 如果重复记录可能到达的时间有上限,则可以在 event time column (事件时间列)上定义 watermark ,并使用 guid 和 event time columns 进行重复数据删除。 该查询将使用 watermark 从以前的记录中删除旧的状态数据,这些记录不会再受到任何重复。 这界定了查询必须维护的状态量。

    Without watermark (不适用 watermark )- 由于当重复记录可能到达时没有界限,查询将来自所有过去记录的数据存储为状态。

    Scala

    Java

    Python

    valstreamingDf=spark.readStream....// columns: guid, eventTime, ...// Without watermark using guid columnstreamingDf.dropDuplicates("guid")// With watermark using guid and eventTime columnsstreamingDf.withWatermark("eventTime","10 seconds").dropDuplicates("guid","eventTime")

    Arbitrary Stateful Operations (任意有状态的操作)

    许多用例需要比 aggregations 更高级的状态操作。例如,在许多用例中,您必须 track (跟踪) data streams of events (事件数据流)中的 sessions (会话)。对于进行此类 sessionization (会话),您必须将 arbitrary types of data (任意类型的数据)保存为 state (状态),并在每个 trigger 中使用 state using the data stream events (数据流事件对状态)执行 arbitrary operations 。自从 Spark 2.2 ,可以使用mapGroupsWithState操作和更强大的操作flatMapGroupsWithState来完成。这两个操作都允许您在 grouped Datasets (分组的数据集)上应用用户定义的代码来更新用户定义的状态。有关更具体的细节,请查看 API文档(Scala/Java) 和例子 (Scala/Java)。

    不支持的操作

    streaming DataFrames/Datasets 不支持一些 DataFrame/Dataset 操作。其中一些如下。

    streaming Datasets 不支持 Multiple streaming aggregations (多个流聚合) (i.e. a chain of aggregations on a streaming DF)(即 streaming DF 上的聚合链)

    streaming Datasets 不支持 Limit and take first N rows 。

    streaming Datasets 上的 Distinct operations 不支持。

    只有在 aggregation 和 Complete Output Mode 下,streaming Datasets 才支持排序操作。

    有条件地支持 streaming 和 static Datasets 之间的 Outer joins 。

    不支持使用 streaming Dataset 的 Full outer join

    不支持在右侧使用 streaming Dataset 的 Left outer join

    不支持在左侧使用 streaming Dataset 的 Right outer join

    不支持两种 streaming Datasets 之间的任何种类的 joins 。

    此外,还有一些 Dataset 方法将不适用于 streaming Datasets 。他们是立即运行查询并返回结果的操作,这在 streaming Dataset 上没有意义。相反,这些功能可以通过显式启动 streaming query 来完成(参见下一节)。

    count()- 无法从 streaming Dataset 返回 single count 。 而是使用ds.groupBy().count()返回一个包含 running count 的 streaming Dataset 。

    foreach()- 而是使用ds.writeStream.foreach(...)(参见下一节).

    show()- 而是使用 console sink (参见下一节).

    如果您尝试任何这些操作,您将看到一个AnalysisException,如 “operation XYZ is not supported with streaming DataFrames/Datasets” 。虽然其中一些可能在未来版本的 Spark 中得到支持,还有其他一些从根本上难以有效地实现 streaming data 。例如, input stream 的排序不受支持,因为它需要保留 track of all the data received in the stream (跟踪流中接收到的所有数据)。 因此从根本上难以有效率地执行。

    开始 Streaming Queries

    一旦定义了 final result DataFrame/Dataset ,剩下的就是让你开始 streaming computation 。 为此,您必须使用DataStreamWriter(Scala/Java/Python文档)通过Dataset.writeStream()返回。您将必须在此 interface 中指定以下一个或多个。

    Details of the output sink ( output sink 的详细信息):Data format, location, etc.

    Output mode (输出模式):指定写入 output sink 的内容。

    Query name (查询名称):可选,指定用于标识的查询的唯一名称。

    Trigger interval (触发间隔):可选,指定触发间隔。 如果未指定,则系统将在上一次处理完成后立即检查新数据的可用性。 如果由于先前的处理尚未完成而导致触发时间错误,则系统将尝试在下一个触发点触发,而不是在处理完成后立即触发。

    Checkpoint location (检查点位置):对于可以保证 end-to-end fault-tolerance (端对端容错)能力的某些 output sinks ,请指定系统将写入所有 checkpoint (检查点)信息的位置。 这应该是与 HDFS 兼容的容错文件系统中的目录。 检查点的语义将在下一节中进行更详细的讨论。

    Output Modes (输出模式)

    有几种类型的输出模式。

    Append mode (default) (附加模式(默认))- 这是默认模式,其中只有 自从 last trigger (上一次触发)以来,添加到 Result Table 的新行将会是 outputted to the sink 。 只有添加到 Result Table 的行将永远不会改变那些查询才支持这一点。 因此,这种模式 保证每行只能输出一次(假设 fault-tolerant sink )。例如,只有select,where,map,flatMap,filter,join等查询支持 Append mode 。

    Complete mode (完全模式)- 每次触发后,整个 Result Table 将被输出到 sink 。 aggregation queries (聚合查询)支持这一点。

    Update mode (更新模式)- (自 Spark 2.1.1 可用) 只有 Result Table rows 自上次触发后更新将被输出到 sink 。更多信息将在以后的版本中添加。

    不同类型的 streaming queries 支持不同的 output modes 。 以下是兼容性矩阵。

    Query Type(查询类型)Supported Output Modes(支持的输出模式)Notes(说明)

    Queries with aggregation (使用聚合的查询)Aggregation on event-time with watermark (使用 watermark 的 event-time 聚合 )Append, Update, Complete (附加,更新,完全)Append mode 使用 watermark 来降低 old aggregation state (旧聚合状态)。 但输出 windowed aggregation (窗口聚合)延迟在 `withWatermark()` 中指定的 late threshold (晚期阈值)模式语义,rows 只能在 Result Table 中添加一次在 finalized (最终确定)之后(即 watermark is crossed (水印交叉)后)。 有关详细信息,请参阅Late Data部分。

    Update mode 使用 watermark 删除 old aggregation state (旧的聚合状态)。

    Complete mode (完全模式)不会删除旧的聚合状态,因为从定义这个模式          保留 Result Table 中的所有数据。

    Other aggregations (其他聚合)Complete, Update (完全,更新)由于没有定义 watermark(仅在其他 category 中定义),旧的聚合状态不会删除。

    不支持 Append mode ,因为 aggregates (聚合)可以更新,从而违反了这种模式的语义。

    Queries withmapGroupsWithStateUpdate (更新)

    Queries withflatMapGroupsWithStateAppend operation mode (附加操作模式)Append (附加)flatMapGroupsWithState之后允许 Aggregations (聚合)。

    Update operation mode (更新操作模式)Update(更新)flatMapGroupsWithState之后不允许 Aggregations (聚合)。

    Other queries (其他查询)Append, Update (附加,更新)不支持 Complete mode ,因为将所有未分组数据保存在 Result Table 中是不可行的 。

    Output Sinks (输出接收器)

    有几种类型的内置输出接收器。

    File sink (文件接收器)- 将输出存储到目录中。

    writeStream.format("parquet")// can be "orc", "json", "csv", etc..option("path","path/to/destination/dir").start()

    Foreach sink- 对 output 中的记录运行 arbitrary computation 。 有关详细信息,请参阅本节后面部分。

    writeStream.foreach(...).start()

    Console sink (for debugging) (控制台接收器(用于调试))- 每次触发时,将输出打印到 console/stdout 。 都支持 Append 和 Complete 输出模式。 这应该用于低数据量的调试目的,因为在每次触发后,整个输出被收集并存储在驱动程序的内存中。

    writeStream.format("console").start()

    Memory sink (for debugging) (内存 sink (用于调试))- 输出作为 in-memory table (内存表)存储在内存中。都支持 Append 和 Complete 输出模式。 这应该用于调试目的在低数据量下,整个输出被收集并存储在驱动程序的存储器中。因此,请谨慎使用。

    writeStream.format("memory").queryName("tableName").start()

    某些 sinks 是不容错的,因为它们不能保证输出的持久性并且仅用于调试目的。参见前面的部分容错语义。以下是 Spark 中所有接收器的详细信息。

    Sink (接收器)Supported Output Modes (支持的输出模式)Options (选项)Fault-tolerant (容错)Notes (说明)

    File Sink (文件接收器)Append (附加)path: 必须指定输出目录的路径。

    有关特定于文件格式的选项,请参阅 DataFrameWriter (Scala/Java/Python/R) 中的相关方法。 例如,对于 "parquet" 格式选项,请参阅DataFrameWriter.parquet()Yes支持对 partitioned tables (分区表)的写入。按时间 Partitioning (划分)可能是有用的。

    Foreach SinkAppend, Update, Compelete (附加,更新,完全)None取决于 ForeachWriter 的实现。更多详细信息在下一节

    Console Sink (控制台接收器)Append, Update, Complete (附加,更新,完全)numRows: 每个触发器需要打印的行数(默认:20)

    truncate: 如果输出太长是否截断(默认: true)No

    Memory Sink (内存接收器)Append, Complete (附加,完全)None否。但是在 Complete Mode 模式下,重新启动的查询将重新创建完整的表。Table name is the query name.(表名是查询的名称)

    请注意,您必须调用start()来实际启动查询的执行。 这将返回一个 StreamingQuery 对象,它是连续运行的执行的句柄。 您可以使用此对象来管理查询,我们将在下一小节中讨论。 现在,让我们通过几个例子了解所有这些。

    Scala

    Java

    Python

    R

    // ========== DF with no aggregations ==========valnoAggDF=deviceDataDf.select("device").where("signal > 10")// Print new data to consolenoAggDF.writeStream.format("console").start()// Write new data to Parquet filesnoAggDF.writeStream.format("parquet").option("checkpointLocation","path/to/checkpoint/dir").option("path","path/to/destination/dir").start()// ========== DF with aggregation ==========valaggDF=df.groupBy("device").count()// Print updated aggregations to consoleaggDF.writeStream.outputMode("complete").format("console").start()// Have all the aggregates in an in-memory tableaggDF.writeStream.queryName("aggregates")// this query name will be the table name.outputMode("complete").format("memory").start()spark.sql("select * from aggregates").show()// interactively query in-memory table

    使用 Foreach

    foreach操作允许在输出数据上计算 arbitrary operations 。从 Spark 2.1 开始,这只适用于 Scala 和 Java 。为了使用这个,你必须实现接口ForeachWriter(Scala/Java文档) 其具有在 trigger (触发器)之后生成 sequence of rows generated as output (作为输出的行的序列)时被调用的方法。请注意以下要点。

    writer 必须是 serializable (可序列化)的,因为它将被序列化并发送给 executors 执行。

    所有这三个方法,open,process和close都会在执行器上被调用。

    只有当调用open方法时,writer 才能执行所有的初始化(例如打开连接,启动事务等)。请注意,如果在创建对象时立即在类中进行任何初始化,那么该初始化将在 driver 中发生(因为这是正在创建的实例),这可能不是您打算的。

    version和partition是open中的两个参数,它们独特地表示一组需要被 pushed out 的行。version是每个触发器增加的单调递增的 id 。partition是一个表示输出分区的 id ,因为输出是分布式的,将在多个执行器上处理。

    open可以使用version和partition来选择是否需要写入行的顺序。因此,它可以返回true(继续写入)或false( 不需要写入 )。如果返回false,那么process不会在任何行上被调用。例如,在 partial failure (部分失败)之后,失败的触发器的一些输出分区可能已经被提交到数据库。基于存储在数据库中的 metadata (元数据), writer 可以识别已经提交的分区,因此返回 false 以跳过再次提交它们。

    open被调用时,close也将被调用(除非 JVM 由于某些错误而退出)。即使open返回 false 也是如此。如果在处理和写入数据时出现任何错误,那么close将被错误地调用。您有责任清理以open创建的状态(例如,连接,事务等),以免资源泄漏。

    管理 Streaming Queries

    在启动查询时创建的StreamingQuery对象可用于 monitor and manage the query (监视和管理查询)。

    Scala

    Java

    Python

    R

    valquery=df.writeStream.format("console").start()// get the query objectquery.id// get the unique identifier of the running query that persists across restarts from checkpoint dataquery.runId// get the unique id of this run of the query, which will be generated at every start/restartquery.name// get the name of the auto-generated or user-specified namequery.explain()// print detailed explanations of the queryquery.stop()// stop the queryquery.awaitTermination()// block until query is terminated, with stop() or with errorquery.exception// the exception if the query has been terminated with errorquery.recentProgress// an array of the most recent progress updates for this queryquery.lastProgress// the most recent progress update of this streaming query

    您可以在单个 SparkSession 中启动任意数量的查询。 他们都将同时运行共享集群资源。 您可以使用sparkSession.streams()获取StreamingQueryManager(Scala/Java/Python文档) 可用于管理 currently active queries (当前活动的查询)。

    Scala

    Java

    Python

    R

    valspark:SparkSession=...spark.streams.active// get the list of currently active streaming queriesspark.streams.get(id)// get a query object by its unique idspark.streams.awaitAnyTermination()// block until any one of them terminates

    监控 Streaming Queries

    有两个用于 monitoring and debugging active queries (监视和调试活动查询) 的 API - interactively 和 asynchronously 。

    Interactive APIs

    您可以直接获取活动查询的当前状态和指标使用streamingQuery.lastProgress()和streamingQuery.status()。lastProgress()返回一个StreamingQueryProgress对象 在ScalaJava Python 中具有相同字段的字典。它有所有的信息在 stream 的最后一个触发器中取得的 progress - 处理了哪些数据,处理率是多少,延迟等等。streamingQuery.recentProgress返回最后几个进度的 array 。

    另外,streamingQuery.status()返回一个StreamingQueryStatus对象在ScalaJava Python 中具有相同字段的字典。它提供有关的信息立即执行的查询 - 触发器是否 active ,数据是否正在处理等。

    这里有几个例子。

    Scala

    Java

    Python

    R

    valquery:StreamingQuery=...println(query.lastProgress)/* Will print something like the following.{"id" : "ce011fdc-8762-4dcb-84eb-a77333e28109","runId" : "88e2ff94-ede0-45a8-b687-6316fbef529a","name" : "MyQuery","timestamp" : "2016-12-14T18:45:24.873Z","numInputRows" : 10,"inputRowsPerSecond" : 120.0,"processedRowsPerSecond" : 200.0,"durationMs" : {"triggerExecution" : 3,"getOffset" : 2},"eventTime" : {"watermark" : "2016-12-14T18:45:24.873Z"},"stateOperators" : [ ],"sources" : [ {"description" : "KafkaSource[Subscribe[topic-0]]","startOffset" : {"topic-0" : {"2" : 0,"4" : 1,"1" : 1,"3" : 1,"0" : 1}},"endOffset" : {"topic-0" : {"2" : 0,"4" : 115,"1" : 134,"3" : 21,"0" : 534}},"numInputRows" : 10,"inputRowsPerSecond" : 120.0,"processedRowsPerSecond" : 200.0} ],"sink" : {"description" : "MemorySink"}}*/println(query.status)/*  Will print something like the following.{"message" : "Waiting for data to arrive","isDataAvailable" : false,"isTriggerActive" : false}*/

    Asynchronous API

    您还可以 asynchronously monitor (异步监视)与SparkSession相关联的所有查询 通过附加一个StreamingQueryListener(Scala/Javadocs) 。一旦你使用sparkSession.streams.attachListener()附加你的自定义StreamingQueryListener对象,当您启动查询和当有活动查询有进度时停止时,您将收到 callbacks (回调)。 这是一个例子,

    Scala

    Java

    Python

    R

    valspark:SparkSession=...spark.streams.addListener(newStreamingQueryListener(){overridedefonQueryStarted(queryStarted:QueryStartedEvent):Unit={println("Query started: "+queryStarted.id)}overridedefonQueryTerminated(queryTerminated:QueryTerminatedEvent):Unit={println("Query terminated: "+queryTerminated.id)}overridedefonQueryProgress(queryProgress:QueryProgressEvent):Unit={println("Query made progress: "+queryProgress.progress)}})

    Recovering from Failures with Checkpointing (从检查点恢复故障)

    如果发生 failure or intentional shutdown (故障或故意关机),您可以恢复之前的查询的进度和状态,并继续停止的位置。 这是使用 checkpointing and write ahead logs (检查点和预写入日志)来完成的。 您可以使用 checkpoint location (检查点位置)配置查询,并且查询将保存所有进度信息(即,每个触发器中处理的偏移范围)和正在运行的 aggregates (聚合)(例如quick example中的 woed counts ) 到 checkpoint location (检查点位置)。 此检查点位置必须是 HDFS 兼容文件系统中的路径,并且可以在starting a query时将其设置为DataStreamWriter 中的选项。

    Scala

    Java

    Python

    R

    aggDF.writeStream.outputMode("complete").option("checkpointLocation","path/to/HDFS/dir").format("memory").start()

    从这里去哪儿

    示例: 查看并运行Scala/Java/Python/R示例。

    Spark Summit 2016 Talk -深入 Structured Streaming

    我们一直在努力

    apachecn/spark-doc-zh

     

     

    原文地址: http://spark.apachecn.org/docs/cn/2.2.0/structured-streaming-programming-guide.html

    网页地址: http://spark.apachecn.org/

    github: https://github.com/apachecn/spark-doc-zh(觉得不错麻烦给个 Star,谢谢!~)

     

     

     

     

     

     

     

     

     

     

     

    Apache Spark 2.2.0 中文文档 - Submitting Applications | ApacheCN

    Geekhoo 关注

    2017.09.27 17:44* 字数 2564 阅读 15评论 0喜欢 1

    Submitting Applications

    script in Spark的bin目录中的spark-submit脚本用与在集群上启动应用程序。它可以通过一个统一的接口使用所有 Spark 支持的cluster managers,所以您不需要专门的为每个cluster managers配置您的应用程序。

    打包应用依赖

    如果您的代码依赖了其它的项目,为了分发代码到 Spark 集群中您将需要将它们和您的应用程序一起打包。为此,创建一个包含您的代码以及依赖的 assembly jar(或者 “uber” jar)。无论是sbt还是Maven都有 assembly 插件。在创建 assembly jar 时,列出 Spark 和 Hadoop的依赖为provided。它们不需要被打包,因为在运行时它们已经被 Cluster Manager 提供了。如果您有一个 assembled jar 您就可以调用bin/spark-submit脚本(如下所示)来传递您的 jar。

    对于 Python 来说,您可以使用spark-submit的--py-files参数来添加.py,.zip和.egg文件以与您的应用程序一起分发。如果您依赖了多个 Python 文件我们推荐将它们打包成一个.zip或者.egg文件。

    spark-submit 启动应用

    如果用户的应用程序被打包好了,它可以使用bin/spark-submit脚本来启动。这个脚本负责设置 Spark 和它的依赖的 classpath,并且可以支持 Spark 所支持的不同的 Cluster Manager 以及 deploy mode(部署模式):

    ./bin/spark-submit--class --master --deploy-mode --conf =...# other options[application-arguments]

    一些常用的 options(选项)有 :

    --class: 您的应用程序的入口点(例如。org.apache.spark.examples.SparkPi)

    --master: 集群的master URL(例如spark://23.195.26.187:7077)

    --deploy-mode: 是在 worker 节点(cluster) 上还是在本地作为一个外部的客户端(client) 部署您的 driver(默认:client)†

    --conf: 按照 key=value 格式任意的 Spark 配置属性。对于包含空格的 value(值)使用引号包 “key=value” 起来。

    application-jar: 包括您的应用以及所有依赖的一个打包的 Jar 的路径。该 URL 在您的集群上必须是全局可见的,例如,一个hdfs://path 或者一个file://在所有节点是可见的。

    application-arguments: 传递到您的 main class 的 main 方法的参数,如果有的话。

    †常见的部署策略是从一台 gateway 机器物理位置与您 worker 在一起的机器(比如,在 standalone EC2 集群中的 Master 节点上)来提交您的应用。在这种设置中,client模式是合适的。在client模式中,driver 直接运行在一个充当集群 client 的spark-submit进程内。应用程序的输入和输出直接连到控制台。因此,这个模式特别适合那些设计 REPL(例如,Spark shell)的应用程序。

    另外,如果您从一台远离 worker 机器的机器(例如,本地的笔记本电脑上)提交应用程序,通常使用cluster模式来降低 driver 和 executor 之间的延迟。目前,Standalone 模式不支持 Cluster 模式的 Python 应用。

    对于 Python 应用,在的位置简单的传递一个.py文件而不是一个 JAR,并且可以用--py-files添加 Python.zip,.egg或者.py文件到 search path(搜索路径)。

    这里有一些选项可用于特定的cluster manager中。例如,Spark standalone clustercluster部署模式, 您也可以指定--supervise来确保 driver 在 non-zero exit code 失败时可以自动重启。为了列出所有spark-submit, 可用的选项,用--help. 来运行它。这里是一些常见选项的例子 :

    # Run application locally on 8 cores./bin/spark-submit--class org.apache.spark.examples.SparkPi--master local[8]/path/to/examples.jar100# Run on a Spark standalone cluster in client deploy mode./bin/spark-submit--class org.apache.spark.examples.SparkPi--master spark://207.184.161.138:7077--executor-memory 20G--total-executor-cores100/path/to/examples.jar1000# Run on a Spark standalone cluster in cluster deploy mode with supervise./bin/spark-submit--class org.apache.spark.examples.SparkPi--master spark://207.184.161.138:7077--deploy-mode cluster--supervise--executor-memory 20G--total-executor-cores100/path/to/examples.jar1000# Run on a YARN clusterexportHADOOP_CONF_DIR=XXX./bin/spark-submit--class org.apache.spark.examples.SparkPi--master yarn--deploy-mode cluster# can be client for client mode--executor-memory 20G--num-executors50/path/to/examples.jar1000# Run a Python application on a Spark standalone cluster./bin/spark-submit--master spark://207.184.161.138:7077examples/src/main/python/pi.py1000# Run on a Mesos cluster in cluster deploy mode with supervise./bin/spark-submit--class org.apache.spark.examples.SparkPi--master mesos://207.184.161.138:7077--deploy-mode cluster--supervise--executor-memory 20G--total-executor-cores100http://path/to/examples.jar1000

    Master URLs

    传递给 Spark 的 master URL 可以使用下列格式中的一种 :

    Master URLMeaning

    local使用一个线程本地运行 Spark(即,没有并行性)。

    local[K]使用 K 个 worker 线程本地运行 Spark(理想情况下,设置这个值的数量为您机器的 core 数量)。

    local[K,F]使用 K 个 worker 线程本地运行 Spark并允许最多失败 F次 (查阅spark.task.maxFailures以获取对该变量的解释)

    local[*]使用更多的 worker 线程作为逻辑的 core 在您的机器上来本地的运行 Spark。

    local[*,F]使用更多的 worker 线程作为逻辑的 core 在您的机器上来本地的运行 Spark并允许最多失败 F次。

    spark://HOST:PORT连接至给定的Spark standalone clustermaster. master。该 port(端口)必须有一个作为您的 master 配置来使用,默认是 7077。

    spark://HOST1:PORT1,HOST2:PORT2连接至给定的Spark standalone cluster with standby masters with Zookeeper. 该列表必须包含由zookeeper设置的高可用集群中的所有master主机。该 port(端口)必须有一个作为您的 master 配置来使用,默认是 7077。

    mesos://HOST:PORT连接至给定的Mesos集群. 该 port(端口)必须有一个作为您的配置来使用,默认是 5050。或者,对于使用了 ZooKeeper 的 Mesos cluster 来说,使用mesos://zk://.... 。使用--deploy-mode cluster, 来提交,该 HOST:PORT 应该被配置以连接到MesosClusterDispatcher.

    yarn连接至一个YARNcluster inclientorclustermode 取决于--deploy-mode. 的值在 client 或者 cluster 模式中。该 cluster 的位置将根据HADOOP_CONF_DIR或者YARN_CONF_DIR变量来找到。

    从文件中加载配置

    spark-submit脚本可以从一个 properties 文件加载默认的Spark configuration values并且传递它们到您的应用中去。默认情况下,它将从 Spark 目录下的conf/spark-defaults.conf读取配置。更多详细信息,请看加载默认配置.

    加载默认的 Spark 配置,这种方式可以消除某些标记到spark-submit. 的必要性。例如,如果spark.master属性被设置了,您可以在spark-submit中安全的省略--master配置 . 一般情况下,明确设置在SparkConf上的配置值的优先级最高,然后是传递给spark-submit的值, 最后才是 default value(默认文件)中的值。

    如果您不是很清楚其中的配置设置来自哪里,您可以通过使用--verbose选项来运行spark-submit打印出细粒度的调试信息。

    高级的依赖管理

    在使用spark-submit时,使用--jars选项包括的应用程序的 jar 和任何其它的 jar 都将被自动的传输到集群。在--jars后面提供的 URL 必须用逗号分隔。该列表会被包含到 driver 和 executor 的 classpath 中。--jars不支持目录的形式。

    Spark 使用下面的 URL 格式以允许传播 jar 时使用不同的策略 :

    file:- 绝对路径和file:/URI 通过 driver 的 HTTP file server 提供服务,并且每个 executor 会从 driver 的 HTTP server 拉取这些文件。

    hdfs:,http:,https:,ftp:- 如预期的一样拉取下载文件和 JAR

    local:- 一个用 local:/ 开头的 URL 预期作在每个 worker 节点上作为一个本地文件存在。这样意味着没有网络 IO 发生,并且非常适用于那些已经被推送到每个 worker 或通过 NFS,GlusterFS等共享的大型的 file/JAR。

    N注意,那些 JAR 和文件被复制到 working directory(工作目录)用于在 executor 节点上的每个 SparkContext。这可以使用最多的空间显著量随着时间的推移,将需要清理。在 Spark On YARN 模式中,自动执行清理操作。在 Spark standalone 模式中,可以通过配置spark.worker.cleanup.appDataTtl属性来执行自动清理。

    用户也可以通过使用--packages来提供一个逗号分隔的 maven coordinates(maven 坐标)以包含任何其它的依赖。在使用这个命令时所有可传递的依赖将被处理。其它的 repository(或者在 SBT 中被解析的)可以使用--repositories该标记添加到一个逗号分隔的样式中。 (注意,对于那些设置了密码保护的库,在一些情况下可以在库URL中提供验证信息,例如https://user:password@host/....以这种方式提供验证信息需要小心。) 这些命令可以与pyspark,spark-shell和spark-submit配置会使用以包含 Spark Packages(Spark 包)。 对于 Python 来说,也可以使用--py-files选项用于分发.egg,.zip和.pylibraries 到 executor 中。

    # 更多信息

    如果您已经部署了您的应用程序,集群模式概述描述了在分布式执行中涉及到的组件,以及如何去监控和调试应用程序。

    我们一直在努力

    apachecn/spark-doc-zh

     

     

    原文地址: http://spark.apachecn.org/docs/cn/2.2.0/submitting-applications.html

    网页地址: http://spark.apachecn.org/

    github: https://github.com/apachecn/spark-doc-zh(觉得不错麻烦给个 Star,谢谢!~)

  • 相关阅读:
    Cookie
    laydate
    layer
    字符流
    java虚拟机学习(四)--垃圾收集算法
    java虚拟机学习(三)
    java虚拟机学习(二)
    java虚拟机学习(一)
    Mybatis学习(一)
    mysql数据库面试总结(一)
  • 原文地址:https://www.cnblogs.com/think90/p/8257031.html
Copyright © 2020-2023  润新知