文章目录
I know, i know
地球另一端有你陪我
一、代码优化
1、对多次使用的RDD进行缓冲
使用 cache,注意持久化策略
MEMORY_ONLY 和 MEMORY_AND_DISK_SER
序列化能够对数据进行压缩,减少数据的占用
2、使用高性能算子
1、reduceBykey 代替 groupBykey
在 map 端进行预聚合
并且 reduceBykey 能够通过修改函数,获得数据中的最大值或最小值
本质类似冒泡排序中一次遍历的相邻依次比较
或者通过返回一对二元组,计算平均值
(无法通过依次比较计算平均值)
2、mapPartiton 代替 map
数据库进行交互的时候一般会用到,会在分区上进行操作,避免创建过多链接
foreachPartition 一般只会在测试时使用到,不会在提交中使用
3、filter 后进行 coalesce 操作
一般过滤后获得的数据都会减少,会使用 coalesce 进行分区的缩减
repartition:coalesce(numPartitions,true)增多分区,有 shuffle
coalesce(numPartitions,false)减少分区,没有 shuffle,只有合并 partition
3、广播变量 broadcast
将数据先拉到 Driver 端,再广播(缓存)到 executor 中,
一般会对绝对小的数据进行广播(100MB)
executor 本身有一个申请到的内存,用于广播的容量默认允许占 60%
如给予的内存为 1G,能够用于广播的只用 600M
4、Kryo优化序列化性能
spark 三个地方涉及到序列化
1、算子里面用到外部变量
2、RDD 类型为自定义类型,使用 checkpoint 或使用 shuffle 类算子时会产生序列化
3、cache 缓存策略中包含 SER
Kryo序列化器介绍:
Spark支持使用Kryo序列化机制。Kryo序列化机制,比默认的Java序列化机制,速度要快 ,序列化后的数据要更小,大概是Java序列化机制的1/10。所以Kryo序列化优化以后,可 以让网络传输的数据变少;在集群中耗费的内存资源大大减少。
对于这三种出现序列化的地方,我们都可以通过使用Kryo序列化类库,来优化序列化和 反序列化的性能。Spark默认使用的是Java的序列化机制,也就是 ObjectOutputStream/ObjectInputStream API来进行序列化和反序列化。但是Spark同 时支持使用Kryo序列化库,Kryo序列化类库的性能比Java序列化类库的性能要高很多。 官方介绍,Kryo序列化机制比Java序列化机制,性能高10倍左右。Spark之所以默认没有 使用Kryo作为序列化类库,是因为Kryo要求最好要注册所有需要进行序列化的自定义类 型,因此对于开发者来说,这种方式比较麻烦
5、数据本地性
Application任务执行流程:
在Spark Application提交后,Driver会根据action算子划分成一个个的job,然后对每一 个job划分成一个个的stage,stage内部实际上是由一系列并行计算的task组成的,然后 以TaskSet的形式提交给你TaskScheduler,TaskScheduler在进行分配之前都会计算出 每一个task最优计算位置。Spark的task的分配算法优先将task发布到数据所在的节点上 ,从而达到数据最优计算位置。
数据本地化级别
进程本地化:PROCESS_LOCAL
节点本地化:NODE_LOCA
无本地化:NO_PREF
机架本地化:RACK_LOCAL
跨机架本地化:ANY
二、参数调优
–num-executors
直接决定任务可以执行多块
1、一般指定50-100比较合适
2、num-executors * executor-core 不能超过 yarn 总的 cpu 数
3、num-executors * executor-memory 不能超过yarn总的内存 一般最多使用一半左右
–executor-memory
每个 executor 分配到的内存大小
一般指定4G 到8G
–executor-cores
每个 executor 中分配的核数量
一般2-4个 保证每一个cpu 的内存分到的内存不低于2G
–driver-memory
driver 中的内存大小
Driver内,用于存储广播变量
–conf spark.default.parallelism=100
rdd shuffle之后reduce的数量
–confi spark.sql.shuffle.partitions=100
spark sql shuffle之后reduce的数量,默认是200
–conf spark.storage.memoryFraction=0.4
持久化可以用的内存
–conf spark.shuffle.memoryFraction0.4
shuffle阶段可以使用的内存
–conf spark.locality.wait=10
task再Executor中执行等待时间
–conf spark.network.timeout=600s
spark 网络链接的超时时间
–conf spark.yarn.executor.memoryOverhead=2048
堆外内存的大小
–conf spark.core.connection.ack.wait.timeout=300
等待连接的时间
–spark.shuffle.file.buffer
该参数用于设置shuffle write task的BufferedOutputStream的buffer缓冲大小。将数据写到磁盘文件之前,会先写入buffer缓冲中,待缓冲写满之后,才会溢写到磁盘
一个参数调优模板
spark-submit
–class com.shujia.Test
–master yarn-client
–num-executors 50
–executor-memory 4G
–executor-cores 2
–driver-memory 2G
–conf spark.storage.memoryFraction=0.6
–conf spark.shuffle.memoryFraction=0.2
–conf spark.locality.wait=10s
–conf spark.shuffle.file.buffer=64k
–conf spark.yarn.executor.memoryOverhead=2048
–conf spark.core.connection.ack.wait.timeout=300
–conf spark.network.timeout=120s \
三、数据倾斜优化
产生的原因
1、key分布不均
2、产生shuffle
1、双重聚合
1、加前缀进行第一次聚合
2、去掉前缀进行第二次聚合
package day75
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import scala.util.Random
object DoubleReduce {
/**
* 双重聚合
* 一般适用于 业务不复杂的情况
*
*/
def main(args: Array[String]): Unit = {
val conf: SparkConf
= new SparkConf().setMaster("local").setAppName("app")
val sc: SparkContext = new SparkContext(conf)
val lines: RDD[String] = sc.textFile("data/word")
val wordRDD: RDD[String] = lines
.flatMap(_.split(","))
.filter(!_.equals(""))
// 对每一个key打上随机5以内前缀
wordRDD.map(word => {
val pix: Int = Random.nextInt(5)
(pix + "-" + word, 1)
})
.groupByKey() //第一次聚合
.map(t => (t._1, t._2.toList.sum))
.map(t => {
///去掉随机前缀
(t._1.split("-")(1), t._2)
})
.groupByKey() //第二次聚合
.map(t => (t._1, t._2.toList.sum))
.foreach(println)
while (true) {
}
}
}
2、将 reduce join 转为 map join
利用广播变量,在 map 阶段完成 join,从而避免 shuffle
只适合大表关联一个绝对小的表(小于1G)
3、双重 join
将数据倾斜的 key 拆分出来
package day75
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object DoubleJoin {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("app").setMaster("local")
val sc = new SparkContext(conf)
val dataList1 = List(
("java", 1),
("shujia", 2),
("shujia", 3),
("shujia", 1),
("shujia", 1))
val dataList2 = List(
("java", 100),
("java", 99),
("shujia", 88),
("shujia", 66))
val RDD1: RDD[(String, Int)] = sc.parallelize(dataList1)
val RDD2: RDD[(String, Int)] = sc.parallelize(dataList2)
//采样倾斜的key
val sampleRDD: RDD[(String, Int)] = RDD1.sample(false, 1.0)
//skewedKey 导致数据倾斜的key shujia
val skewedKey: String = sampleRDD.map(x => (x._1, 1))
.reduceByKey(_ + _)
.map(x => (x._2, x._1))
.sortByKey(ascending = false)
.take(1)(0)._2
//导致数据倾斜key的RDD
val skewedRDD1: RDD[(String, Int)] = RDD1.filter(tuple => {
tuple._1.equals(skewedKey)
})
//没有倾斜的key
val commonRDD1: RDD[(String, Int)] = RDD1.filter(tuple => {
!tuple._1.equals(skewedKey)
})
val skewedRDD2: RDD[(String, Int)] = RDD2.filter(tuple => {
tuple._1.equals(skewedKey)
})
val commonRDD2: RDD[(String, Int)] = RDD2.filter(tuple => {
!tuple._1.equals(skewedKey)
})
val n = 2
//对产生数据倾斜的key 使用mapjoin
val skewedMap: Map[String, Int] = skewedRDD2.collect().toMap
val bro: Broadcast[Map[String, Int]] = sc.broadcast(skewedMap)
val resultRDD1: RDD[(String, (Int, Int))] = skewedRDD1.map(kv => {
val word: String = kv._1
val i: Int = bro.value.getOrElse(word, 0)
(word, (kv._2, i))
})
//没有数据倾斜的RDD 正常join
val resultRDD2: RDD[(String, (Int, Int))] = commonRDD1.join(commonRDD2)
//将两个结果拼接
resultRDD1.union(resultRDD2)
.foreach(println)
}
}