spark入门到精通

1、对于spark object类型的类,直接拿来用就好了,不用new
2、rdd join

val list1 = List(1, 2)
val list2 = List(2, 3)
val t1 =sc.parallelize(list1).map(x => ("kkkk", x))
val t2 = sc.parallelize(list2).map(x => ("kkkk", x))
// 两个rdd中,同一个key值的放到一个元组中。即u为(kkkk,(Some(1),Some(2)))
val u = t1.fullOuterJoin(t2)		

3、一个节点上可以有多个executor进程
4、报错:java.net.BindException: Address already in use: Service ‘sparkDriver’ failed after 16 retries!
每一个Spark任务都会起一个Driver端口,即SparkUI,默认为4040,如果被占用则随机选取端口重试,默认会重试16次。16次重试都失败后,会放弃任务的运行。使用jps命令查看当前节点上提交的任务数量,如果当前节点的任务数超过了16个,就会造成这样的错误。
解决办法:使用spark-submit提交任务时,在命令行中添加 –conf spark.port.maxRetries=100,将100改为1,可以复现此BUG
5、spark中的collect(收集)操作是将远程数据通过网络传输到driver端本地,如果数据量特别大的话,会造成很大的网络压力,更为严重的问题是会造成driver端的内存溢出。所以对于数据量小的rdd可以用collect,数据量大的就不要用了
6、executor执行的时候,用的内存可能会超过executor-memoy,所以会为executor额外预留一部分内存。spark.yarn.executor.memoryOverhead代表了这部分内存
报错“Container killed by YARN for exceeding memory limits. 9.8 GB of 9.5 GB physical memory used. Consider boosting spark.yarn.executor.memoryOverhead.”,可以设置spark.yarn.executor.memoryOverhead=1000, 单位为兆
7、spark报错:java.lang.StackOverflowError
栈空间不足:增加执行时的栈空间; --conf spark.executor.extraJavaOptions=-Xss4096k或者spark.executor.extraJavaOptions=-Xss8192k 特别注意,不能加引号
8、spark写hive的方式

  • rdd.saveAsTextFile(“hive表对应的hdfs路径”)
    hiveContext.sql(“alter table test.test add if not exists partition(day=‘2019-05-27’, hour=‘10’)”) #还需要增加分区表当前的分区值
  • 直接用spark sql or hive sql
    insert overwrite table test.test partition(day=’ d a y ′ , h o u r = day',hour= day,hour={hour.toInt}) select * from …

9、重分区的数目PartitionsNum对性能有极大影响,对于数据量较大的情况(一天几亿),这个值设置70比较合适,如果设置成3000,性能极慢(也可能跟具体的数据形式相关,反正如果有性能问题,可以先尝试调整重分区数目,试试效果)
resultRdd.repartition(PartitionsNum).saveAsTextFile(outputPath, classOf[LzopCodec])
10、spark处理大数据时,记得加try catch, 否则因为一条记录有问题报错,整个程序就会停止
11、rdd01 = resultRdd.repartition(10) 假如resultRdd有20个分区,那么执行repartition的task会有20个,而不是10个。rdd01会有10个分区,rdd01后续操作会有10个task
12、分区字段,由string修改为int ,在存储和使用的性能比string 要好很多
13、spark sql写hive

  • 1)定义case class nginxitem(sdkdata: String) 可以将所有字段内容拼接成一个string,然后在写sparksql语句的时候对这个string进行split。也可以直接在这里定义多个字段…
  • 2)定义val schema = StructType(Array(StructField(“sdkdata”,
    DataTypes.StringType)))
  • 3)将rdd先转换成rowRDD, 然后按照schema进行转换 val df =
    hiveContext.createDataFrame(log_rdd.map(Row(_)), schema)
  • 4)insert overwrite table t a b l e p a r t i t i o n ( d a y = ′ table partition(day=' tablepartition(day=day’) select
    split(sdkdata,’\t’)[0] call_id,…from …

14、rdd.repartition可以防止数据倾斜,另外在将rdd保存到hdfs之前,对rdd重分区,这样可以自定义输出的hdfs的文件数,可防止生成小文件
15、spark累加器在driver端声明,在transform里面使用,最后用rdd的action操作触发计算后,在driver端取其值(使用value方法)
使用Accumulator时,为保证准确性,有几种方式:

  • transform中执行的累加器更新,只使用一次action操作。
  • transform中执行的累加器更新,被使用多次时,则使用cache或persist操作切断依赖。
累加器的错误用法
	val accum= sc.accumulator(0, "Error Accumulator")
	val data = sc.parallelize(1 to 10)
	//用accumulator统计偶数出现的次数,同时偶数返回0,奇数返回1
	val newData = data.map{x => {
	  if(x%2 == 0){
		accum += 1
		  0
		}else 1
	}}
	//使用action操作触发执行
	newData.count
	//此时accum的值为5,是我们要的结果
	accum.value

	//继续操作,查看刚才变动的数据,foreach也是action操作
	newData.foreach(println)
	//上个步骤没有进行累计器操作,可是累加器此时的结果已经是10了
	//这并不是我们想要的结果
	accum.value

16、hadoop读取文件
1)读取本地文件: sc.textFile(file:///usr/local/ljy.txt)
2)读取hdfs文件: sc.textFile(hdfs://usr/local/ljy.txt) 或者sc.textFile(/usr/local/ljy.txt)
3)val fileRdd = sc.hadoopFileLongWritable, Text, TextInputFormat
textFile 也是调用的 hadoopFile, hadoopFile比较灵活一些,比如可以指定压缩格式之类的
17、spark读取多个hdfs文件 sparkContext.textFile(“hdfs://localhost:9000/test/log1, hdfs://localhost:9000/test/log2”)
18、spark 参数调优-----动态资源分配(Dynamic Resource Allocation)
使spark资源按需分配:高峰和低峰Spark Streaming要处理的数据量相差可能三倍以上,预分配资源会导致低峰的时候资源的大量浪费。
spark.dynamicAllocation.enabled 是否开启动态资源配置,根据工作负载来衡量是否应该增加或减少executor,默认false
spark.dynamicAllocation.minExecutors 动态分配最小executor个数,在启动时就申请好的,默认0
spark.dynamicAllocation.maxExecutors 动态分配最大executor个数,默认infinity
动态资源分配策略:application会在task因没有足够资源被挂起的时候去动态申请资源
资源回收策略:当application的executor空闲时间超过spark.dynamicAllocation.executorIdleTimeout(默认60s)后,就会被回收。
19、spark读取hdfs文件形成的RDD的分区数和HDFS文件的split分片数一致,而分片数多数情况下和block数一致,分区数又和task数一致,注意,这里的task数是指运行rdd的总的task数,不是同一时间运行的task数
对于reduceByKey等需要shuffle而生成的RDD,其Partition数量依如下顺序确定:1. 方法的第二个参数 > 2. spark.default.parallelism参数 > 3. 所有依赖的RDD中,Partition最多的RDD的Partition的数量。
对于其他的RDD则其依赖于父RDD的分区个数。对于读取Hadoop文件的RDD,其默认的分区个数是HDFS块个数
除了上述情况,其他rdd的分区数和spark.default.parallelism参数一致
特别注意:spark.default.parallelism并不是指同一时间并行运行的task(core)的个数,而是在生成执行计划时决定分区数的一个设置。同一时间运行的task数还是由我们设置的总core数决定
spark.default.parallelism一般设置为总core数的3倍,比如总core=100,如果spark.default.parallelism <= 100,那么rdd有<= 100个分区,当有10个运行较快的core跑完之后,他们就空闲了,造成了资源浪费。所以我们的目的是保证所有core都在运行
20、spark读取hdfs文件形成的RDD的分区数和HDFS文件的split分片数一致,而HDFS文件的split分片数与block大小相关
如果一共读取100个hdfs文件,每个文件都大于128M, 那么就会有200个分区,如果每个文件都不大于128M,那么就只有100个分区
21、spark.default.parallelism
参数说明:该参数用于设置每个stage的默认task数量。这个参数极为重要,如果不设置可能会直接影响你的Spark作业性能。
  参数调优建议:Spark作业的默认task数量为500~1000个较为合适。很多同学常犯的一个错误就是不去设置这个参数,那么此时就会导致Spark自己根据底层HDFS的block数量来设置task的数量,默认是一个HDFS block对应一个task。
通常来说,Spark默认设置的数量是偏少的(比如就几十个task),如果task数量偏少的话,就会导致你前面设置好的Executor的参数都前功尽弃。试想一下,无论你的Executor进程有多少个,内存和CPU有多大,但是task只有1个或者10个,
那么90%的Executor进程可能根本就没有task执行,也就是白白浪费了资源!因此Spark官网建议的设置原则是,设置该参数为num-executors * executor-cores的2~3倍较为合适,比如Executor的总CPU core数量为300个,那么设置1000个task是可以的,
此时可以充分地利用Spark集群的资源。
22、spark优化数据结构
(1)优先使用数组及字符串,而不是集合类。也就是:优先使用array,而不是ArrayList,LinkedList,HashMap,既减少了额外信息的存储开销,还能使用原始数据类型(int)来存储数据而不是Integer,节省内存
(2)避免使用多层嵌套的对象结构,如:public class teacher{private List students=new ArrayList()}。如果非要使用此类结构,可以使用json字符串来保存数据
(3)可以的话,尽量用int代替String。spark应用中,id不要使用常用的uuid,因为无法转换为int,使用自增的id即可
23、spark多个executor在同一台机器上,也可能一台机器只有一个executor
24、spark参数配置优先级:代码里面SparkConf > spark-submit 或 spark-shell >spark-defaults.conf
25、调度平台配置一个spark执行脚本,这个执行脚本可以分配到多台执行节点。那么脚本里面用到的工具应放到hdfs上,每次运行脚本的时候从hdfs下到执行节点本地。否则如果工具放到本地的话,需要每个节点都放一个,这样很麻烦
26、sparksql小文件优化: hiveContext.sql(“set mapred.reduce.tasks=” + par_num)
par_num的数量就是最后输出的文件个数
27、spark join只适用于二元元组,不适用于多元元组
28、sparkGC调优

  • 1)spark.executor.extraJavaOptions=-XX:+UseG1GC
    使用 G1垃圾收集器,在gc成为瓶颈的时候可以显著提升性能。在G1算法中,采用了另外一种完全不同的方式组织堆内存,堆内存被划分为多个大小相等的内存块(Region),每个Region是逻辑连续的一段内存。对于executor占用的堆内存大的情况,可以通过-XX:G1HeapRegionSize增加G1区域大小

  • 2)垃圾收集的成本与Java对象的数量成正比,因此使用较少对象的数据结构(例如,Ints数组,代替LinkedList)将大大降低了成本

29、spark和Mapreduce快? 为什么快呢? 快在哪里呢?
1)内存迭代
2)RDD设计
3)算子的设计
30、spark二次排序:先用groupByKey对key分组,然后对key排序,然后再对同一个key里面的value进行排序。

input.map(x => {
	  val line = x.split(",")
	  (line(0), line(2).toInt)
	}).groupByKey().sortByKey(true).map(x => (x._1,x._2.toList.sortWith(_<_)))

31、spark推测式执行
推测任务是指对于一个Stage里面拖后腿的Task,会在其他节点的Executor上再次启动这个task,如果其中一个Task实例运行成功则将这个最先完成的Task的计算结果作为最终结果,同时会干掉其他Executor上运行的实例。spark推测式执行默认是关闭的,可通过spark.speculation属性来开启。
1)spark推测执行开启
设置 spark.speculation=true即可
2)spark开启推测执行的好处
推测执行是指对于一个Stage里面运行慢的Task,会在其他节点的Executor上再次启动这个task,如果其中一个Task实例运行成功则将这个最先完成的Task的计算结果作为最终结果,同时会干掉其他Executor上运行的实例,从而加快运行速度
3)问题
我们的spark任务会将计算结果写入kafka,再由logstash写入es。
最近由于kafka集群写入慢,甚至写不进去,spark任务直接卡住,为防止卡住的情况发生,加了推测执行,但发现跑出来的数据存在重复的情况。同一条数据写了2次,排查发现是由于推测执行的问题,像这种讲执行结果写入kafka的场景,不适用推测执行,因为一个task虽然没有执行完,但是一部分结果已经输出了,启动多个task就会造成数据重复,所以具体的配置还是要看应用的场景来做权衡
32、spark join优化

  • 1)spark RDD join的时候,如果数据量很大,且有数据倾斜,可以考虑将join两边的RDD的每一个key的后面都加上一个随机数,关联后再把随机数去掉,然后再次关联

  • 2)spark RDD join的时候,(左边的rdd一般是业务数据的rdd,这个rdd的key不能加倍,减少,即不能变化。右边的rdd是需要关联的数据。)
    如果数据量很大,且有数据倾斜,可以考虑将join左边的RDD的每一个key的后面都加上一个1到n的随机数,将右边rdd的每一个key膨胀成n倍,每个key后面也加上一个1到n的随机数,然后关联,再把随机数去掉即可
    如(lao,jingyao)join(lao,jingbing)
    左边的key加上一个1到3的随机数变成(lao2,jingyao)
    右边膨胀成3倍,变成(lao1,jingbing),(lao2,jingbing),(lao3,jingbing)
    最后两者join即可
    比如生产上经常会遇到rdd的key为null或者"",join的时候这些key就会全都分配到一个分区,后续的操作就会产生数据倾斜,就会很慢

  • 3)尽量减少join的数据量,比如过滤掉key为null或者"“的数据,因为null或者”"这个数据有时候往往会很大。很容易造成数据倾斜。还有某些对业务没影响的数据,尽量过滤出来,不要join

33、使用spark累加器的陷阱:以下两种情况都会导致累加器翻倍,可以通过加cache解决。或者在action算子里面用累加器而不是放在transform算子里面

1)transform中使用累加器,其后跟着两个action操作
		val accum = sc.longAccumulator("longAccum")
		val srcRDD = sc.parallelize(Array(1,2,3,4,5,6,7,8,9),2)
		val tmpRDD = map(n=>{
		  accum.add(1L)
		  n+1
		})
		val resultrRDD1 = tmpRDD.map.....
		val resultrRDD2 = resultrRDD1.map.....
		resultrRDD1.count
		println("accum1:"+accum.value)
		resultrRDD2.reduce(_+_)
		println("accum2: "+accum.value)
2)transform中使用累加器,其后跟着一个action操作,但是同一个rdd用了两遍
		val accum = sc.longAccumulator("longAccum")
		val srcRDD = sc.parallelize(Array(1,2,3,4,5,6,7,8,9),2)
		val tmpRDD = map(n=>{
		  accum.add(1L)
		  n+1
		})
		val resultrRDD1 = tmpRDD.map.....
		val resultrRDD2 = tmpRDD.map.....
		resultrRDD1.count
		println("accum1:"+accum.value)
	上面两种情况,除了用cache,也可以在使用了累加器的transform算子后面加个shuffle算子,因为经过shuffle的计算,计算结果会写到磁盘,当用第二次action或者复用rdd的时候,会Skipped Stages,不会重新计算,如:
	val accum = sc.longAccumulator("longAccum")
		val srcRDD = sc.parallelize(Array(1,2,3,4,5,6,7,8,9),2)
		val tmpRDD = map(n=>{
		  accum.add(1L)
		  n+1
		}).repartition(100)
		val resultrRDD1 = tmpRDD.map.....
		val resultrRDD2 = tmpRDD.map.....
		resultrRDD1.count
		println("accum1:"+accum.value)

34、spark shuffle解析

  • spark shuffle: http://sharkdtu.com/posts/spark-shuffle.html
    Spark在DAG调度阶段会将一个Job划分为多个Stage,上游Stage做map工作,下游Stage做reduce工作,其本质上还是MapReduce计算框架。Shuffle是连接map和reduce之间的桥梁,它将map的输出对应到reduce输入中,这期间涉及到序列化反序列化、跨节点网络IO以及磁盘读写IO等,所以说Shuffle是整个应用程序运行过程中非常昂贵的一个阶段

  • Hash Based:Shuffle的每个Mapper都需要为每个Reducer写一个文件,供Reducer读取,即需要产生M*R个数量的文件,如果Mapper和Reducer的数量比较大,产生的文件数会非常多。
    而Sort Based Shuffle的模式是:每个Shuffle Map Task不会为每个Reducer生成一个单独的文件;相反,它会将所有的结果写到一个文件里,同时会生成一个Index文件,

  • shuffle write阶段
    即map阶段,主要就是在一个stage结束计算之后,为了下一个stage可以执行shuffle类的算子(比如reduceByKey),而将每个task处理的数据按key进行“分类”。所谓“分类”,就是对相同的key执行hash算法,从而将相同key都写入同一个磁盘文件中,而每一个磁盘文件都只属于下游stage的一个task。在将数据写入磁盘之前,会先将数据写入内存缓冲中,当内存缓冲填满之后,才会溢写到磁盘文件中去。
    shuffle read阶段
    即reduce阶段,所以reducebykey等shuffle算子是属于这一阶段,而不是上一阶段
    shuffle read,通常就是一个stage刚开始时要做的事情。此时该stage的每一个task就需要将上一个stage的计算结果中的所有相同key,从各个节点上通过网络都拉取到自己所在的节点上,然后进行key的聚合或连接等操作。
    由于shuffle write的过程中,task给下游stage的每个task都创建了一个磁盘文件,因此shuffle read的过程中,每个task只要从上游stage的所有task所在节点上,拉取属于自己的那一个磁盘文件即可。
    shuffle read的拉取过程是一边拉取一边进行聚合的。每个shuffle read task都会有一个自己的buffer缓冲,每次都只能拉取与buffer缓冲相同大小的数据,然后通过内存中的一个Map进行聚合等操作。聚合完一批数据后,再拉取下一批数据,并放到buffer缓冲中进行聚合操作。以此类推,直到最后将所有数据到拉取完,并得到最终的结果。

  • spark遇到action算子就会提交一个job,会将这个action算子前面所有的transform算子都执行一遍。比如有两个action,每次都会从头往后开始算一遍,以下两种情况除外:
    1)用了cache
    2)action算子前面有shuffle操作,对于shuffle算子前面的stage,由于会将计算结果shuffle write,所以执行第二个action或者第二次使用同一rdd时,不会再重新计算shuffle算子前面的stage。但是shuffle算子后面的stage还是会执行两遍,重复计算。
    对于用了cache的操作,DAG Visualization上面还是会显示从头开始执行,是因为有些分区丢失了,需要重新计算,但这部分数据很小,所以不耗时。DAG Visualization上面会有个绿点 而且,如果内存不足,cache也会失效,但不会报错

  • 在job里面,如果遇到shuffle类型算子,就会分成两个stage,这个shuffle算子前面的代码作为1个stage,这个shuffle算子和它最后的代码最为另外一个stage。
    而spark web UI里面每个stage对应的Description的那行代码,都是这个stage的最后一个算子,如果这个stage最后一个算子是action算子,那么Description就是这个action算子。
    所以当产生数据倾斜的时候,spark web UI会显示某个stage报错,并不是指这个stage的Description的那行代码有倾斜,而是这行代码上面的某个shuffle算子有倾斜。比如Description的那行代码是个map或者是个filter,这行代码肯定是没有倾斜的,发生倾斜的是map前面的某个shuffle算子

  • 如果一个job里面有shuffle操作,对于这个job的第一个stage,只有shuffle write(写磁盘)而没有shuffle read操作,最后一个stage只有shuffle read操作,中间的stage两者都有。
    前一个stage shuffle write的数量,就是下一个stage shuffle read的数量。

  • 重分区大法好,如repartition或者join的时候指定分区,能在一定程度上缓解数据倾斜。数据量越大,需要指定的分区就越多越好。如果重分区无法解决,就要对key进行分析,采样key查看key分布,或者取出排名前几位的key,如:ExtractVaidRdd.map(x => (x.1, 1)).reduceByKey( + ).sortBy(._2, false).take(5).foreach()

  • 数据倾斜一般发生在shuffle算子上,而且rdd一般都要有key,即rdd[(String, String)]。对于rdd[String]类型,一般不会发生数据倾斜。除非rdd的每一行有很多一样,比如大量重复日志

  • 数据量突增,产生数据倾斜,先重分区看下效果。如果重分区(增大分区)还是不行,可以尝试把key打散,即加随机数。与此同时,也要重分区(加大分区),数据量越大,随机数和重分区数就要设置得越大

36、Locality Level
这几个值代表 task 的计算节点和 task 的输入数据的节点位置关系

  • PROCESS_LOCAL: 数据在同一个 JVM 中,即同一个 executor 上。这是最佳数据 locality。
  • NODE_LOCAL: 数据在同一个节点上。比如数据在同一个节点的另一个 executor上;或在 HDFS 上,恰好有 block
    在同一个节点上。速度比 PROCESS_LOCAL 稍慢,因为数据需要在不同进程之间传递或从文件中读取
  • NO_PREF: 数据从哪里访问都一样快,不需要位置优先
  • RACK_LOCAL: 数据在同一机架的不同节点上。需要通过网络传输数据及文件 IO,比 NODE_LOCAL 慢
  • ANY: 数据在非同一机架的网络上,速度最慢

37、spark cache级别

  • 1)MEMORY_ONLY
    使用未序列化的Java对象格式,将数据存在内存。如果内存不够存放所有的数据,则数据可能就不会进行持久化。下次对这个RDD执行算子操作时,那些没被持久化的数据,需要从源头处重新计算一遍。这是RDD的默认持久化级别

  • 2)MEMORY_AND_DISK
    使用未序列化的Java对象格式,优先尝试将数据保存在内存中。如果内存不够存放所有的数据,会将数据写入磁盘文件中,这是DataFrame的默认持久化级别

  • 3)MEMORY_ONLY_SER/MEMORY_AND_DISK_SER
    基本含义同MEMORY_ONLY/MEMORY_AND_DISK。唯一的区别是,会将RDD中的数据进行序列化,RDD的每个partition会被序列化成一个字节数组。这种方式更加节省内存,从而可以避免持久化的数据占用过多内存导致频繁GC
    这种级别比MEMORY_ONLY多出来的性能开销,主要就是序列化与反序列化的开销。但是后续算子可以基于纯内存进行操作,因此性能总体还是比较高的

  • 4)DISK_ONLY

  • 5)MEMORY_ONLY_2/MEMORY_AND_DISK_2
    对于上述任意一种持久化策略,如果加上后缀_2,代表的是将每个持久化的数据,都复制一份副本,并将副本保存到其他节点上。这种基于副本的持久化机制主要用于进行容错。假如某个节点挂掉,
    节点的内存或磁盘中的持久化数据丢失了,那么后续对RDD计算时还可以使用该数据在其他节点上的副本。如果没有副本的话,就只能将这些数据从源头处重新计算一遍了。

38、Spark中的分区只是一个逻辑上的概念,在Spark集群上数据的真正存储单元被称为数据块(Block),由数据管理器(BlockManager)维护和管理。逻辑概念上的分区和物理上的数据块一一对应,即一个分区对应一个数据块。 rdd是跨节点的,而rdd由分区组成,分区是分布在每个节点上的。即一个分区只会分布在一个节点上,不会跨节点
39、spark数据本地化:task在执行前都会获取数据的分区信息进行分配,总是会优先将其分配到它要计算的数据所在节点,尽可能的减少网络传输。一个Stage内,最终的RDD有多少个partition,就会产生多少个task,一个task处理一个partition的数据;作业划分为task分到Executor上,然后一个cpu core执行一个task;task是一个逻辑上的概念,当一个节点上面数据量过大,这个节点的计算资源满了,那么这个节点的一部分数据就不能在本机上分配task了。此时spark会等待一段时间,看能否等到这个节点有资源了,可以给这个节点的数据分配task。这个等待时间就是spark.locality.wait 这个参数。如果超过等待时长,无法继续等待,就会选择一个性能比较差的本地化级别,比如说:将task分配到距离它所要处理数据节点比较近的一个节点上,然后传输数据进行计算。但不是这个时间加长了就一定会变快。因为有时候就算跨节点计算。也很快,比等待时间还快。这时候甚至可以把spark.locality.wait设为0,即消除本地性。
41、在spark streaming 调优时,发现个增加job并行度的参数spark.streaming.concurrentJobs,spark 默认值为1。即执行完前面的job才能继续执行后面的job。当设置为大于等于2时,就允许多个job同时运行。(spark遇到action算子就会提交一个job)
42、spark每一台host上面可以并行N个worker,每一个worker下面可以并行M个executor,task们会被分配到executor上面去执行。
(1)实践中跑的EMR Spark job,有的特别慢,查看CPU利用率很低,我们就尝试减少每个executor占用CPU core的数量,增加并行的executor数量,同时配合增加分片,整体上增加了CPU的利用率,加快数据处理速度。
(2)发现某job很容易发生内存溢出,我们就增大分片数量,从而减少了每片数据的规模,同时还减少并行的executor数量,这样相同的内存资源分配给数量更少的executor,相当于增加了每个task的内存分配,这样运行速度可能慢了些,但是总比OOM强
43、在Spark Streaming中消费Kafka数据,保证Exactly-once的核心有三点:使用Direct方式连接Kafka;自己保存和维护Offset;更新Offset和计算在同一事务中完成
实现方式1:http://lxw1234.com/archives/2018/02/901.htm
启动后,先从Redis中获取上次保存的Offset,Redis中的key为”topic_partition”,即每个分区维护一个Offset;使用获取到的Offset,创建DirectStream;在处理每批次的消息时,利用Redis的事务机制,确保在Redis中指标的计算和Offset的更新维护,在同一事务中完成。只有这两者同步,才能真正保证消息的Exactly-once。
44、redis事务
Redis事务机制
严格意义来讲,Redis的事务和我们理解的传统数据库(如mysql)的事务是不一样的;Redis的事务实质上是命令的集合,在一个事务中要么所有命令都被执行,要么所有事物都不执行。
一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。
在MySQL中我们使用START TRANSACTION 或 BEGIN开启一个事务,使用COMMIT提交一个事务;而在Redis中我们使用MULTI 开始一个事务,由 EXEC 命令触发事务, 一并执行事务中的所有命令。
可以看到,MULTI 开始到 EXEC结束前,中间所有的命令都被加入到一个命令队列中;当执行 EXEC命令后,将QUEUE中所有的命令执行。
45、spark Executor的GC时间要看Task Time (GC Time),Task Time是包含了GC time的,如果tasktime=1m, gctime=30S,那么GC就有问题,如果tasktime=10m,gctime=30S,那一般正常
46、mr推测执行
Straggle(掉队者)是指那些跑的很慢但最终会成功完成的任务。一个掉队的Map任务会阻止Reduce任务开始执行。Hadoop不能自动纠正掉队任务,但是可以识别那些跑的比较慢的任务,然后它会产生另一个等效的任务作为备份,并使用首先完成的那个任务的结果,此时另外一个任务则会被要求停止执行。这种技术称为推测执行(speculative execution)。
mapreduce.map.speculative 控制Map任务的推测执行(默认true)
mapreduce.reduce.speculative 控制Reduce任务的推测执行(默认true)
47、sparkstreaming为什么要限流

  • 当数据处理的时间大于batch的间隔时间,即batch processing time > batch interval时,就会导致Executor端的Receiver的数据产生堆积,极端的情况下会导致OOM异常

  • 限流方式

    1)通过参数限流:缺点是一旦业务量发生变化,就只能手动修改参数并重启Streaming程序。另外,人为估计的参数毕竟有可能不准,设置得太激进或太保守都不好。
    spark.streaming.kafka.maxRatePerPartition
    2)背压(动态限流,根据集群资源动态的自动调整数据处理的速率):
    spark.streaming.backpressure.enabled,将该参数设置为true即可开启背压机制。

48、Spark Streaming 任务失败后需要自动拉起。长时间运行发现,Spark Streaming 并不能 7 * 24h 稳定运行,我们用 Supervisor 管理 Driver 进程,当任务挂掉后 Driver 进程将不复存在,此时 Supervisor 将重新拉起 Streaming 任务
49、数据倾斜也可以加上参数:–conf spark.locality.wait=0s
这样当某个节点倾斜时,此时这个节点很忙,其他数据就不会等待这个节点的数据本地性,而去选择其他节点
50、spark比mr快,但是spark无法取代mr。spark的稳定性远不如hadoop,比如spark经常发生oom,task lost。有时候甚至这个任务会一直跑不过。但是mr慢慢跑总是能跑过的
51、spark性能优化

  • 使用kyro序列化 spark提供了两种序列化机制:Java序列化机制,kyro序列化机制
    Java序列化机制序列化速度慢,而且序列化后的数据内存占用较大。
    kyro性能上比Java序列化强,但是不是所有类都能进行kyro序列化,而且kyro序列化需要在spark程序里面对序列化的类进行注册,比较麻烦
    kyro序列化使用方法 SparkConf.set(“spark.serializer”,“org.apache.spark.serializer.KryoSerializer”);
    此时spark就会对shuffle操作,scala基本类型的序列化采用kyro序列化机制。因为spark已经对这些类型进行了kyro注册。但是对于我们自定义的类。需要在代码里面自己注册kyro

  • 优先使用数组和字符串,而不是集合。即优先用array而不是arraylist,hashmap。或者将arraylist里面的每一个元素拼接成一个字符串。
    优先用int,比如hive日期和小时分区用int类型而不是string类型

  • GC调优
    spark默认分配executor 60%的内存用于rdd的cache,rdd中数据的存储,40%内存用于存储task创建的对象。当对象创建过多,这40%内存不足,就会发生频繁GC,GC也是一个线程,此时就会有线程占用,即task的线程就会停止,性能下降!所以如果发生频繁GC,在有限的资源下可以降低rdd缓存的内存比例。
    默认的这40%的task对象内存,其实就是一个jvm堆空间,即分为新生代(eden,survivor1,2)和老年代。那么正常的情况就是短时间存活的对象在年轻代,真正长时间存活的对象进入老年代。异常状况:eden区较小,很容易就满了,那么就很容易minorGC,短时间存活的对象本来要放进survivor,但是survivor内存不够了,对象被放进了老年代,老年代空间满了就会进行fullGC,此时full GC要大量回收短时间存活的对象,本身fullGC就比较耗时,导致GC时间过长,性能下降。所以可以适当提升eden区的大小

  • 设置并行度spark.default.parallelism
    spark读取hdfs文件形成的RDD的分区数和HDFS文件的split分片数一致,而分片数多数情况下和block数一致,分区数又和task数一致,所以hdfs block数和spark分区数一致。注意,这里的task数是指运行rdd的总的task数,不是同一时间运行的task数

  • 广播大变量
    driver中的大变量如大的配置文件,需要在executor中用到。默认是在每个节点的每个task中发送一份,此时需要网络传输和内存占用。用广播变量的形式后,只会在每个节点存在一份这个大变量

  • 数据本地化调参
    spark倾向于用最好的本地化级别分配task,如果数据所在的executor不空闲,那么spark就会等待一会儿,如果还是不空闲那么本地化就会降低一个级别。我们要调的参数就是这个等一会儿的时间。

  • 如果可以,用reducebykey代替groupbykey,因为reducebykey会在map端进行combine

  • shuffle调优

    • spark.shuffle.consolidateFiles shuffle过程中,假如一个节点下shuffle map task数量为10,shuffle reduce task数量为5,那么默认情况下每个map task会为每个reduce task在本地节点创建一个磁盘文件,则一共创建105=50个磁盘文件。开启了此参数后,假如task并行度是4,那么第一批4个map task会创建45=20个磁盘文件,第二批5个task运行的时候,就会往上一批的磁盘文件写,所以磁盘文件大大减少了,即此时磁盘文件数=cpu cores * reduce task数

    • shuffle map写数据到磁盘时,会先写到一个缓存。shuffle
      reduce拉取map端数据时,也是一次性只拉取指定缓存大小的数据。所以在内存够大的情况下,可以调大这两个缓存。
      spark.shuffle.file.buffer 参数说明:该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小(默认是32K)。将数据写到磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘。
      调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如64k),从而减少shuffle write过程中溢写磁盘文件的次数,也就可以减少磁盘IO次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。
      spark.reducer.maxSizeInFlight: 参数说明:该参数用于设置shuffle read task的buffer缓冲大小,而这个buffer缓冲决定了每次能够拉取多少数据。
      调优建议:如果作业可用的内存资源较为充足的话,可以适当增加这个参数的大小(比如96m),从而减少拉取数据的次数,也就可以减少网络传输的次数,进而提升性能。在实践中发现,合理调节该参数,性能会有1%~5%的提升。

52、spark部署模式
(1)local 单机模式
(2)cluster 集群模式
* standalone --master的参数为 “spark://host:port” 独立模式,自带完整服务,无需依赖外界的资源管理系统比如yarn
* mesos
* yarn : 分为yarn cluster 和 yarn client
53、spark-submit中,如果配置了 --conf spark.driver.userClassPathFirst=true --conf spark.executor.userClassPathFirst=true
表示优先使用用户自己项目里面的资源(或者用户–jars指定的jar包)。
比如如果用户项目里面maven配了spark或者hadoop依赖,那么如果把这些jar包都打包进总的jar包里面的话,那么运行这个总的jar包就会优先使用它里面的hadoop,而不是用集群的hadoop,就会报错:No FileSystem for scheme: hdfs。所以配置了两个参数的话,就不能把spark或者hadoop的包打进去,加这两个配置的原因是因为有时候集群的某些包不适合我们的项目,所以需要我们额外指定相对应的jar包
54、spark闭包:RDD的函数比如foreach是在work上运行,而函数外部的代码是driver上运行。这样就形成了不同的闭包。
spark streaming的foreachRDD是在driver上运行的,而RDD的
foreachPartition和foreach都是在work上运行的
55、spark的代码也要做try catch,因为如果没有try catch,虽然整个spark作业不会断,但是这批RDD的数据,只要有一条数据抛异常了,那么这一批RDD的数据以及后面的代码就作废了。下一批正常的RDD数据才会恢复正常。而且spark里面catch到异常后,不能throw new Exception,因为这样子做的话,java会补抓到另一个异常java.exception…所以打印异常就好
56、宽依赖:父RDD和子RDD的分区是多对多的关系,可以简单理解为shuffle操作
窄依赖:父RDD和子RDD的分区是一对一的关系
DAG:有向无环图,Spark中使用DAG对RDD的关系进行建模,描述了RDD的依赖关系,这种关系也被称之为lineage(血统),简单理解为点和线构成图。点是某种数据结构,spark计算框架的点就是RDD(或者stage)。线就是关联关系,spark计算框架的线就是RDD算子(或者宽依赖算子)
57、spark读取hdfs小文件优化
hadoop spark合并小文件
一.输入文件类型设置为 CombineTextInputFormat

hadoop:job.setInputFormatClass(CombineTextInputFormat.class)
spark:val data = sc.newAPIHadoopFile(args(1),
      classOf[CombineTextInputFormat],
      classOf[LongWritable],
      classOf[Text], hadoopConf)
      .map { //TODO }

二.再配置以下参数:

如果设置了CombineTextInputFormat而不配置分片大小的参数,所有输入会合并为一个文件,也就是说,不管你数据多大,只有一个Map

1.运行时加参数

-D mapreduce.input.fileinputformat.split.minsize=134217728
-D mapreduce.input.fileinputformat.split.maxsize=512000000
-D mapred.linerecordreader.maxlength=32768

例如: hadoop jar xx.jar -D mapreduce.input.fileinputformat.split.minsize=134217728 -D mapreduce.input.fileinputformat.split.maxsize=512000000

运行时添加参数这种方法需要在Diver 的main方法第一行添加如下代码(很重要):

String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();

不然直接拿会把运行参数-D当成 args[0],用GenericOptionsParser解析后otherArgs[0]参数是才是;

不习惯运行时添加参数可以直接在Diver类中写死,代码中的设置会覆盖运行时添加的参数。

2.代码中设置参数

var hadoopConf = new Configuration()
hadoopConf.set(“mapreduce.input.fileinputformat.split.maxsize”, “512000000”)
hadoopConf.set(“mapreduce.input.fileinputformat.split.minsize”, “268435456”)
hadoopConf.set(“mapreduce.input.fileinputformat.split.minsize.per.node”, “134217728”) //下面这两参数可以不设置,详情看文章末尾
hadoopConf.set(“mapreduce.input.fileinputformat.split.minsize.per.rack”, “268435456”)
MapReduce中获取job实例的时候把hadoopConf传入
Job job = Job.getInstance(hadoopConf,“MyJob”);
在spark中就传入这个hadoopConf即可

spark方式二:
sc.hadoopConfiguration.set(“mapreduce.input.fileinputformat.split.maxsize”, “512000000”)

val fileRdd =
sc.hadoopFile[LongWritable, Text, TextInputFormat] (inputPath)

你可能感兴趣的:(spark入门到精通)