1、概述

总的来讲,每一个spark驱动程序应用都由一个驱动程序组成,该驱动程序包含一个由用户编写的main方法,该方法会在集群上执行一些并行计算操作。Spark最重要的一个概念是弹性分布式数据集,简称RDD,RDD是一个数据容器,他将分布式在集群上各个节点上的数据抽象为一个数据集,并且RDD能够进行一系列的并行计算操作。可以将RDD理解为一个分布式的List,该List的数据为分布在各个节点上的数据。RDD通过读取Hadoop文件系统中的一个文件进行创建,也可以有一个RDD经过转换得到。用户也可以将RDD缓存到内存,从而高效的处理RDD,提高计算效率。另外,RDD有良好的容错机制。

Spark另外一个重要概念是共享变量。在并行计算时,可以方便的使用共享变量。在默认情况下,执行Spark任务时会在多个节点上并行执行多个task,Spark将每个每个变量的副本分发给各个task。在一些场景下,需要一个能够在各个task间共享的变量。Spark支持两种类型的共享变量:

广播变量:将一个只读变量缓存到集群的每个节点上。例如,将一份数据的只读缓存分发到每个节点。

累加变量:只允许add操作,用于计数、求和。

2、引入spark

groupId = org.apache.spark
artifactId = spark-core_2.10
version = 1.6.0
groupId = org.apache.hadoop
artifactId = hadoop-client
version = <your-hdfs-version>


import org.apache.spark.SparkContext
import org.apache.spark.SparkConf

3、初始化Spark

使用Scala编写Spark程序首先需要创建一个SparkContext对象(Java则使用JavaSparkContext)。SparkContext对象指定了Spark应用访问集群的方式。创建SparkContext需要先创建一个SparkConf对象,SparkConf对象包含了Spark应用的一些列信息。

//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);

appName参数为应用程序在集群的UI上显示的名字。master为Spark、Mesos、Yarn URL或local。使用local值时,表示在本地模式下运行程序。应用程序的执行模型也可以在使用spark-submit命令提交任务时进行指定。

3.1 使用Spark Shell

在spark Shell下,一个特殊的Sparkcontext对象已经帮用户创建好,变量为sc。使用参数--master设置master参数值,使用参数--jars设置依赖包,多个jar包使用逗号分隔。可以使用--packages参数指定maven坐标来添加依赖包,多个坐标使用逗号分隔。可以使用参数--repositories添加外部的repository。示例如下:

本地模式下,使用4个核运行Spark程序:

$ ./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"

4 、弹性分布式数据集(RDDs)

Spark最重要的一个概念就是RDD,RDD是一个有容错机制的元素容器,他可以进行并行计算操作。得到RDD的方式有两个:

通过并行化驱动程序中已有的一个集合来获得

通过外部存储系统(例如共享的文件系统,HDFS、HBase等)的数据集进行创建

4.1 并行集合

在驱动程序中,在一个已经存在的集合上(例如一个Scala的Seq)调用SparkContext的parallelize方法可以创建一个并行集合。集合里的元素将被复制到一个可被并行操作的分布式数据集中。下面为并行化一个保存数据1到5的集合进行示例:

//Scals
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);

当分布式数据集创建之后,就可以进行并行操作。例如,可以调用方法distData.reduce((a.b)=>a+b)求数组内元素的和。Spark支持的分布式数据集上的操作将在后面章节中详细描述。

并行集合的一个重要的参数是表示将数据划分为几个分区的分区数。Spark将在集群上每个数据分区上启动一个task。通常情况下,你可以在集群上为每个CPU设置2-4个分区。一般情况下,Spark基于集群自动设置分区数目。也可以手动进行设置,设置该参数需要将参数值作为第二个参数传给parallelize方法,例如:sc.parallelize(data,10).注意:在代码中,部分位置使用术语slices(而不是partition),这么做的原因是为了保持版本的向后兼容性。

4.2 外部数据库

Spark可以通过Hadoop支持的外部数据源创建分布式数据集,Hadoop支持的数据源有本地系统、HDFS、Cassandra、HBase、Amazon S3、Spark支持的文本文件、SequenceFiles、Hadoop InputFormat.

SparkContext的testFile方法可以创建文本文件RDD。使用这个方法需要传递文本文件的URI,URI可以为本机文件路径、hdf://、s3n://等。该方法读取文本文件的每一行到容器中。示例如下:

//Scala
val distFile = sc.textFile("data.txt")
distFile: RDD[String] = MappedRDD@1d4cee08
//Java
JavaRDD<String> distFile = sc.textFile("data.txt");

创建之后,distFile就可以进行数据集的通用操作。例如,使用map和reduce操作计算素有行的长度的总和:distFile.map(s => s.length).reduce((a,b) => a+b).
使用Spark读取文件需要注意以下几点:

程序中如果使用到本地文件路径,在其他worker节点上该文件必须在同一目录,并具有访问权限。在这种情况下,可以将文件复制到所有的work结点。也可以使用网络内的共享文件系统。

Spark所有的基于文件输入的方法(包括textFile),都支持文件夹、压缩文件、通配符。例如:textFile("/my/directory")、textFile(“/my/directory/*.txt”)、textFile(“/my/directory/*.gz”)

textFile方法提供了一个可选的第二参数,用于控制文件的分区数。默认情况下,Spark为文件的每个块创建一个分区(块使用HDFS的默认值64M),通过设置这个第二个参数可以修改这个默认值。需要注意的是,分区数不能小于块数。

除此之外,Spark还支持其他的数据格式:

sparkContext.wholeTextFiles能够读取指定目录下的许多小文本文件,返回(filename,content)对。而textFile只能读取一个文本文件,返回该文本文件的每一行。

对于sequenceFiles可以使用SparkContext的sequenceFile[K,V]方法,其中K是文件中key和value的类型。他们必须为像IntWritable和Text那样,是Hadoop的Writable接口的子类。另外,对于通用的Writable,Spark允许用户指定原生类型。例如,sequenceFile[Int,String]将自动读取IntWritable和Text。

对于其他hadoop InputFormat,可以使用SparkContext.hadoopRDD方法,该方法接收任意类型的JobConf和JocConf和输入格式类、键值类型。可以像设置Hadoop job那样设置输入源。对于InputFormat还可以使用基于新版本MapReduce API的AparkContext.newAPIHadoopRDD。

RDD.saveAsObjectFile和SparkContext.objectFile能够保存包含简单的序列化java对象的RDD。但是这个方法不如AVRO高效。

4.3 RDD操作

RDD支持两种类型的操作:

transformation:从一个RDD转换为一个新的RDD

action:基于一个数据集进行运算,并返回RDD

例如,map是一个transformation操作,map将数据集的每一个元素按指定的函数转换为一个RDD返回。reduce是一个aciton操作,reduce将RDD的所有元素按指定的函数进行聚合并返回结果给驱动程序(还有一个并行的reduceByKey能够返回一个分布式的数据集)

Spark的所有transformation操作都是懒执行,他们并不立马执行,而是先记录对数据集的一系列transformation操作。在执行一个需要执行一个action操作时,会执行该数据数据集上所有的transformation操作,然后返回结果。这种设计让Spark的运算更高效。例如,对一个数据集map操作之后使用reduce只返回结果,而不能返回庞大的map运算的结果集。

默认情况下,每个转换的RDD在执行action操作时都会重新计算。即使两个action操作会使用同一个转换的RDD,该RDD也会重新计算。在这种情况下,可以使用persist方法或cache方法将RDD缓存到内存,这样在下次使用这个RDD时将会提高计算效率。在这里,也支持RDD持久化到磁盘,或在多个节点上复制。

4.3.1 基础

参考下面的程序,了解RDD的基本轮廓

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

第一行通过读取一个文件创建一个基本的RDD。这个数据集没有加载到内存,也没有进行其他的操作,变量lines仅仅是一个执行文件的指针。第二行为transformation操作map的结果。此时lineLengths也没有进行运算,因为map操作为懒执行。最后,执行action操作reduce。此时Spark将运算分隔成多个任务分发给多个机器,每个机器执行各自部分的map并进行本地的reduce,最后返回运行结果给驱动程序。

如果在后面的运算中仍会用到lineLengths,可以将其缓存,在reduce操作之前添加如下代码,该persist将在lineLengths第一次计算得到后将其缓存到内存:

//scala

linelengths.persist()

//Java

lineLengths.persist(StorageLevel.MEMEORE_ONLY());

4.3.2 把函数传递到Spark(Passing Functions to Spark)

//Scala

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

使用匿名函数的语法,这可以让代码更加简洁。

使用全局单例对象的静态方法。比如,你可以定义函数对象object MyFunctions,然后将该对象的MyFunction.func1方法传递给Spark,如下所示:

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)
}

//Java

Spark的API,在很大程度上依赖于把驱动程序中的函数传递到器群上运行。在Java中,函数由那些实现了org.apache.spark.api.java.function包中的接口表示。由两种创建这样的函数的方式:

在自己的类中实现Function接口,可以是匿名内部类,或者命名类,并且传递类的一个实例到Spark

在Java8中,使用lambda表达式来简明地定义函数的实现

为了保持简洁性,本指南中大量使用了lambda语法,这在长格式中很容易使用所有相同的APIs。比如我们可以把上面的代码写成:

JavaRDD<String> lines = sc.textFile("data.txt")
JavaRDD lineLengths = lines.map(new Function Integer>(){
public Integer call (String s) { return s.length();}
}); int totaLength = lineLengths.reduce(new Function2 Integer, Integer>(){
public Integer call(Integer a, Integer b){return a + b}
});

同样的功能,使用内联式的实现显得更为笨重繁琐,代码如下:

class GetLeng implements Function Integer>{
public Integer call(String s) {return s.length();}
} class sum implements Funciton2 Integer, Integer>{
public Integer call(Integer a, Integer b){
reurn a + b;
}
} JavaRDD lines = sc.textFile("data.txt");
JavaRDD lineLengths = lines.map(new GetLength());
int totalLength = lineLengths.reduce(new Sum());

注意,java中的内部匿名类,只要带有final关键字,就可以访问类范围内的变量。Spark也会把变量复制到每一个worker节点。

4.3.3 理解闭包

使用Spark的一个难点为:理解程序在集群中执行时变量和方法的生命周期。RDD操作可以在变量范围之外修改变量,这是一个经常导致迷惑的地方。比如下面的例子,使用foreach()方法增加计时器countter的值。

4.3.3.1示例

参考下面简单的RDD元素求和示例,求和运算是否在同一个JVM中执行,其复杂度也不同。Spark可以在local模式下(--master = local[n])执行应用,也可以将该Spark应用提交到集群上执行(例如通过spark-submit提交到YARN):

//scala

var counter = 0
var rdd = sc.parallelize(data) //wrong
rdd.foreach(x=>counter ++ x) println("counter value:" + counter)

//Java

int counter = 0;
JavaRDD<Integer> rdd = sc.parallelize(data); //wrong
rdd.foreach(x->counter +=x);
println("counter value:" + counter);

上面是错误的,应该使用累加器

4.3.3.2 本地模式VS集群模式

在本地模式下仅有一个JVM,上面的代码将直接计算RDD中元素和,并存储到counter中。此时RDD和变量counter都在driver节点同一内存空间中。

然而,在集群模式下,情况会变得复杂,上面的代码并不会按照预期的方式执行。为了执行这个job,Spark把处理RDD的操作分割成多个任务,每个任务将被一个executor处理。在执行之前,Spark首先计算闭包。闭包必须对executor可见的变量和方法,在对RDD进行运算时建辉用到这些变量和方法(本例中指foreach())。这个闭包会被序列化,并发送给每个executor。在local模式下,只有一个executor,所以所有的变量和方法都是用同一个闭包。在其他模式下情况跟local模式不一样,每个executor在不同的worker节点上运行,每个executor都有一个单独的闭包。

在这里,发送给每个executor的闭包内的变量是当前变量的副本,因此当counter在foreach中被引用时,已经不是在driver节点上的counter了。在driver节点的内存中仍然有一个counter,但这个counter对executor不可见。executor只能操作序列化的闭包中的counter副本。因此,最终counter的值仍然是0,因为所有对counter的操作都是在序列化的闭包内的counter上进行的。

在类似这种场景下,为了保证良好的行为确保,应该使用累加器。Spark中的累加器站门为在集群中多个节点间更新

												

spark_API的更多相关文章

随机推荐

  1. java web编程 servlet读取配置文件参数

    新建一个servlet. 然后在web.xml文件里面自动帮助你创建好了<servlet-name><servlet-class><servlet-mapping> ...

  2. Flink 使用(一)——从kafka中读取数据写入到HBASE中

    1.前言 本文是在<如何计算实时热门商品>[1]一文上做的扩展,仅在功能上验证了利用Flink消费Kafka数据,把处理后的数据写入到HBase的流程,其具体性能未做调优.此外,文中并未就 ...

  3. zabbix-web界面显示中文

    转载:https://www.cnblogs.com/miclesvic/p/6145171.html 1.确认zabbix是否开启了中文支持功能(/var/www/html/zabbix/inclu ...

  4. sklearn 翻译笔记:KNeighborsClassifier

    今天做机器学习knn的实现想使用sklearn这个模块,但是里面的函数不懂,无奈只能查文档,但是一大片英文看见我就烦,也不是说不能看  但是以我低下的英语水平实在是太费劲了.幸好找到一篇前人翻译的比较 ...

  5. 2013.4.29 - KDD第十一天

    今天上午在图书馆写FIrst集,真心没写出来,算法是昨天找好的,不过实现的话还是需要很大的代码量,然后就打算用郑茂或者韩冰的代码了. 晚上图书馆快关门的时候开始思考KDD的问题, 我一开始打算给中秋发 ...

  6. word标题和序号列表的使用

  7. 自定义jsr-269注解处理器 Error:服务配置文件不正确,或构造处理程序对象javax.annotation.processing.Processor: Provider not found

    出现的原因 自定义处理器还没有被编译就被调用,所以报 not found在根据配置寻找自定义的注解处理器时,自定义处理器还未被编译12解决方式 maven项目可以配置编译插件,在编译项目之前先编译处理 ...

  8. cobbler相关

    Cobbler通过将设置和管理一个安装服务器所涉及的任务集中在一起,从而简化了系统配置.相当于Cobbler封装了DHCP.TFTP.XINTED等服务,结合了PXE.kickstart等安装方法,可 ...

  9. 【转】MarkDown添加图片的三种方式

    原文:https://www.jianshu.com/p/280c6a6f2594 ----------------------------------------------------- 插图最基 ...

  10. 一次完整的HTTP请求与响应

    本篇介绍的是一次完成的http请求都经过了那些步骤,这些步骤相应的作用又是什么 1.在浏览器端输入网站的url地址 只有知道了一个网站的url地址才能访问到这个网站 2.浏览器查找缓存 浏览器会查找浏 ...