叫做分布式数据集,是spark中最基本的数据抽象,它代表一个不可变,可分区,里面的元素可以并行计算的集合。
弹性的:
分布式,可以并行在集群计算。
用于存放数据的集合。
一个分区列表,RDD中的数据都存储在一个分区列表中。
作用在每一个分区中的函数。
一个RDD依赖于其它多个RDD,这点很重要,RDD的容错机制就是依据这个特性而来的。
可选的,针对kv类型的RDD才有这个特性,作用是决定了数据的来源以及数据处理后的去向。
计算向数据靠拢。
用于遍历RDD,将函数应用于每一个元素,返回新的RDD(transformation算子)。
与遍历RDD,将函数应用于每一个元素,无返回值(action算子)
用于遍历操作RDD中的每一个分区,返回生成一个新的RDD(transformation算子)
用于遍历操作RDD中的每一个分区,无返回值(action算子)。
ps:一般使用mapPartitions和foreachPartition算子比map和foreach更加高效,推荐使用。
RDD和它的父RDD的关系有两种类型:窄依赖和宽依赖
指的是每个父RDD的Partition最多被子RDD的一个partition使用,是一对一的,也就是父RDD的一个分区数据去到了子RDD的一个分区中,这个过程没有shuffle产生。
输入输出一对一的算子,且结果RDD的分区结构不变。主要是map/flatmap。
输入输出一对一的算子,但结果RDD的分区结构发生了变化,如union/coalesce。
从输入中选择部分元素的算子,如filter、distinct、substract、sample。
指的是多个子RDD的Partition会依赖同一个父RDD的Partition,关系是多对一,,父RDD的一个分区的数据去到RDD的不同分区里面,会有shuffle的产生。
对单个RDD基于key进行重组和reduce,如groupByKey,reduceByKey。
对两个RDD基于key进行join和重组,如join。
经过大量shuffle生成的RDD,建议进行缓存。这样避免失败后重新带来的开销。
ps:reduce是一个action,和reduceByKey完全不同。
spark任务会根据RDD之间的依赖关系,形成一个DAG有向无环图,DAG会提交给DAGScheduler,DAGScheduler会把DAG划分相互依赖的多个stage,划分依据就是宽窄依赖,遇到宽依赖就划分stage,每个stage包含一个或多个task,然后将这些task以taskSet的形式提交给TaskScheduler运行,stage是由一组并行的task组成。
是一个面向stage的调度器。
主要入参:dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, allowLocal, resultHandler, localProperties.get)
rdd:final RDD;
cleanedFunc:计算每个分区的函数。
resultHander:结果侦听器。
ps:一个stage内部的错误不是由shuffle输出丢失造成的,DAGScheduler是不管的,由TaskScheduler负责尝试重新提交task执行。
一旦driver程序中出现action,就会生成一个job,比如count等,向DAGScheduler提交job。如果driver程序后面还有action,那么其它action也会生成相应的job。所以,driver端有多少action就会提交多少job。
这可能就是为什么spark将driver程序称为application而不是job的原因。
每一个job可能会包含一个或多个stage,最后一个stage生成result。在提交job的过程中,DAGScheduler会首先从后往前划分stage,划分的标准就是宽依赖,一旦遇到宽依赖就划分。
然后先提交没有父阶段的stage,并在提交过程中,计算该stage的task数目以及类型,并提交具体的task,在这些无父阶段的stage提交完之后,依赖该stage的stage才会提交。
DAG,有向无环图,简单来说,就是一个有顶点和有方向性的边构成的图中,从任意一个顶点出发,没有任何一条路径会将其带回到出发点的顶点位置。
为每个spark job计算具有依赖关系的多个stage任务阶段,通常根据shuffle来划分stage,如reduceByKey、groupByKey等涉及到shuffle的转换(transformation)就会产生新的stage。
然后将每个stage划分为具体的一组任务,以taskSet的形式提交给底层的任务调度模块来执行,其中不同stage之前的RDD为宽依赖关系,TaskScheduler任务调度模块负责具体启动任务,监控和汇报任务运行情况。
spark所有的操作都围绕弹性分布式数据集(RDD)进行,这是一个有容错机制并可以被并行操作的元素集合,具有只读、分区、容错、高效、无需物化、可以缓存、RDD依赖等特征。
目前有两种类型的基础RDD:
接收一个已经存在的scala集合,然后进行各种并行计算。
并行化集合是通过调用SparkContext的parallelize方法,在一个已经存在的scala集合上创建的(一个Seq对象)。
集合的对象将会被拷贝,创建出一个可以被并行操作的分布式数据集。
例如:
val rdd = sc.parallelize(Array(1 to 10))
// 根据能启动的executor的数量来进行切分多个slice,每个slice启动一个task来进行处理
val rdd = sc.parallelize(Array(1 to 10), 5)
// 指定了partition的数量
在一个文件的每条记录上运行函数。只要文件系统是HDFS,或者hadoop支持的任意存储系统即可。
spark可以将任何hadoop所支持的存储资源转换成RDD。如:
ps:这两种类型的RDD都可以通过相同的方式进行操作,从而获得子RDD等一系列扩展,形成Lineage血统关系图。
RDD支持两类操作:
Spark可以使用 persist 和 cache 方法将任意 RDD 缓存到内存、磁盘文件系统中。缓存是容错的,如果一个 RDD 分片丢失,可以通过构建它的 transformation自动重构。被缓存的 RDD 被使用的时,存取速度会被大大加速。一般的executor内存60%做 cache, 剩下的40%做task。
Spark中,RDD类可以使用cache() 和 persist() 方法来缓存。cache()是persist()的特例,将该RDD缓存到内存中。而persist可以指定一个StorageLevel。StorageLevel的列表可以在StorageLevel 伴生单例对象中找到。
Spark的不同StorageLevel ,目的满足内存使用和CPU效率权衡上的不同需求。我们建议通过以下的步骤来进行选择:
在应用开发中,一个函数被传递给Spark操作(例如map和reduce),在一个远程集群上运行,它实际上操作的是这个函数用到的所有变量的独立拷贝。这些变量会被拷贝到每一台机器。通常看来,在任务之间中,读写共享变量显然不够高效。然而,Spark还是为两种常见的使用模式,提供了两种有限的共享变量:广播变量和累加器。
累加器只支持加法操作,可以高效地并行,用于实现计数器和变量求和。
Spark 原生支持数值类型和标准可变集合的计数器,但用户可以添加新的类型。
只有驱动程序才能获取累加器的值。
在通过spark-submit提交任务时,可以通过添加配置参数来指定。
可以增大driver的内存参数:spark.driver.memory(default 1G)。
ps:
这个参数用来设置Driver的内存。
在spark程序中,sparkContext,DAGScheduler都是运行在Driver端的。对应rdd的stage切分也是在driver端运行。如果用户自己写的程序有过多的步骤,切分出过多的stage。
这部分信息消耗的是driver的内存,这个时候就需要调大driver的内存。
这种溢出的原因是在单个map中产生了大量的对象导致的。例如:
rdd.map(x=>for(i <- 1 to 10000) yield i.toString)
这个操作在rdd中的每个对象都产生了10000个对象,这肯定容易产生内存溢出的问题。
针对这种问题,在不增加内存的情况下,可以通过减少每个task的大小,以便达到每个task即使产生大量的对象Executor的内存也能够装得下。
具体做法:
在会产生大量对象的map操作之前调用repartition方法,分区成更小的块传入map。例如
rdd.repartition(10000).map(x=>for(i <- 1 to 10000) yield i.toString)
ps:面对这种问题,不能使用rdd.coalesce方法,因为他只能减少分区,不能增加分区,不会有shuffle的过程。
数据不平衡除了有可能导致内存溢出外,也有可能导致性能的问题。
解决办法:
一般都是shuffle后,单个文件过大导致的。
在spark中,join、reduceByKey这一类型的过程,都会有shuffle的过程,在shuffle的使用,需要传入一个partitioner,大部分spark中的shuffle操作,默认的partitioner都是hashpartitioner,默认值是父RDD中最大的分区数,这个参数通过spark.default.parallelism控制(在spark-sql中用spark.sql.shuffle.partitions)。
spark.default.parallelism参数只对hashpartitioner有效,所以如果是别的partitioner或者自己实现的partitioner就不能使用spark.default.parallelism这个参数来控制shuffle的并发量了。
如果是别的partitioner导致的shuffle内存溢出,就需要从partitioner的代码增加partitions的数量。
使用rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)代替rdd.cache()。
rdd.cache()和rdd.persist(Storage.MEMORY_ONLY)是等价的。
在内存不足的时候,rdd.cache()的数据会丢失,再次使用时会重算。
而rdd.persist(StorageLevel.MEMORY_AND_DISK_SER)在内存不足时会存储在磁盘,避免重算,只是消耗I/O时间。
1)下载解压。
2)重命名spark-defaults.conf.template为spark-defaults.conf,并在文件末尾追加如下语句。
spark.eventLog.enabled true
spark.eventLog.dir hdfs://master:9000/spakLogs
spark.eventLog.compress true
spark.yarn.historyServer.address master:18080
3)编辑slaves
slave1
slave2
4)最后将master节点配置好的整个spark文件夹拷贝至各slave节点
scp -r spark hadoop@slave1:~
scp -r spark hadoop@slave2:~
ps:spark引入zookeeper搭建高可用的spark集群(HA)
多数task执行速度较快,少数task执行时间非常长,或者等待很长时间后提示你内存不足,执行失败。
发现数据倾斜的时候,不要急于提高executor的资源,修改参数或是修改程序,首先要检查数据本身,是否存在异常数据。
如果任务长时间卡在最后1个(或几个)任务,首先要对key进行抽样分析,判断是那些key造成的。选取key,对数据进行抽样,统计出现的次数,根据出现次数大小排序取出前几个。
df.select("key").sample(false,0.1).(k => (k,1)).reduceByKey(+).map(k => (k._2,k._1)).sortByKey(false).take(10)
如果发现多数数据分布都较为平均,而个别数据比其他数据大上若干数量级,则说明发生了数据倾斜。
对于1、2情况,直接对数据进行过滤即可(因为该数据对当前业务不会产生影响)
对于3情况,一般可以如下处理:
假设说有倾斜的Key,我们给所有的Key加上一个随机数,然后进行reduceByKey操作。
此时同一个Key会有不同的随机数前缀,在进行reduceByKey操作的时候原来的一个非常大的倾斜的Key就分而治之变成若干个更小的Key,不过此时结果和原来不一样,怎么破?
进行map操作,目的是把随机数前缀去掉,然后再次进行reduceByKey操作。(当然,如果你很无聊,可以再次做随机数前缀)。
这样我们就可以把原本倾斜的Key通过分而治之方案分散开来,最后又进行了全局聚合。
ps:
注意1:如果此时依旧存在问题,建议筛选出倾斜的数据单独处理。最后将这份数据与正常的数据进行union即可。
注意2: 单独处理异常数据时,可以配合使用Map Join解决。
将多份数据进行关联是数据处理过程非常普遍的用法,不过在分布式计算系统中,这个问题往往会变得非常麻烦。
框架提供的join操作一般会将所有数据根据key发送到所有的reduce分区中去,也就是shuffle的过程,造成大量网络以及磁盘IO消耗,运行效率及其低下,这个过程一般被称为reduce-side-join。
reduce-side-join的缺陷在于会将key相同的数据发送到同一个partition中进行运算,大数据集的传输需要长时间的IO,同时任务并发度收到限制,还可能造成数据倾斜。
如果其中有张小表的化,我们可以自己实现在map端实现数据关联,跳过大量数据进行shuffle的过程,运行时间得到大量缩短,根据不同数据可能会有几倍到数十倍的性能提升。
将少量的数据转化为map进行广播,广播会将此map发送到每个节点中,如果不进行广播,每个task执行时都会去获取该数据,造成了性能浪费。
对大数据进行遍历,使用mapPartition而不是map,因为mappartition是在每个partition中进行操作,因此可以减少遍历时新建broadCastMap.value对象的空间消耗,同时匹配不到的数据也不会返回。
在海量数据中匹配少量特定数据时。
在kafka0.10版本之前有2种方式于sparkStreaming整合一种时receiver,一种是directt。
采用kafka高级api,利用receiver接收器来接受kafka topic中的数据。
从kafka接收来的数据会存储在spark的executor中,之后spark streaming提交的job会处理这些数据。
kafka中topic的偏移量是保存在zk中的。
ps:
在spark1.3之后,引入了Direct方式。不同于receiver方式,direct方式没有receiver这一层。
其会周期性的获取kafka中每个topic的每个partition中的最新offsets,之后根据设定的maxRatePerPartition来处理每个batch。
(设置spark.streaming.kafka.maxRatePerPartition=10000。限制每秒钟从topic的每个partition最多消费的消息条数)。
如果要读取多个partition,不需要创建多个输入Dstream,然后对他们进行union操作。
spark会创建跟kafkapartition一样多的RDD partition,并且会并行从kafka中读取数据。
所以kafka partition和RDD partition之间,有一个一对一的映射关系。
只要kafka中做了数据的副本,那么就可以通过kafka的副本进行恢复。
避免了receiver的WAL复制数据(分布式文件系统的预写日志)的步骤。
使用kafka的简单api,sparkStreaming自己就负责追踪消费的offset,并保存在checkpoint中。
spark自己一定是同步的,因此可以保证数据是消费一次且仅消费一次。
不过需要自己完成将offset写入zk的过程,在官方文档中有相应介绍。
ps:
sparkStreaming程序自己消费完成后,自己主动去更新zk上面的偏移量。
也可以将zk中的偏移量保存在mysql或者redis数据库中,下次重启的时候,直接读取mysql或者redis中的偏移量,获取到上次的偏移量,接着读取数据。
spark通过这个参数spark.deploy.zookeeper.dir指定master元数据在zookeeper中保存的位置。
包括worker,master,application,executors.standby节点要从zk中获得元数据信息,恢复集群运行状态,才能继续对外提供服务、作业提交资源申请等,在恢复前时不能接收请求的。
因为程序在运行之前,已经向集群申请过资源,这些资源已经提交给driver了,也就是说已经分配好资源了。
这是粗粒度分配,一次性分配好资源后不需要再关心资源分配,在运行时让driver和executor自动交互。弊端是如果资源分配太多,任务运行完不会很快释放,造成资源浪费。
这里不适合细粒度分配的原因是因为提交太慢。
启动时就分配好资源,程序启动,后序具体使用就使用分配好的资源,不需要再分配。
好处:作业特别多时,资源复用率较高。使用粗粒度。
缺点:容易浪费资源,如果job有1000个task,完成999个,还有一个没完成,就会有999个资源闲置在那里,会造成资源大量浪费。
用资源的时候分配,用完了就立即回收资源。
启动会麻烦一点,启动一次就分配一次,申请资源会比较麻烦。
Spark不一定非要跑在hadoop集群,可以在本地,起多个线程的方式来指定。将Spark应用以多线程的方式直接运行在本地,一般都是为了方便调试,本地模式分三类
分布式部署集群,自带完整的服务。
资源管理和任务监控是spark自己监控。
这个模式也是其它模式的基础。
分布式部署集群,资源和任务监控交给yarn管理。
但是目前仅支持粗粒度资源分配方式。
包含cluster和client运行模式。
官方推荐这种模式(当然,原因之一是血缘关系)。
正是由于spark开发支出就考虑到支持mesos,所以目前而言,spark运行在mesos上会比yarn上更加灵活,更加自然。
用户可以选择两种调度模式之一运行自己的应用程序:
是其他组件的基础,spark的内核,主要包含:优先循环图、RDD、Lingage、Cache、broadcat等,并封装了底层通讯框架,是spark的基础。
一个对实时数据流进行高通量、容错处理的流式处理系统。
可以对多种数据源(如kafka、flume、twitter、zero、和tcp套接字)进行类似map、reduce和join等复杂操作。
将流式计算分解成一系列短小的批处理作业。
shark是sparkSQL的前身,spark sql的一个重要特点是其能够统一处理关系表和RDD,使得开发人员可以轻松的使用sql命令进行外部查询,同时进行更复杂的数据分析。
一个用于海量数据上运行交互式sql查询的大规模并行查询引擎,运行用户通过权衡数据精度来提升查询相应时间,其数据的精度被控制在运行的误差范围内。
spark生态圈的一部分,专注于机器学习,让机器学习的门槛更低,让一些可能并不了解机器学习的用户也能方便的使用MLBase。MLBase分为四部分:MLlib、MLI、ML Optimizer和MLRuntime。
spark中用于图并行计算的组件。
主要功能:
ps:
两者并没有大的差别。
都是将mapper(spark中式ShuffleMapTask)的输出进行partition,不同的partition送到不同的reducer(spark里reducer可能是下一个stage里的shufflemaptask,也可能是resulttask)。
reducer以内存作缓冲区,边shuffle边aggregate数据,等到数据aggregate好以后进行reduce()(spark里可能是后续的一系列操作)。
两者差别不小。
hadoop mapreduce是sort-based,进入combine()和reduce()的records必须先sort。这样的好处在于combin/reduce可以处理大规模的数据,因为其输入数据可以通过外部排序得到(mapper对每段数据先做排序,reducer的shuffle对排好序的每段数据做归并)。
目前spark默认选择的是hash-based,通常使用hashmap来对shuffle来的数据进行aggregate,不会对数据进行提前排序。如果用户需要经过排序的数据,那么需要自己调用类似的sortByKey()的操作。
如果你是spark1.1的用户,可以将spark.shuffle.manager设为sort,则会对数据进行排序。在spark1.2中,sort将作为默认的shuffle实现。
两者也有不少差别。
mapreduce将处理流程划分出明显的几个阶段:map、spill、merge、shuffle、sort、reduce等。
每个阶段各司其职,可以按照过程式的编程思想来逐个实现每个阶段的功能。
在spark中,没有这样明确的阶段,只有不同的stage和一系列的transformation,所以spill、merge、aggregate等操作需要蕴含在transformation中。
ps:
如果将map端划分数据、持久化数据的过程称为shuffle write,而将reducer读入数据、aggregate数据的过程称为shuffle read。
那么在spark中,问题就变为怎么在job的逻辑或者物理执行图中加入shuffle write和shuffle read的处理逻辑?以及两个处理逻辑应该怎么高效实现?
shuffle write由于不要求数据有序,shuffle write的任务很简单,将数据partition好,并持久化。
之所以要持久化,一方面是要减少内存存储空间压力,另一方面也是为了fault-tolerance。
两者都是用mr模型来进行并行计算。
RDD中文意思是弹性分布式数据集,简单的理解成一种数据结构,是spark框架上的通用货币。
所有算子都是基于rdd来执行的,不同的场景会有不同的rdd实现类,但是都可以进行互相转换。
rdd执行过程中会形成dag图,然后形成lineage(血缘)保证容错性等。
从物理的角度来看rdd存储的是block和node之间的映射。
管理集群和节点,不参与计算。
计算节点,进程本身不参与计算,给master汇报存活状态(workerid)。
运行程序的main方法,创建sparkcontext对象。
控制整个application的生命周期,包括DAGScheduler和taskscheduler等组件。
用户提交程序的入口。
用户在client端提交作业后,会由driver运行main方法并创建sparkcontext上下文。
执行rdd算子,形成dag图输入dagscheduler,按照rdd之间的依赖关系划分stage输入taskscheduler。
taskscheduler会将stage划分为taskset分发到各个节点的executor中执行。
spark调优比较复杂,但是大体可以分为三个方面来进行。
防止不必要的jar包分发,提高数据的本地性,选择高效的存储格式,如parquet。
设置合适的资源量,设置合理的jvm,启用高效的序列化方法如kyro,增大offhead内存等。。。
ps: