spark 基础和spark sql翻译

3 篇文章 0 订阅
1 篇文章 0 订阅

1、 Spark 安装

1.1 编译Spark 1.3.0

下载spark时,如果存在自己hadoop版本对应的pre-built版,可以直接下载编译好的版本。由于集群hive版本不匹配预编译版本Spark支持的hive版本,需要重新编译。

下载Spark1.3.0 源码:
https://spark.apache.org/downloads.html

本文使用maven进行编译,编译时首先执行命令:

export MAVEN_OPTS=”-Xmx2g -XX:MaxPermSize=512M -XX:ReservedCodeCacheSize=512m”

执行上述命令,避免在编译过程中内存不足错误。

然后执行以下命令:

mvn -Pyarn -Phadoop-2.3 -Dhadoop.version=2.3.0 -Phive -Phive-0.12.0 -Phive-thriftserver -DskipTests clean package

Pyarn 代表支持yarn,Phadoop 指定hadoop版本,Phive 指定hive版本。

构建Spark详情可参考官网:
https://spark.apache.org/docs/latest/building-spark.html

编译时间大概为两个小时,具体根据网络情况而定。

  maven编译完成后,spark_HOME/assembly/target/scala-2.10目录下生成spark-assembly-1.3.0-hadoop2.3.0.jar包。

  我们只需将刚才构建的spark-assembly-1.3.0-hadoop2.3.0.jar文件替换掉pre-built for hadoop2.3版本目录下lib中的相同文件。此时pre-built for hadoop2.3就是我们需要的spark for hadoop2.3 and hive0.12.0。

  此外,执行下面命令也可以重新构建安装包。
make-distribution.sh –tgz –skip-java-test -Dyarn.version=2.3.0 -Dhadoop.version=2.3.0 -Pyarn -Phive -Phive-thriftserver

  命令执行后,会在spark_HOME目录下生成spark-1.3.0-bin-*.tgz安装包。

1.2 Spark 1.3.0 配置

首先,将1.1节构建的Spark1.3.0安装包分发到需要部署的机器上,我的目录为:/home/wangchao/,然后解压文件

之后,将master机器和slave之间ssh设置为无密码登录,参考:http://haitao.iteye.com/blog/1744272

配置SPARK

修改Spark-env.sh文件:

export JAVA_HOME=/usr/java/jdk1.7.0_55-cloudera
export SCALA_HOME=/path/scala-2.10.5
export HADOOP_CONF_DIR=/etc/hadoop/conf
export HIVE_CONF_DIR=/etc/hive/conf
export SPARK_EXECUTOR_INSTANCES=12
export SPARK_EXECUTOR_CORES=6
export SPARK_EXECUTOR_MEMORY=4G
export SPARK_DRIVER_MEMORY=512M
export SPARK_YARN_APP_NAME="spark-130"
export SPARK_LIBRARY_PATH=${SPARK_HOME}/lib

上面的配置是针对spark on yarn的client模式下起作用。

如果使用的是Spark on Standalone模式,修改Spark-env.sh文件为:

export JAVA_HOME=/usr/java/jdk1.7.0_55-cloudera
export SCALA_HOME=/home/wangchao2/scala-2.10.5
export HADOOP_CONF_DIR=/etc/hadoop/conf
export HIVE_CONF_DIR=/etc/hive/conf
export SPARK_MASTER_IP=web393
export SPARK_MASTER_PORT=7077
export SPARK_MASTER_WEBUI_PORT=18080
export SPARK_WORKER_MEMORY=3G
export SPARK_WORKER_CORES=3
export SPARK_WORKER_WEBUI_PORT=8081
export SPARK_WORKER_INSTANCES=2
export SPARK_DAEMON_MEMORY=1G
export SPARK_WORKER_DIR=/home/cdh-log/spark
export SPARK_LIBRARY_PATH=${SPARK_HOME}/lib:/opt/cloudera/parcels/CDH/lib/hbase/lib/
export SPARK_PID_DIR=${SPARK_HOME}/pid

修改spark-defaults.conf文件

spark.masterspark://web393:7077
spark.app.name  spark-standalone
park.scheduler.mode FAIR
spark.akka.frameSize30
spark.storage.memoryFraction0.5
spark.cores.max 20
spark.driver.memory 3G
spark.driver.maxResultSize  6G
spark.eventLog.compress true
spark.executor.memory   2G
spark.eventLog.dir  hdfs:///user/spark/applicationHistory
spark.eventLog.enabled  true
spark.ui.killEnabledtrue
spark.ui.port   4040
spark.local.dir /tmp/spark
spark.akka.timeout  200
spark.default.parallelism   400

修改slaves文件,添加slaves节点

web194
web195
web196
web197
web200

集群中每台机器配置文件根据机器情况进行配置,可以不同,尤其是cores分配和内存分配根据机器具体情况。spark-env.sh配置对每台机器起作用,spark-defaults.conf配置对整个集群起作用。

1.3 Spark 启动与测试

Spark on Standalone

进入spark_home/sbin目录下,执行start-all.sh。

显示如下,则启动成功.也可以进入相应机器执行jps命令,可以看到worker或者master进程。

starting org.apache.spark.deploy.master.Master, logging to ......
web200: starting org.apache.spark.deploy.worker.Worker, logging to ......
web194: starting org.apache.spark.deploy.worker.Worker, logging to ......
web196: starting org.apache.spark.deploy.worker.Worker, logging to ......
web195: starting org.apache.spark.deploy.worker.Worker, logging to ......
web197: starting org.apache.spark.deploy.worker.Worker, logging to ......
web200: starting org.apache.spark.deploy.worker.Worker, logging to ......
web194: starting org.apache.spark.deploy.worker.Worker, logging to ......
web196: starting org.apache.spark.deploy.worker.Worker, logging to ......
web197: starting org.apache.spark.deploy.worker.Worker, logging to ......
web195: starting org.apache.spark.deploy.worker.Worker, logging to ......

启动spark-shell,测试集群编程环境。

命令为:

./spark-shell \
  --master spark://web393:7077

启动后便进入到了scala编程界面。

读取hdfs数据测试:

val textfile = sc.textFile("HDFSPath/file",30).flatMap(x=>x.split("\t"))
textfile.take(10)

执行上面命令后,如果显示结果,则成功。

spark执行相关情况,可进入 masterip:18080网页进行查看。

Spark on yarn

spark on yarn的执行不需要执行spark_home/sbin/start-all.sh命令,只需要机器中的hadoop集群相关模块正在运行即可。

启动基于yarn的spark-shell,测试集群编程环境。

注:如果使用spark on yarn,spark-env.sh的设置需要根据yarn方式下进行设置,否则不会生效。

命令为:
./spark-shell \
–master yarn-client

–master指定spark执行方式,spark on yarn 启动相比standalone慢些,因为机器资源需要yarn重新进行分配,而standalone是在已经启动的集群中分配一部分资源。启动后同样进入到了scala编程界面。

读取hdfs数据测试:

val textfile = sc.textFile("HDFSPath/file",30).flatMap(x=>x.split("\t"))
textfile.take(10)

执行上面命令后,如果显示结果,则成功。

spark执行相关情况,需要进入到yarn管理界面,点击running,查看正在执行spark日志。

如需了解更多spark-shell参数,执行spark-shell –help 即可。

2、 Spark 运行架构

2.1 Spark 基本术语

Application:用户在spark构建的程序,包括一个驱动程序和集群上的executors

Application jar:包含用户spark应用程序的jar包,不包含hadoop和spark库

Driver program:运行应用程序的main()函数和创建sparkcontext的进程

Cluster manager:一个外部服务用来请求集群资源(yarn,mesos,standalone)

Deploy mode:区别驱动程序在哪运行。在“cluster”模式下,framework发送驱动在集群内部,在“client”模式下,the submitter发送驱动在集群外部。

Worker node:集群中能运行应用程序代码的结点

Executor:worker结点上的一个进程。该进程负责运行tasks,并负责将数据存在内存或者磁盘上。每一个application都有自己独立的executors。

Task:发送到一个executor上执行的单元

Job:有多个任务组成的并发计算,一般有spark action触发

Stage:每一个作业划分为多个小的任务的集合,任务的集合称为stage,阶段之间相互依赖。

2.2 Spark 运行架构

原理图如下:


Spark应用程序在集群上运行多个独立的进程,通过主程序中的sparkcontext对象来协调。具体的说,为了在集群上运行spark,sparkcontext会连接多种类型的cluster managers,像yarn、mesos、standalone,它们会分配资源给应用程序。一旦连接建立,spark会请求集群结点分配executors,executors为你的应用程序运行计算和存储数据。接下来,sparkcontext发送应用程序代码(定义为JAR或者python文件交给sparkcontext)给executors。最后,sparkcontext发送tasks给executors来运行。

关于上面架构描述:

  1. 每一个应用程序拥有自己的executor processes,在程序运行期间一直占用,同时多线程方式运行任务。应用程序之间相互隔离有以下好处:在调度方面,每一个驱动调度自己的任务;在executors方面,不同的应用程序运行在不同的JVM中。然而,它也意味着不同spark应用程序(sparkcontext实例)不能共享数据。
  2. Spark不关心使用哪种cluster manager。只要能够获得 executor processes,它们之间可以相互通信,运行在一个cluster manager是相对容易的。
  3. driver program在整个生命周期必须监听和接收来自executors的传入连接。这样, driver program必须网络可寻址从 worker nodes。
  4. 因为the driver在集群上调度tasks,所有它应该靠近 the worker nodes,在本地相同网络最好。如果你发送请求到远程的集群,最好打开一个RPC到driver,让它提交操作从附近而不是使用一个远离worker nodes的driver。

3、Spark 编程向导

3.1 引入Spark

Spark 1.3.0 使用scala 2.10。如果使用scala写应用程序,需要使用一个兼容的版本(2.10.X)

为了写Spark应用程序,你需要添加一个Maven依赖,Spark可以在Maven 中心仓库获得。

groupId = org.apache.spark
artifactId = spark-core_2.10
version = 1.3.0

除此之外,如果你希望加入HDFS集群,你需要添加hadoop-client依赖对应你的HDFS版本。

groupId = org.apache.hadoop
artifactId = hadoop-client
version = <your-hdfs-version>

最后,你需要在程序中导入Spark类。

import org.apache.spark.api.java.JavaSparkContext
import org.apache.spark.api.java.JavaRDD
import org.apache.spark.SparkConf

Spark1.3.0 可以使用Python2.6或更高版本。它使用标准的CPython解释器,所以C库像numpy可以使用。

为了运行Spark应用程序用Pyrhon,使用bin/spark-submit脚本。这个脚本将会加载Spark的java/scala库,允许你提交应用程序到集群。你也可以使用 bin/pyspark 运行一个交互式的Python shell。

如果你希望访问HDFS数据,你需要使用一个PySpark 建立链接到你的版本HDFS。一些通用HDFS版本标签在 third party distributions列出,Prebuilt packages可以在Spark主页获取对应通常的HDFS版本。

最后你需要导入Spark类。

from pyspark import SparkContext, SparkConf

3.2 初始化Spark

Spark 编程的第一步是需要创建一个 SparkContext 对象,用来告诉 Spark 如何访问集群。在创建 SparkContext 之前,你需要构建一个 SparkConf 对象, SparkConf 对象包含了一些你应用程序的信息。

在每一个JVM中仅有一个SparkContext是活动的,在创建一个新的SparkContext 时必须将active SparkContext进行stop。

scala版

val conf = new SparkConf().setAppName(appName).setMaster(master)
new SparkContext(conf)

java版

SparkConf conf = new SparkConf().setAppName(appName).setMaster(master);
JavaSparkContext sc = new JavaSparkContext(conf);

python版

conf = SparkConf().setAppName(appName).setMaster(master)
sc = SparkContext(conf=conf)

appName 参数是你程序的名字,它会显示在 cluster UI 上。 master 是 Spark, Mesos 或 YARN 集群的 URL,或运行在本地模式时,使用专用字符串 “local”。在实践中,当应用程序运行在一个集群上时,你并不想要把 master 硬编码到你的程序中,你可以用 spark-submit 启动你的应用程序的时候传递它。然而,你可以在本地测试和单元测试中使用 “local” 运行 Spark进程。

3.3 使用Shell

在 Spark shell 中,有一个专有的 SparkContext 已经为你创建好。在变量中叫做 sc 。你自己创建的 SparkContext 将无法工作。可以用 –master 参数来设置 SparkContext 要连接的集群,用 –jars 来设置需要添加到 classpath 中的 JAR 包,如果有多个 JAR 包使用逗号分割符连接它们。你也可以增加依赖到shell使用–packages参数,指定maven坐标。一些额外依赖的repositories 可以使用–repositories参数设置。例如:在一个拥有 4 核的环境上运行 bin/spark-shell ,使用:

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

或在 classpath 中添加 code.jar ,使用:

$ ./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 查看完整选项列表。

在 PySpark shell 中,有一个专有的 SparkContext 已经为你创建好。在变量中叫做 sc 。你自己创建的 SparkContext 将无法工作。可以用 –master 参数来设置 SparkContext 要连接的集群,用 –py-files 来设置需要添加到 classpath 中的 Python .zip, .egg or .py files。你也可以增加依赖到shell使用–packages参数,指定maven坐标。一些额外依赖的repositories 可以使用–repositories参数设置。任何python依赖包(spark要求)必须手动安装使用Pip。例如:在一个拥有 4 核的环境上运行 bin/pyspark-shell ,使用:

$ ./bin/pyspark --master local[4]

或者添加code.py到搜索路径,使用:

$ ./bin/pyspark --master local[4] --py-files code.py

可以使用pyspark-shell –help 查看完整选项列表。

3.4 Resilient Distributed Datasets (RDDs)

Spark 核心的概念是 Resilient Distributed Dataset (RDD):一个可并行操作的有容错机制的数据集合。有 2 种方式创建RDDs:第一种是在你的驱动程序中并行化一个已经存在的集合;另外一种是引用一个外部存储系统的数据集,例如共享的文件系统,HDFS,HBase或其他 Hadoop 数据格式的数据源。

3.4.1 并行集合

并行集合 (Parallelized collections) 的创建是通过在一个已有的集合(Scala Seq )上调用 SparkContext 的 parallelize 方法实现的。集合中的元素被复制到一个可并行操作的分布式数据集中。例如,这里演示了如何在一个包含 1 到 5 的数组中创建并行集合:

scala版

val data = Array(1, 2, 3, 4, 5)
val distData = sc.parallelize(data)

java版

List<Integer> data = Arrays.asList(1, 2, 3, 4, 5);
JavaRDD<Integer> distData = sc.parallelize(data);

python版

data = [1, 2, 3, 4, 5]
distData = sc.parallelize(data)

一旦创建完成,这个分布式数据集( distData )就可以被并行操作。例如,我们可以调用 distData.reduce((a, b) => a + b)将这个数组中的元素相加。我们以后再描述在分布式上的一些操作。

并行集合一个很重要的参数是切片数(slices),表示一个数据集切分的份数。Spark 会在集群上为每一个切片运行一个任务。你可以在集群上为每个 CPU 设置 2-4 个切片(slices)。正常情况下,Spark 会试着基于你的集群状况自动地设置切片的数目。然而,你也可以通过 parallelize 的第二个参数手动地设置(例如: sc.parallelize(data, 10) )。

3.4.2 External Datasets

Spark 可以从任何一个 Hadoop 支持的存储源创建分布式数据集,包括你的本地文件系统,HDFS,Cassandra,HBase,Amazon S3等。 Spark 支持文本文件(text files),SequenceFiles 和其他 Hadoop InputFormat。

文本文件 RDDs 可以使用 SparkContext 的 textFile 方法创建。 在这个方法里传入文件的 URI (机器上的本地路径或hdfs:// , s3n:// 等),然后它会将文件读取成一个行集合。这里是一个调用例子:

scala版本

scala> val distFile = sc.textFile("data.txt")

java版本

JavaRDD<String> distFile = sc.textFile("data.txt");

python版本

distFile = sc.textFile("data.txt")

一旦创建完成, distFiile 就能做数据集操作。例如,我们可以用下面的方式使用 map 和 reduce 操作将所有行的长度相加: distFile.map(s => s.length).reduce((a, b) => a + b) 。

注意,Spark 读文件时:

  • 如果使用本地文件系统路径,文件必须能在 work 节点上用相同的路径访问到。要么复制文件到所有的 workers,要么使用网络的方式共享文件系统。
  • 所有 Spark 的基于文件的方法,包括 textFile ,能很好地支持文件目录,压缩过的文件和通配符。例如,你可以使用textFile(“/my/文件目录”) , textFile(“/my/文件目录/.txt”) 和 textFile(“/my/文件目录/.gz”) 。
  • textFile 方法也可以选择第二个可选参数来控制切片(slices)的数目。默认情况下,Spark 为每一个文件块(HDFS 默认文件块大小是 64M)创建一个切片(slice)。但是你也可以通过一个更大的值来设置一个更高的切片数目。注意,你不能设置一个小于文件块数目的切片值。

除了文本文件,Spark 的 Scala API 支持其他几种数据格式:

  • SparkContext.wholeTextFiles 让你读取一个包含多个小文本文件的文件目录并且返回每一个(filename, content)对。与textFile 的差异是:它记录的是每个文件中的每一行。
  • 对于 SequenceFiles,可以使用 SparkContext 的 sequenceFile[K, V] 方法创建,K 和 V 分别对应的是 key 和 values的类型。像 IntWritable 与 Text 一样,它们必须是 Hadoop 的 Writable 接口的子类。另外,对于几种通用的 Writables,Spark 允许你指定原声类型来替代。例如: sequenceFile[Int, String] 将会自动读取 IntWritables 和 Text。
  • 对于其他的 Hadoop InputFormats,你可以使用 SparkContext.hadoopRDD 方法,它可以指定任意的 JobConf ,输入格式(InputFormat),key 类型,values 类型。你可以跟设置 Hadoop job 一样的方法设置输入源。你还可以在新的MapReduce 接口(org.apache.hadoop.mapreduce)基础上使用 SparkContext.newAPIHadoopRDD。
  • RDD.saveAsObjectFile 和 SparkContext.objectFile 支持保存一个RDD,保存格式是一个简单的 Java 对象序列化格式。这是一种效率不高的专有格式,如 Avro,它提供了简单的方法来保存任何一个 RDD。

3.5 RDD操作

RDDs 支持 2 种类型的操作:转换(transformations) 从已经存在的数据集中创建一个新的数据集;动作(actions) 在数据集上进行计算之后返回一个值到驱动程序。例如, map 是一个转换操作,它将每一个数据集元素传递给一个函数并且返回一个新的 RDD。另一方面, reduce 是一个动作,它使用相同的函数来聚合 RDD 的所有元素,并且将最终的结果返回到驱动程序(也有一个并行 reduceByKey 能返回一个分布式数据集)。

在 Spark 中,所有的转换(transformations)都是惰性(lazy)的,它们不会马上计算它们的结果。相反的,它们仅仅记录转换操作是应用到哪些基础数据集(例如一个文件)上的。转换仅仅在这个时候计算:当动作(action) 需要一个结果返回给驱动程序的时候。这个设计能够让 Spark 运行得更加高效。例如,我们可以实现:通过 map 创建一个新数据集在 reduce 中使用,并且仅仅返回 reduce 的结果给 driver,而不是整个大的映射过的数据集。

默认情况下,每一个转换过的 RDD 会在每次执行动作(action)的时候重新计算一次。然而,你也可以使用 persist (或cache )方法持久化( persist )一个 RDD 到内存中。在这个情况下,Spark 会在集群上保存相关的元素,在你下次查询的时候会变得更快。在这里也同样支持持久化 RDD 到磁盘,或在多个节点间复制。

3.5.1 基础

为了说明 RDD 基本知识,考虑下面的简单程序:

scala版本

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

第一行是定义来自于外部文件的 RDD。这个数据集并没有加载到内存或做其他的操作: lines 仅仅是一个指向文件的指针。第二行是定义 lineLengths ,它是 map 转换(transformation)的结果。同样, lineLengths 由于懒惰模式也没有立即计算。最后,我们执行 reduce ,它是一个动作(action)。在这个地方,Spark 把计算分成多个任务(task),并且让它们运行在多个机器上。每台机器都运行自己的 map 部分和本地 reduce 部分。然后仅仅将结果返回给驱动程序。

如果我们想要再次使用 lineLengths ,我们可以添加:

lineLengths.persist()

在 reduce 之前,它会导致 lineLengths 在第一次计算完成之后保存到内存中。

java版本

JavaRDD<String> lines = sc.textFile("data.txt");
JavaRDD<Integer> lineLengths = lines.map(s -> s.length());
int totalLength = lineLengths.reduce((a, b) -> a + b);

python版本

lines = sc.textFile("data.txt")
lineLengths = lines.map(lambda s: len(s))
totalLength = lineLengths.reduce(lambda a, b: a + b)
3.5.2 传递函数到 Spark

Spark 的 API 很大程度上依靠在驱动程序里传递函数到集群上运行。这里有两种推荐的方式:

  • 匿名函数 (Anonymous function syntax),可以在比较短的代码中使用。
  • 全局单例对象里的静态方法。例如,你可以定义 object MyFunctions 然后传递 MyFounctions.func1 ,像下面这样:

    object MyFunctions {
    def func1(s: String): String = { ... }
    }
    myRdd.map(MyFunctions.func1)
    

注意,它可能传递的是一个类实例里的一个方法引用(而不是一个单例对象),这里必须传送包含方法的整个对象。例如:

class MyClass {
  def func1(s: String): String = { ... }
  def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(func1) }
}

这里,如果我们创建了一个 new MyClass 对象,并且调用它的 doStuff , map 里面引用了这个 MyClass 实例中的 func1方法,所以这个对象必须传送到集群上。类似写成 rdd.map(x => this.func1(x)) 。

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

class MyClass {
  val field = "Hello"
  def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(x => field + x) }
}

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

def doStuff(rdd: RDD[String]): RDD[String] = {
  val field_ = this.field
  rdd.map(x => field_ + x)
}

以上为scala版本,之后对Spark的介绍将着重使用Scala,对于java和python版本,查看官网文档:https://spark.apache.org/docs/1.3.0/programming-guide.html#initializing-spark

3.5.3 使用键值对

虽然很多 Spark 操作工作在包含任意类型对象的 RDDs 上的,但是少数几个特殊操作仅仅在键值(key-value)对 RDDs 上可用。最常见的是分布式 “shuffle” 操作,例如根据一个 key 对一组数据进行分组和聚合。

在 Scala 中,这些操作在包含二元组(Tuple2)(在语言的内建元组中,通过简单的写 (a, b) 创建) 的 RDD 上自动地变成可用的,只要在你的程序中导入org.apache.spark.SparkContext._ 来启用 Spark 的隐式转换。在 PairRDDFunctions 的类里键值对操作是可以使用的,如果你导入隐式转换它会自动地包装成元组 RDD。

例如,下面的代码在键值对上使用 reduceByKey 操作来统计在一个文件里每一行文本内容出现的次数:

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

我们也可以使用 counts.sortByKey() ,例如,将键值对按照字母进行排序,最后 counts.collect() 把它们作为一个对象数组带回到驱动程序。

注意:当使用一个自定义对象作为 key 在使用键值对操作的时候,你需要确保自定义 equals() 方法和 hashCode() 方法是匹配的。

3.5.4 Transformations

下面的表格列了 Sparkk 支持的一些常用 transformations。详细内容请参阅 RDD API 文档(Scala, Java, Python)和PairRDDFunctions 文档(Scala, Java)。

TransformationMeaning
map(func)返回一个新的分布式数据集,将数据源的每一个元素传递给函数 func 映射组成。
filter(func)返回一个新的数据集,从数据源中选中一些元素通过函数 func 返回 true。
flatMap(func)类似于 map,但是每个输入项能被映射成多个输出项(所以 func 必须返回一个 Seq,而不是单个 item)。
mapPartitions(func)类似于 map,但是分别运行在 RDD 的每个分区上,所以 func 的类型必须是 Iterator<T> => Iterator<U> 当运行在类型为 T 的 RDD 上。
mapPartitionsWithIndex(func)类似于 mapPartitions,但是 func 需要提供一个 integer 值描述索引(index),所以func 的类型必须是 (Int, Iterator<T>) => Iterator<U> 当运行在类型为 T 的 RDD 上。
sample(withReplacement, fraction, seed)随机抽取样本
union(otherDataset)返回新的数据集包含源数据集和联合数据集
intersection(otherDataset)返回一个新的数据集包含源数据集和论点数据集交集
distinct([numTasks]))返回一个新的数据集包含源数据集中的不用的元素,排重
groupByKey([numTasks])当调用(K,V)对数据集时,返回(K, Iterable)对数据集
reduceByKey(func, [numTasks])当调用(K,V)对数据集时,返回(K,V)对数据集此处的V使用了recuce函数,类型为 (V,V) => V
aggregateByKey(zeroValue)(seqOp, combOp, [numTasks])When called on a dataset of (K, V) pairs, returns a dataset of (K, U) pairs where the values for each key are aggregated using the given combine functions and a neutral “zero” value. Allows an aggregated value type that is different than the input value type, while avoiding unnecessary allocations. Like in groupByKey, the number of reduce tasks is configurable through an optional second argument.
sortByKey([ascending], [numTasks])When called on a dataset of (K, V) pairs where K implements Ordered, returns a dataset of (K, V) pairs sorted by keys in ascending or descending order, as specified in the boolean ascending argument.
join(otherDataset, [numTasks])When called on datasets of type (K, V) and (K, W), returns a dataset of (K, (V, W)) pairs with all pairs of elements for each key. Outer joins are supported through leftOuterJoin, rightOuterJoin, and fullOuterJoin.
cogroup(otherDataset, [numTasks])When called on datasets of type (K, V) and (K, W), returns a dataset of (K, (Iterable, Iterable)) tuples. This operation is also called groupWith.
cartesian(otherDataset)When called on datasets of types T and U, returns a dataset of (T, U) pairs (all pairs of elements).
pipe(command, [envVars])Pipe each partition of the RDD through a shell command, e.g. a Perl or bash script. RDD elements are written to the process’s stdin and lines output to its stdout are returned as an RDD of strings.
coalesce(numPartitions)Decrease the number of partitions in the RDD to numPartitions. Useful for running operations more efficiently after filtering down a large dataset.
repartition(numPartitions)Reshuffle the data in the RDD randomly to create either more or fewer partitions and balance it across them. This always shuffles all data over the network.
repartitionAndSortWithinPartitions(partitioner)Repartition the RDD according to the given partitioner and, within each resulting partition, sort records by their keys. This is more efficient than calling repartition and then sorting within each partition because it can push the sorting down into the shuffle machinery.
3.5.5 Actions

下面的表格列了 Sparkk 支持的一些常用 actions。详细内容请参阅 RDD API 文档(Scala, Java, Python) 和
PairRDDFunctions 文档(Scala, Java)。

ActionMeaning
reduce(func)Aggregate the elements of the dataset using a function func (which takes two arguments and returns one). The function should be commutative and associative so that it can be computed correctly in parallel.example:reduce((a, b) => a + b)
collect()Return all the elements of the dataset as an array at the driver program. This is usually useful after a filter or other operation that returns a sufficiently small subset of the data.
count()Return the number of elements in the dataset.
first()Return the first element of the dataset (similar to take(1)).
take(n)Return an array with the first n elements of the dataset. Note that this is currently not executed in parallel. Instead, the driver program computes all the elements.
takeSample(withReplacement, num, [seed])Return an array with a random sample of num elements of the dataset, with or without replacement, optionally pre-specifying a random number generator seed.
takeOrdered(n, [ordering])Return the first n elements of the RDD using either their natural order or a custom comparator.
saveAsTextFile(path)Write the elements of the dataset as a text file (or set of text files) in a given directory in the local filesystem, HDFS or any other Hadoop-supported file system. Spark will call toString on each element to convert it to a line of text in the file.
saveAsSequenceFile(path)(Java and Scala)Write the elements of the dataset as a Hadoop SequenceFile in a given path in the local filesystem, HDFS or any other Hadoop-supported file system. This is available on RDDs of key-value pairs that either implement Hadoop’s Writable interface. In Scala, it is also available on types that are implicitly convertible to Writable (Spark includes conversions for basic types like Int, Double, String, etc).
saveAsObjectFile(path) (Java and Scala)Write the elements of the dataset in a simple format using Java serialization, which can then be loaded using SparkContext.objectFile().
countByKey()Only available on RDDs of type (K, V). Returns a hashmap of (K, Int) pairs with the count of each key.
foreach(func)Run a function func on each element of the dataset. This is usually done for side effects such as updating an accumulator variable (see below) or interacting with external storage systems.

3.6 RDD Persistence

Spark最重要的一个功能是它可以通过各种操作(operations)持久化(或者缓存)一个集合到内存中。当你持久化一个RDD的时候,每一个节点都将参与计算的所有分区数据存储到内存中,并且这些 数据可以被这个集合(以及这个集合衍生的其他集合)的动作(action)重复利用。这个能力使后续的动作速度更快(通常快10倍以上)。对应迭代算法和快速的交互使用来说,缓存是一个关键的工具。

你能通过 persist() 或者 cache() 方法持久化一个rdd。首先,在action中计算得到rdd;然后,将其保存在每个节点的内存中。Spark的缓存是一个容错的技术-如果RDD的任何一个分区丢失,它 可以通过原有的转换(transformations)操作自动的重复计算并且创建出这个分区。

此外,我们可以利用不同的存储级别存储每一个被持久化的RDD。例如,它允许我们持久化集合到磁盘上、将集合作为序列化的Java对象持久化到内存中、在节点间复制集合或者存储集合到 Tachyon中。我们可以通过传递一个 StorageLevel 对象给 persist() 方法设置这些存储级别。 cache() 方法使用了默认的存储级别—StorageLevel.MEMORY_ONLY 。完整的存储级别介绍如下所示:

Storage LevelMeaning
MEMORY_ONLY将RDD作为非序列化的Java对象存储在jvm中。如果RDD不适合存在内存中,一些分区将不会被缓存,从而在每次需要这些分区时都需重新计算它们。这是系统默认的存储级别。
MEMORY_AND_DISK将RDD作为非序列化的Java对象存储在jvm中。如果RDD不适合存在内存中,将这些不适合存在内存中的分区存储在磁盘中,每次需要时读出它们。
MEMORY_ONLY_SER将RDD作为序列化的Java对象存储(每个分区一个byte数组)。这种方式比非序列化方式更节省空间,特别是用到快速的序列化工具时,但是会更耗费cpu资源—密集的读操作。
MEMORY_AND_DISK_SER和MEMORY_ONLY_SER类似,但不是在每次需要时重复计算这些不适合存储到内存中的分区,而是将这些分区存储到磁盘中。
DISK_ONLY仅仅将RDD分区存储到磁盘中
MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc.和上面的存储级别类似,但是复制每个分区到集群的两个节点上面
OFF_HEAP (experimental)以序列化的格式存储RDD到Tachyon中。相对于MEMORY_ONLY_SER, OFF_HEAP减少了垃圾回收的花费,允许更小的执行者共享内存池。这使其在拥有大量内存的环境下或者多并发应用程序的环境中具有更强的吸引力。

注意:在python中,存储的对象都是通过Pickle库序列化了的,所以是否选择序列化等级并不重要。

Spark也会自动持久化一些shuffle操作(如 reduceByKey )中的中间数据,即使用户没有调用 persist 方法。这样的好处是避免了在shuffle出错情况下,需要重复计算整个输入。如果用户计划重用 计算过程中产生的RDD,我们仍然推荐用户调用 persist 方法。

3.6.1 如何选择存储级别

Spark的多个存储级别意味着在内存利用率和cpu利用效率间的不同权衡。我们推荐通过下面的过程选择一个合适的存储级别:

  • 如果你的RDD适合默认的存储级别(MEMORY_ONLY),就选择默认的存储级别。因为这是cpu利用率最高的选项,会使RDD上的操作尽可能的快。
  • 如果不适合用默认的级别,选择MEMORY_ONLY_SER。选择一个更快的序列化库提高对象的空间使用率,但是仍能够相当快的访问。
  • 除非函数计算RDD的花费较大或者它们需要过滤大量的数据,不要将RDD存储到磁盘上,否则,重复计算一个分区就会和重磁盘上读取数据一样慢。
  • 如果你希望更快的错误恢复,可以利用重复(replicated)存储级别。所有的存储级别都可以通过重复计算丢失的数据来支持完整的容错,但是重复的数据能够使你在RDD上继续运行任务,而不需要重复计算丢失的数据。
  • 在拥有大量内存的环境中或者多应用程序的环境中,OFF_HEAP具有如下优势:
    • 它运行多个执行者共享Tachyon中相同的内存池
    • 它显著地减少垃圾回收的花费
    • 如果单个的执行者崩溃,缓存的数据不会丢失
3.6.2 删除数据

Spark自动的监控每个节点缓存的使用情况,利用最近最少使用原则删除老旧的数据。如果你想手动的删除RDD,可以使用 RDD.unpersist() 方法。

3.7 共享变量

一般情况下,当一个传递给Spark操作(例如map和reduce)的函数在远程节点上面运行时,Spark操作实际上操作的是这个函数所用变量的一个独立副本。这些变量被复制到每台机器上,并且这些变量在远程机器上的所有更新都不会传递回驱动程序。通常跨任务的读写变量是低效的,但是,Spark还是为两种常见的使用模式提供了两种有限的共享变量:广播变量(broadcast variable)和累加器(accumulator)

3.7.1 广播变量

广播变量允许程序员缓存一个只读的变量在每台机器上面,而不是每个任务保存一份拷贝。例如,利用广播变量,我们能够以一种更有效率的方式将一个大数据量输入集合的副本分配给每个节点。Spark也尝试着利用有效的广播算法去分配广播变量,以减少通信的成本。

一个广播变量可以通过调用 SparkContext.broadcast(v) 方法从一个初始变量v中创建。广播变量是v的一个包装变量,它的值可以通过 value 方法访问,下面的代码说明了这个过程:

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

scala> broadcastVar.value
res0: Array[Int] = Array(1, 2, 3)

广播变量创建以后,我们就能够在集群的任何函数中使用它来代替变量v,这样我们就不需要再次传递变量v到每个节点上。另外,为了保证所有的节点得到广播变量具有相同的值,对象v不能在广播之后被修改。

3.7.2 累加器

顾名思义,累加器是一种只能通过关联操作进行“加”操作的变量,因此它能够高效的应用于并行操作中。它们能够用来实现 counters 和 sums 。Spark原生支持数值类型的累加器,开发者可以自己添加支持的类型。 如果创建了一个具名的累加器,它可以在spark的UI中显示。这对于理解运行阶段(running stages)的过程有很重要的作用。(注意:这在python中还不被支持)

scala> val accum = sc.accumulator(0, "My Accumulator")
accum: spark.Accumulator[Int] = 0
scala> sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum += x)
...
10/09/29 18:41:08 INFO SparkContext: Tasks finished in 0.317106 s
scala> accum.value
res2: Int = 10

这个例子利用了内置的整数类型累加器。开发者可以利用子类AccumulatorParam创建自己的 累加器类型。AccumulatorParam接口有两个方法: zero 方法为你的数据类型提供一个“0 值”(zero value); addInPlace 方法计算两个值
的和。例如,假设我们有一个 Vector 类代表数学上的向量,我们能够 如下定义累加器:

object VectorAccumulatorParam extends AccumulatorParam[Vector] {
def zero(initialValue: Vector): Vector = {
Vector.zeros(initialValue.size)
    }
def addInPlace(v1: Vector, v2: Vector): Vector = {
v1 += v2
    }
}
// Then, create an Accumulator of this type:
val vecAccum = sc.accumulator(new Vector(...))(VectorAccumulatorParam)

在scala中,Spark支持用更一般的Accumulable接口来累积数据-结果类型和用于累加的元素类型 不一样(例如通过收集的元素建立一个列表)。Spark也支持用 SparkContext.accumulableCollection 方法累加一般的scala集合类型。

在scala中,Spark支持用更一般的Accumulable接口来累积数据-结果类型和用于累加的元素类型 不一样(例如通过收集的元素建立一个列表)。Spark也支持用 SparkContext.accumulableCollection 方法累加一般的scala集合类型。

4、 Spark SQL

Spark SQL 是spark的结构化数据处理模块。它提供了一个抽象设计成为DataFrames,它也被看做分布式SQL查询引擎。

4.1 DataFrames

DataFrame是分布式按列组织数据的集合。它概念上等同关系型数据库的表或者R/Python中的data frame,但是它提供了更多的优化。DataFrames可以被构建从一个广泛的来源:结构化数据文件,hive表,外部数据库,存在的RDDs。

4.1.1 开始

Spark中所有相关功能的入口点是SQLContext类或者它的子类, 创建一个SQLContext的所有需要仅仅是一个SparkContext。

val sc: SparkContext // An existing SparkContext.
val sqlContext = new org.apache.spark.sql.SQLContext(sc)

// this is used to implicitly convert an RDD to a DataFrame.
import sqlContext.implicits._

除了一个基本的SQLContext,你也能够创建一个HiveContext,它支持基本SQLContext所支持功能的一个超集。它的额外的功能包括用更完整的HiveQL分析器写查询去访问HiveUDFs的能力、 从Hive表读取数据的能力。用HiveContext你不需要一个已经存在的Hive开启,SQLContext可用的数据源对HiveContext也可用。HiveContext分开打包是为了避免在Spark构建时包含了所有 的Hive依赖。如果对你的应用程序来说,这些依赖不存在问题,Spark 1.3推荐使用HiveContext。以后的稳定版
本将专注于为SQLContext提供与HiveContext等价的功能。

用来解析查询语句的特定SQL变种语言可以通过 spark.sql.dialect 选项来选择。这个参数可以通过两种方式改变,一种方式是通过 setConf 方法设定,另一种方式是在SQL命令中通过 SET key=value 来设定。对于SQLContext,唯一可用的方言是“sql”,它是Spark SQL提供的一个简单的SQL解析器。在HiveContext中,虽然也支持”sql”,但默认的方言是“hiveql”。这是因为HiveQL解析器更 完整。在很多用例中推荐使用“hiveql”。

4.1.2 创建DataFrames

用一个SQLContext,应用程序可以创建DataFrames从一个已经存在的RDDs,从一个hive表,或者从数据源。

下面是一个基于JSON文件内容创建DataFrames的实例:

val sc: SparkContext // An existing SparkContext.
val sqlContext = new org.apache.spark.sql.SQLContext(sc)

val df = sqlContext.jsonFile("examples/src/main/resources/people.json")

// Displays the content of the DataFrame to stdout
df.show()

4.1.3 DataFrame 操作

DataFrames提供了一个特定域语言(DSL)对结构化数据操作。下面是使用DataFrames处理结构化语言的基本实例:

val sc: SparkContext // An existing SparkContext.
val sqlContext = new org.apache.spark.sql.SQLContext(sc)

// Create the DataFrame
val df = sqlContext.jsonFile("examples/src/main/resources/people.json")

// Show the content of the DataFrame
df.show()
// age  name
// null Michael
// 30   Andy
// 19   Justin

// Print the schema in a tree format
df.printSchema()
// root
// |-- age: long (nullable = true)
// |-- name: string (nullable = true)

// Select only the "name" column
df.select("name").show()
// name
// Michael
// Andy
// Justin

// Select everybody, but increment the age by 1
df.select("name", df("age") + 1).show()
// name    (age + 1)
// Michael null
// Andy    31
// Justin  20

// Select people older than 21
df.filter(df("name") > 21).show()
// age name
// 30  Andy

// Count people by age
df.groupBy("age").count().show()
// age  count
// null 1
// 19   1
// 30   1
4.1.4 运行SQL请求程序

SQLContext的sql函数能使应用程序运行SQL请求,并且返回结果作为一个DataFrame。

val sqlContext = ...  // An existing SQLContext
val df = sqlContext.sql("SELECT * FROM table")
4.1.5 DataFrame和RDDs交互操作

Spark SQL提供两种方式转换RDDs到DataFrames。第一种方法使用反射来推断包含特定对象类型的RDD的模式(schema)。当你已经知道了模式,这种基于反射的方法可以使代码更简洁并且程序工作得更好。

创建SchemaRDDs的第二种方法是通过一个编程接口来实现,这个接口允许你构造一个模式,然后在存在的RDDs上使用它。虽然这种方法更冗长,但是它允许你在运行期之前不知道列以及列的类型的情况下构建DataFrames。

利用反射推断模式

Spark SQL的Scala接口支持将包含样本类的RDDs自动转换为DataFrame。样本类定义表的结构。给样本类的参数名字通过反射来读取,然后作为列的名字。样本类可以嵌套或者包含复杂的类型如序列或者数组。这个RDD可以隐式转化为一个DataFrame,然后注册为一个表。表可以在后续的 sql语句中使用。

// sc is an existing SparkContext.
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
// this is used to implicitly convert an RDD to a DataFrame.
import sqlContext.implicits._

// Define the schema using a case class.
// 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 interface.
case class Person(name: String, age: Int)

// Create an RDD of Person objects and register it as a table.
val people = sc.textFile("examples/src/main/resources/people.txt").map(_.split(",")).map(p => Person(p(0), p(1).trim.toInt)).toDF()
people.registerTempTable("people")

// SQL statements can be run by using the sql methods provided by sqlContext.
val teenagers = sqlContext.sql("SELECT name FROM people WHERE age >= 13 AND age <= 19")

// 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 ordinal.
teenagers.map(t => "Name: " + t(0)).collect().foreach(println)
编程指定模式

当样本类不能提前确定(例如,记录的结构是经过编码的字符串,或者一个文本集合将会被解析,不同的字段投影给不同的用户),一个DataFrame可以通过三步来创建。

  1. 从原来的RDD创建一个行的RDD
  2. 创建由一个 StructType 表示的模式与第一步创建的RDD的行结构相匹配
  3. 应用模式到RDD的行通过SQLContext提供的方法createDataFrame

实例:

// sc is an existing SparkContext.
val sqlContext = new org.apache.spark.sql.SQLContext(sc)

// Create an RDD
val people = sc.textFile("examples/src/main/resources/people.txt")

// The schema is encoded in a string
val schemaString = "name age"

// Import Spark SQL data types and Row.
import org.apache.spark.sql._

// Generate the schema based on the string of schema
val schema =
  StructType(
    schemaString.split(" ").map(fieldName => StructField(fieldName, StringType, true)))

// Convert records of the RDD (people) to Rows.
val rowRDD = people.map(_.split(",")).map(p => Row(p(0), p(1).trim))

// Apply the schema to the RDD.
val peopleDataFrame = sqlContext.createDataFrame(rowRDD, schema)

// Register the DataFrames as a table.
peopleDataFrame.registerTempTable("people")

// SQL statements can be run by using the sql methods provided by sqlContext.
val results = sqlContext.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 ordinal.
results.map(t => "Name: " + t(0)).collect().foreach(println)

4.2 Data Sources

4.2.1 LOad/Save函数

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

val df = sqlContext.load("people.parquet")
df.select("name", "age").save("namesAndAges.parquet")
4.2.1.1 手动指定选项

你可以指定数据源用额外的选项。数据源被指定通过他们的完全限定名(例如:org.apache.spark.sql.parquet)。单数内建数据源可以用简写名字(json, parquet, jdbc)。一些类型的DataFrames能被转换为其他类型使用这种方式。

val df = sqlContext.load("people.json", "json")
df.select("name", "age").save("namesAndAges.parquet", "parquet")
4.2.1.2 保存模式

保存操作可以选择性的选择一个SaveMode,它指定如何处理目前存在的数据。了解保存模式不能利用锁和不是原子性的是很重要的。因此,很多写操作试图写到相同位置是很不安全的。除此之外,当使用overwrite时,之前的数据将被删除。

Scala/JavaPython
SaveMode.ErrorIfExists (default)“error” (default)
SaveMode.Append“append”
SaveMode.Overwrite“overwrite”
SaveMode.Ignore“ignore”
4.2.1.3 保存数据到持久化表

当使用HiveContext时,DataFrames能被保存为持久化表,使用saveAsTable命令。不像 registerTempTable命令,saveAsTable将会写入 dataframe的内容,在HiveMetastore创建一个指针指向数据。持久化表会一直存在,即使你的Spark程序重启,只要你保持你的连接到相同的metastore。一个DataFrame针对一个持久化表能被创建通过调用SQLContext中的table方法指定表名。

默认,saveAsTable将会创建一个托管的表,数据的位置将会被metastore控制。当一个表被删除时,托管表将不会有自动数据检测。

4.2.2 Parquet文件

Parquet是一种柱状(columnar)格式,可以被许多其它的数据处理系统支持。Spark SQL提供支持读和写Parquet文件的功能,这些文件可以自动地保留原始数据的模式。

4.2.2.1 加载数据
// sqlContext from the previous example is used in this example.
// This is used to implicitly convert an RDD to a DataFrame.
import sqlContext.implicits._

val people: RDD[Person] = ... // An RDD of case class objects, from the previous example.

// The RDD is implicitly converted to a DataFrame by implicits, allowing it to be stored using Parquet.
people.saveAsParquetFile("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 DataFrame.
val parquetFile = sqlContext.parquetFile("people.parquet")

//Parquet files can also be registered as tables and then used in SQL statements.
parquetFile.registerTempTable("parquetFile")
val teenagers = sqlContext.sql("SELECT name FROM parquetFile WHERE age >= 13 AND age <= 19")
teenagers.map(t => "Name: " + t(0)).collect().foreach(println)
4.2.2.2 分区发现

表分区是一种优化方法用在系统像Hive。在一个分区表中,数据通常存储在不同的目录中,分区的列值通常作为每个目录的名字。Parquet数据源可以自动发现和推断分区信息。例如,我们存储过去的人口数据在一个分区表,使用gender和country作为分区:

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到SQLContext.parquetFile 或者SQLContext.load。Spark SQL将会自动的从路径摘取分区信息。下面是返回的DataFrame:

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

注意:分区列的数据类型是自动推断。当前,data类型和String类型被支持。

4.2.2.3 模式融合

像ProtocolBuffer、Avro and Thrift, Parquet 也支持模式演变。用户可以开始使用一个简单的模式,渐渐的增加更多需要的列到模式中。在这种方式,用户可以建立多个不同的但是模式兼容的Parquet文件。Parquet数据源可以自动检测这种状况,融合所有这些文件的模式。

// sqlContext from the previous example is used in this example.
// This is used to implicitly convert an RDD to a DataFrame.
import sqlContext.implicits._

// Create a simple DataFrame, stored into a partition directory
val df1 = sparkContext.makeRDD(1 to 5).map(i => (i, i * 2)).toDF("single", "double")
df1.saveAsParquetFile("data/test_table/key=1")

// Create another DataFrame in a new partition directory,
// adding a new column and dropping an existing column
val df2 = sparkContext.makeRDD(6 to 10).map(i => (i, i * 3)).toDF("single", "triple")
df2.saveAsParquetFile("data/test_table/key=2")

// Read the partitioned table
val df3 = sqlContext.parquetFile("data/test_table")
df3.printSchema()

// The final schema consists of all 3 columns in the Parquet files together
// with the partiioning column appeared in the partition directory paths.
// root
// |-- single: int (nullable = true)
// |-- double: int (nullable = true)
// |-- triple: int (nullable = true)
// |-- key : int (nullable = true)
4.2.2.4 配置

可以在SQLContext上使用setConf方法配置Parquet或者在用SQL时运行 SET key=value 命令来配置Parquet。

Property NameDefaultMeaning
spark.sql.parquet.binaryAsStringfalseSome other Parquet-producing systems, in particular Impala and older versions of Spark SQL, do not differentiate between binary data and strings when writing out the Parquet schema. This flag tells Spark SQL to interpret binary data as a string to provide compatibility with these systems.
spark.sql.parquet.int96AsTimestamptrueSome Parquet-producing systems, in particular Impala, store Timestamp into INT96. Spark would also store Timestamp as INT96 because we need to avoid precision lost of the nanoseconds field. This flag tells Spark SQL to interpret INT96 data as a timestamp to provide compatibility with these systems.
spark.sql.parquet.cacheMetadatatrueTurns on caching of Parquet schema metadata. Can speed up querying of static data.
spark.sql.parquet.compression.codecgzipSets the compression codec use when writing Parquet files. Acceptable values include: uncompressed, snappy, gzip, lzo.
spark.sql.parquet.filterPushdownfalseTurn on Parquet filter pushdown optimization. This feature is turned off by default because of a known bug in Paruet 1.6.0rc3 (PARQUET-136). However, if your table doesn’t contain any nullable string or binary columns, it’s still safe to turn this feature on.
spark.sql.hive.convertMetastoreParquettrueWhen set to false, Spark SQL will use the Hive SerDe for parquet tables instead of the built in support.
4.2.3 JSON数据集

Spark SQL能够自动推断JSON数据集的模式,加载它为一个DataFrame。这种转换可以通过下面两种方法来实现

  • jsonFile :从一个包含JSON文件的目录中加载。文件中的每一行是一个JSON对象
  • jsonRDD :从存在的RDD加载数据,这些RDD的每个元素是一个包含JSON对象的字符串

注意:作为jsonFile的文件不是一个典型的JSON文件,每行必须是独立的并且包含一个有效的JSON对象。一个多行的JSON文件经常会失败。

// sc is an existing SparkContext.
val sqlContext = new org.apache.spark.sql.SQLContext(sc)

// A JSON dataset is pointed to by path.
// The path can be either a single text file or a directory storing text files.
val path = "examples/src/main/resources/people.json"
// Create a DataFrame from the file(s) pointed to by path
val people = sqlContext.jsonFile(path)

// The inferred schema can be visualized using the printSchema() method.
people.printSchema()
// root
//  |-- age: integer (nullable = true)
//  |-- name: string (nullable = true)

// Register this DataFrame as a table.
people.registerTempTable("people")

// SQL statements can be run by using the sql methods provided by sqlContext.
val teenagers = sqlContext.sql("SELECT name FROM people WHERE age >= 13 AND age <= 19")

// Alternatively, a DataFrame can be created for a JSON dataset represented by
// an RDD[String] storing one JSON object per string.
val anotherPeopleRDD = sc.parallelize(
  """{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil)
val anotherPeople = sqlContext.jsonRDD(anotherPeopleRDD)
4.2.4 Hive表

Spark SQL也支持从Apache Hive中读出和写入数据。然而,Hive有大量的依赖,所以它不包含在默认Spark assembly。可以通过 -Phive 和 -Phive-thriftserver 参数构建Spark,使其支持Hive。注意这个重新构建的jar包必须存在于所有的worker节点中,因为它们需要通过Hive的序列化和反序列化库访问存储在Hive中的数据。

Hive配置文件hive-site.xml需要放入spark的conf/ 下。

当和Hive一起工作是,开发者需要提供HiveContext。HiveContext从SQLContext继承而来,它增加了在MetaStore中发现表以及利用HiveSql写查询的功能。没有Hive部署的用户也 可以创建HiveContext。当没有通过 hive-site.xml 配置,上下文将会在当前目录自动地创建 metastore_db 和 warehouse 。

// sc is an existing SparkContext.
val sqlContext = new org.apache.spark.sql.hive.HiveContext(sc)

sqlContext.sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING)")
sqlContext.sql("LOAD DATA LOCAL INPATH 'examples/src/main/resources/kv1.txt' INTO TABLE src")

// Queries are expressed in HiveQL
sqlContext.sql("FROM src SELECT key, value").collect().foreach(println)

4.3 性能调优

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

4.3.1 在内存中缓存数据

Spark SQL可以通过sqlContext.cacheTable(“tableName”) 或者dataFrame.cache()来缓存使用柱状格式的表。然后,Spark将会仅仅浏览需要
的列并且自动地压缩数据以减少内存的使用以及垃圾回收的压力。你可以通过调用 sqlContext.uncacheTable(“tableName”) 方法在内存中删除表。

可以在SQLContext上使用setConf方法或者在用SQL时运行 SET key=value 命令来配置内存缓存。

Property NameDefaultMeaning
spark.sql.inMemoryColumnarStorage.compressedtrueWhen set to true Spark SQL will automatically select a compression codec for each column based on statistics of the data.
spark.sql.inMemoryColumnarStorage.batchSize10000Controls the size of batches for columnar caching. Larger batch sizes can improve memory utilization and compression, but risk OOMs when caching data.

其他配置选项

Property NameDefaultMeaning
spark.sql.autoBroadcastJoinThreshold10485760 (10 MB)Configures the maximum size in bytes for a table that will be broadcast to all worker nodes when performing a join. By setting this value to -1 broadcasting can be disabled. Note that currently statistics are only supported for Hive Metastore tables where the command `ANALYZE TABLE <tableName> COMPUTE STATISTICS noscan` has been run.
spark.sql.codegenfalseWhen true, code will be dynamically generated at runtime for expression evaluation in a specific query. For some queries with complicated expression this option can lead to significant speed-ups. However, for simple queries this can actually slow down query execution.
spark.sql.shuffle.partitions200Configures the number of partitions to use when shuffling data for joins or aggregations.

4.4 运行Spark SQL CLI

Spark SQL CLI是一个便利的工具,它可以在本地运行Hive元存储服务、执行命令行输入的查询。注意,Spark SQL CLI不能与Thrift JDBC服务器通信。

在Spark目录运行下面的命令可以启动Spark SQL CLI。

./bin/spark-sql

Hive的配置文件 hive-site.xml需要放入spark 的conf/ 目录。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值