Spark Core 基础

  我使用的Hadoop是cdh版本的,官网的spark二进制文件使用的是Apache版本的,所以必须要重新编译过的。

一、编译

  前期准备:

  • java 1.8
  • maven 3.3.9
  • scala 2.11
  • spark-2.3.1.src.tgz
      由于我用的是cdh版本的hadoop,所以在spark源码包的pom.xml中加入
<repository>
    <id>cloudera</id>
    <name>cloudera Repository</name>
    <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repositor>

  因为想生成的是打包好的文件所以使用如下命令进行编译

./dev/make-distribution.sh \
--name 2.6.0-cdh5.7.0 --tgz \
-Phadoop-2.6 \
-Dhadoop.version=2.6.0-cdh5.7.0 \
-Phive -Phive-thriftserver \
-Pyarn

二、RDD

  什么是RDD?一种弹性的分布式数据集,拥有5大特性:

  • 拥有多个分区
  • RDD中的每一个方法作用于每个分区
  • RDD可以转换成另一个RDD,(分区数据出错,可以根据依赖重做)
  • 可选,分区是以键值对存在
  • 可选,计算数据本地性
      对于第四点,如果RDD里面存的数据是key-value形式,则可以传递一个自定义的Partitioner进行重新分区,例如这里自定义的Partitioner是基于key进行分区,那则会将不同RDD里面的相同key的数据放到同一个partition里面。

三、术语

3.1 Application

  Application = 1 driver + N executors。应用程序一般来说,一个SparkContext就是一个应用程序,包含了一个driver和多个executes。有自己的WebUI界面(默认4040),生命周期到sc.stop()。

3.2 Driver

  是一个进程,是一个运行main程序和创建一个SparkContext。

3.3 Cluster manager

  Spark可以运行很多模型之上,Yarn、Mesos、Standalone。是用来沟通集群,获取资源。就是Spark shell中 --master

3.4 Worker Node

  运行应用程序节点的节点,在Yarn中就是NodeManager。

3.5 Executor

  是一个进程,启动在WorkerNode下,运行task任务,缓存数据在内存中。每个Application 拥有自己的Executor。可以想象成Yarn中的container。

3.6 Task

  Executor端执行的最小工作单元。task 数量 == partition 数量。

3.7 Job

  就是action产生一个job,Transformation是不会产生Job的。

3.8 stage

  和shuffle有关,没有shuffle的都是一个stage。Application ⇒ n job ⇒ n stages ⇒ n tasks

这里写图片描述

  一个Spark Application中,可以有很多的Action算子所以,就有很多的Job。一个Job分成很多的Stage(可以理解成逻辑执行步骤),每一个Stage要处理很多并行化工作,所以每个Stage有很多的Task处理并行化任务。总结:1 Application ==> n Jobs ==> x*n Stages ==>x*n*y Tasks。
  那Stage和Task数量和job到底什么关系呢?其实1个Task==1个partition数量,RDD的partition数量多少个决定了task数量。Stage的数量和Shuffle有关,如果没有Shuffle过程都是一个Stage(逻辑过程),如果Shuffle,根据不同的情况不定。

四、Spark缓存

  Spark持久化(缓存),就是将一个数据集存储在executor的内存或者磁盘上,cache是一个lazy的过程,如果要去除一个RDD的缓存使用的是unparsist(true),这个不是lazy过程。在RDD中,标识一个持久化的RDD可以使用cache()和parsist(),这两个方法底层都是parsist,只是具体的模式不一样。
  持久化有一个副本的数量的选择,但是会更加再用空间,但是容错会更好。建议不设置副本数。

五、依赖

  Spark的RDD介绍中的R是弹性的意思。含义就是,如果在某个分区的数据错了,就会根据依赖关系从父RDD中的partition中重新生成,从而不需要整个RDD重算。
  依赖分为窄依赖和宽依赖。窄依赖:一个父RDD的partition最多被子RDD的一个partition使用一次;宽依赖:一个父RDD的partition可能被子RDD的partition使用多次。
这里写图片描述
  可以如上图所示,窄依赖是没有shuffle,宽依赖需要shuffle,有shuffle就会生成新的stage。所以在宽依赖的时候,弹性计算需要重算整个父RDD。能避免宽依赖就尽量避免宽依赖,因为窄依赖在弹性恢复的时候效率更高,但是如果想解决数据倾斜的问题还是需要宽依赖。
  下图就可以很好解释了stage和shuffle之间关系。
这里写图片描述

六、Spark On Yarn

  将Spark的作业交到Yarn中执行,spark就仅仅是个客户端。先回顾下Yarn的工作流程,如下图。
这里写图片描述
  如果要是用Yarn的集群模式,就必须先告诉Spark要使用的Hadoop或者Yarn的环境。必须指明环境变量HADOOP_CONF_DIR或者YARN_CONF_DIR指向${HADOOP_HOME}/etc/hadoop。这个可以写在环境变量中或者spark-env.sh中。

6.1 Yarn模式

  Spark 跑Yarn中有两种模式,一种就是client和cluster,这两种其实本质上就是driver的所处在集群中的位置不同。client的driver是在客户端,cluster的driver是在AM中,运行在默认指定 yarn,就是指 yarn-client。这个两种方式在生产上的选择取舍的依据:

  • Driver所处的位置
  • Executor和deriver的通讯频繁度
  • 日志存储的位置

  说明,因为Client的Driver在提交作业的机器本地,所以,会不会存在集群和提交作业的机器之间有网络隔离,导致driver和executor无法通讯,即使通信成功,如果都是一台机器提交作业,各个作业和自己的Executor之间通讯压力大不大,会不会导致网络IO堵塞。虽然cluster的driver在AM中,但是这样子Spark产生的日志没法再Client中呈现,这也是一个问题。

6.2 Yarn模式的问题

  在运行Spark作业的时候会处理的非常的慢,通过日志会得到几个可能卡顿的地方。

  • SparkContext 加载应用程序的jar包
  • 传送资源到AM container
  • AM申请资源

  在第二点,资源上传到AM container中,是要上传一些Spark所要依赖的jar包和Spark conf的配置文件,这个jar会非常的大,非常耗费IO资源,但是可以优化,就是配置spark.yarn.jars 或者 spark.yarn.archive,就可以不用打包上传到分布式缓存中。通常的做法就是:

  1. 在HDFS上上传需要的jars
  2. 在${SPARK_HOME}/conf/spark-default.conf中配置jars的地址,如:spark.yarn.jars hdfs://master:9000/opt/spark/jars/*
  3. 重启任务
6.3 日志

  虽然Yarn中有日志体系,可以通过8088端口查看,但是有时候后我们更关心Spark日志,但是Spark的driver的4040端口在Job运行完了以后就关闭了。这个时候需要将日志接到某个地方,然后再用某个工具读取出来。这个工具就叫做history-server。
  如何配置每个提交Spark的作业把日志记录到某个地方,在${SPARK_HOME}/conf的 spark-defaults.conf中配置spark.eventLog.enabled使能,并且配置日志记录的位置 spark.eventLog.dir 。
  如何配置工具读取日志,先配置所要读取的日志的地址。在${SPARK_HOME}/conf/spark_env.sh 中配置 SPARK_HISTORY_OPTS="-Dspark.history.fs.logDirectory=hdfs://hadoop000:9000/dirrctory -Dspark.history.ui.port=18080"。然后找一台机器启动${SPARK_HOME}/sbin/start-history-server.sh,然后通过这台机器的18080端口,就可以查询到spark日志。

七、共享变量

  正常情况下,需要有一个东西,每个task都要用到的,执行的时候会拷贝到每个task中,每个task的变量相互不影响,这个就是共享变量。共享变量分两种,广播变量和计数器。

7.1 计数器

  计数器仅仅去完成计数或者求和的过程,它可以使用added方式。每个task会保留自己的值,会后的值会被两个task加起来。

val accum = sc.longAccumulator("My Accumulator")
sc.parallelize(Array(1, 2, 3, 4)).foreach(x => accum.add(x))
accum.value

  应用:可以计数每个作业不符合业务数据的数据量。

7.2 广播变量

  可以理解成Hive中的MapJoin,将小表放到分布式缓存中。所以该变量只读,而且是保持在每一台机器上(其实每个executor)而不是每一个task有一个备份。类似分布式缓存。

val infoRDD = sc.parallelize(Array((1,"hehe"),(2,"haha"),(3,"huhu"))).collectAsMap()
val detailRDD = sc.parallelize(Array((1,"hehe",18))).map(x => (x._1,x))
val broadcast = sc.broadcast(infoRDD)      //把infoRDD 当成小表,广播出来,在内存中
detailRDD.mapPartitions(x => {
      val map = broadcast.value
      for((key, value) <- x if(map.contains(key)))
        yield (key,map.get(key).getOrElse(""),value._3)
    }).foreach(println)

八、调优

8.1 序列化

  Spark是分布式计算框架,所以,是转移计算,而不是说用来转移数据的。序列化可以减少网络传输的IO、执行性能等等。
  序列化有两种一种就是Java序列化和Kryo序列化。java序列化比较普遍化,但是比较慢。Kryo序列化对象比较快,而且体积更小,但是比较复杂。
  Java的序列化比较简单,只要继承 java.io.Serializable就可以了。
  Kryo序列比较复杂,spark需要先配置使用Kryo序列化。在${SPARK_HOME}/conf/spark-defaults.conf下,设置spark.serializer org.apache.spark.serializer.KryoSerializer,然后还要在SparkContext创建前注册一下conf.registerKryoClasses(Array(classOf[MyClass1], classOf[MyClass2])),不然没有这么好的序列化效果。

8.2 内存的调优

  如果需要将一个对象cache到内存或者说一个对象索要占用较大内存,就需要这部分调优。
  java的对象在内存中容易访问,但是会膨胀几倍的内存空间。Spark 中内存涉及到两个部分:计算时的内存和存储时的内存。

  • execution: computation
  • storage: caching and propagaring

  计算的内存和存储的内存共享所有的被分配的内存空间。如果计算的所需要的空间不足,会挤压存储的空间。
  当你不知道你的数据需要占Executor多少的内存,可以这么做,将数据封装成RDD,cache到内存中(注意,cache是个lazy操作),然后在Spark ui上的storage上查看占用多少存储空间。或者你不想把它缓存,就想知道占用多少空间,可以使用**SizeEstimator.estimate()**方法得到占用的大小。两者计算出来的大小有点出入
  所以为了得到较小的内存占用方式,所以建议在程序设计的时候尽量使用数组,和基本数据结构,不要使用多层引用。具体可以参考官网的内存优化方式。

8.3 RDD的缓存

  根据8.2的内容可知,当有一个重要的RDD需要重复读取,可以cache到集群中。cache有很多的方式,比如副本数、可序列化、内存还是本地磁盘等。这里还有一个知识点:cache和persist的区别。

  • cache:底层调用persist()
  • persist:默认只使用内存缓存方式,副本为1,反序列化

  所以这个persist很灵活,可以有各种模式的缓存策略。如果需要取消缓存就使用unpersist,这是个action。
  如果executor上内存不够了,那cache会有部分partition不能够缓存到内存,剩余的partition只能在用到的时候通过依赖重算。

8.4 Spark on Yarn 启动优化

  一个作业用spark-submit提交到Spark on Yarn 上会很慢,通过全打印日志查找原因,可能的原因如下:
   1,当AM申请到时候,节点上如果没有指定spark的jar包路径,这个时候会 打包 Spark运行必备的jar包压缩包上传到HDFS上(是${SPARK_HOME/jars}下的文件),然后有需要的节点取出zip包,运行Spark环境。这个参数:spark.yarn.jars或者spark.yarn.archive。这个参数在${SPARK_HOME}/conf/spark-defaults.conf中配置spark.yarn.jars这个参数。这个参数配起来有点意思,

spark.yarn.jars      hdfs://dbmtimehadoop/tmp/spark/lib_jars/   ##报错
spark.yarn.jars      hdfs://dbmtimehadoop/tmp/spark/lib_jars/*.jar  ##可行
spark.yarn.jars      hdfs://dbmtimehadoop/tmp/spark/lib_jars/*      ##可行
spark.yarn.jars      hdfs://dbmtimehadoop/tmp/spark/lib_jars/activation-1.1.1.jar,hdfs://dbmtimehadoop/tmp/spark/lib_jars/antlr-2.7.7.jar,hdfs://dbmtimehadoop/tmp/spark/lib_jars/antlr4-runtime-4.5.3.jar,hdfs://dbmtimehadoop/tmp/spark/lib_jars/antlr-runtime-3.4.jar  ##可行

   2,除了jar包,还有配置文件也会打包上传,${SPARK_HOME\conf}下的文件。
   3,AM向RM申请container的资源,所以会不停申请资源,申请到了才开始运行。这是个优化点。

九、日志持久化

  web UI的默认端口是http://driver:4040 端口,但是这个端口在你spark-submit提交的作业结束后就不存在了,如何才能让spark作业产生的日志数据永远可以被查看,就需要存在日志持久化。
  需要开启两个参数(${SPARK_HOME/conf/spark-defaults.conf}文件中),spark.eventLog.enabled 和 spark.eventLog.dir。这两个参数是提交的时候告诉spark 打开日志记录功能,并日志记录在dir的指定路径下。
  既然日志被记录了,怎么查看。需要找一台机器运行Spark HistoryServer服务(${SPARK_HOME/sbin/start-history-server.sh})。然后通过http:\history-server:18080 地址访问。当然,需要指定HistoryServer读取目录的地址,配置${SPARK_HOME/conf/spark-env.sh}文件中。

SPARK_HISTORY_OPTS="-Dspark.history.fs.logDirectory=hdfs://hadoop000:9000/directory -Dspark.history.ui.port=18080"
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值