spark 作业参数以及优化

105 阅读2分钟

1. 资源动态分配参数

测试生产环境中spark on yarn作业 发现资源的参数无论怎么调整资源参数 启动job后,任务的资源参数与启动的都对不上;后来在cdp集群的spark环境查询到资源动态参数开启为true, 所以手动在脚本中添加参数来进行关闭该参数:--conf spark.dynamicAllocation.enabled=false;如果开启了资源动态分配,我们需要开启以下参数来配置资源分配

spark.shuffle.service.enabled:默认为false,禁用独立的 Shuffle 服务。如果使用动态资源分配,需要设置为true,将ShuffleExecutor分开。
spark.dynamicAllocation.initialExecutors:默认0,初始执行器的数量。
spark.dynamicAllocation.minExecutors:默认0,执行器的最小数量。
spark.dynamicAllocation.maxExecutors:默认Int最大值,执行器的最大数量。
spark.dynamicAllocation.executorAllocationRatio:默认1.0,用于执行器分配的比例,表示给每个应用程序分配的资源相对于集群中所有可用资源的比例。
spark.dynamicAllocation.schedulerBacklogTimeout:默认1s,作业调度队列中作业等待的超时时间。
spark.dynamicAllocation.sustainedSchedulerBacklogTimeout:默认1s,作业调度队列中连续等待的时间阈值。
spark.dynamicAllocation.executorIdleTimeout:默认60s,没有缓存的执行器空闲时自动释放的超时时间。
spark.dynamicAllocation.cachedExecutorIdleTimeout:默认Int最大值,有缓存的空闲执行器的超时时间。

2.任务分队列

目前每天大约有十万级级别上的spark加工任务(spark文件加工传输平台),主要通过sparkSql进行处理,因为所有的任务都在一个队列。生产有些文件大小是100G以上,这种情况的多文件加工sql处理,往往会卡很长时间。目前建议吧大文件容易的处理任务 提交到新的一个yarn 队列--queue $queue。不要因为大文件加工任务导致小文件的任务卡住,导致容易投诉。

3.调整分区数

目前想着资源参数是否能根据文件的大小阈值进行调整资源参数以及设置并行度;一方面加快处理速度 一方面节省资源 一方面减少小文件的过多的问题。 并行度就是task的数量,或者说分区数量

  • rdd的控制方法:spark.default.parallelism,根据算子计算决定
  • sql的控制方法:spark.sql.shuffle.partitions,默认200

4.作业中间结果进行cache

spark作业过程中会多次触发action操作,为了加快处理速度可以对中间结果进行persist,但是这个persist以及根据文件大小如何确定 多大的文件以下可以persist 可以加快速度。

总体来说,如果使用RDD进行持久化,建议采用kryo序列化+持久化的操作,如果使用Dataset和Dataframe直接使用cache持久化即可。从性能上来讲,DataSet,DataFrame 优于 RDD,建议开发中使用 DataSet、DataFrame。

5.spark开发常见问题

5.1.序列化的问题

spark作业启动如果出现了类似于Serializable、Serialize等等字眼,报错的log,碰到了序列化问题导致的报错。
一般情况下是由于使用了外部的自定义类型的对象,如果该自定义的对象没有序列化那么就会报序列化的问题;解决这个问题,可以Serializable或kryo序列化; kryo序列化的代码类似如下:

<dependencies>
  <dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-core_2.12</artifactId>
    <version>3.4.0</version>
  </dependency>
  <dependency>
    <groupId>com.esotericsoftware</groupId>
    <artifactId>kryo</artifactId>
    <version>4.0.2</version>
  </dependency>
</dependencies>
import com.esotericsoftware.kryo.Kryo
import org.apache.spark.serializer.KryoRegistrator
import scala.collection.immutable.List
import scala.collection.immutable.Map

class MyKryoRegistrator extends KryoRegistrator {
  override def registerClasses(kryo: Kryo): Unit = {
    // 注册自定义类
    kryo.register(classOf[UserProfile])
    kryo.register(classOf[UserBehavior])
    kryo.register(classOf[AnalyticsResult])
    
    // 注册数组类型
    kryo.register(classOf[Array[UserProfile]])
    kryo.register(classOf[Array[UserBehavior]])
    kryo.register(classOf[Array[AnalyticsResult]])
    
    // 注册Scala集合类
    kryo.register(classOf[List[String]])
    kryo.register(classOf[List[UserProfile]])
    kryo.register(classOf[Map[String, Any]])
    kryo.register(classOf[Map[String, String]])
    
    // 注册常用Scala类型
    kryo.register(classOf[scala.collection.immutable.$colon$colon[_]])
    kryo.register(classOf[scala.collection.immutable.Nil.type])
    kryo.register(classOf[scala.collection.immutable.HashMap[_, _]])
    kryo.register(classOf[scala.collection.immutable.ListMap[_, _]])
    
    // 注册Java类型
    kryo.register(classOf[java.lang.String])
    kryo.register(classOf[java.lang.Long])
    kryo.register(classOf[java.lang.Integer])
    kryo.register(classOf[Array[Object]])
  }
}
import org.apache.spark.sql.SparkSession
import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel

object SparkKryoExample {
  def main(args: Array[String]): Unit = {
    
    // 创建SparkSession并配置Kryo
    val spark = SparkSession.builder()
      .appName("KryoSerializationExample")
      .master("local[*]")
      .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
      .config("spark.kryo.registrator", classOf[MyKryoRegistrator].getName)
      .config("spark.kryo.registrationRequired", "true")
      .config("spark.kryoserializer.buffer", "64m")
      .config("spark.kryoserializer.buffer.max", "256m")
      .config("spark.sql.adaptive.enabled", "true")
      .getOrCreate()

    import spark.implicits._
    
    try {
      // 1. 创建测试数据
      val userProfiles = Seq(
        UserProfile(1L, "user1", 25, List("tech", "sports"), Map("premium" -> true, "region" -> "US")),
        UserProfile(2L, "user2", 30, List("music", "art"), Map("premium" -> false, "region" -> "EU")),
        UserProfile(3L, "user3", 28, List("tech", "music"), Map("premium" -> true, "region" -> "ASIA")),
        UserProfile(4L, "user4", 35, List("sports"), Map("premium" -> false, "region" -> "US")),
        UserProfile(5L, "user5", 22, List("art", "books"), Map("premium" -> true, "region" -> "EU"))
      )

      val userBehaviors = Seq(
        UserBehavior(1L, "click", 1633046400L, "homepage", 120),
        UserBehavior(1L, "purchase", 1633047000L, "product", 300),
        UserBehavior(2L, "view", 1633046400L, "category", 45),
        UserBehavior(3L, "click", 1633046500L, "homepage", 80),
        UserBehavior(3L, "view", 1633046600L, "product", 200),
        UserBehavior(4L, "purchase", 1633046700L, "checkout", 150),
        UserBehavior(5L, "click", 1633046800L, "homepage", 90)
      )

      // 2. 创建RDD(将使用Kryo序列化)
      val profilesRDD: RDD[UserProfile] = spark.sparkContext.parallelize(userProfiles)
      val behaviorsRDD: RDD[UserBehavior] = spark.sparkContext.parallelize(userBehaviors)

      // 3. 使用Kryo序列化持久化RDD
      val persistedProfiles = profilesRDD.persist(StorageLevel.MEMORY_ONLY_SER)
      val persistedBehaviors = behaviorsRDD.persist(StorageLevel.MEMORY_ONLY_SER)

      // 4. 执行复杂的数据处理操作
      val userAnalytics = persistedBehaviors
        .map(behavior => (behavior.userId, behavior))
        .groupByKey()
        .map { case (userId, behaviors) =>
          val behaviorList = behaviors.toList
          val totalDuration = behaviorList.map(_.duration).sum
          val uniquePages = behaviorList.map(_.page).distinct
          val behaviorCount = behaviorList.size
          
          AnalyticsResult(userId, behaviorCount, totalDuration, uniquePages)
        }

      // 5. 连接用户画像和行为数据
      val enrichedResults = userAnalytics
        .map(result => (result.userId, result))
        .join(persistedProfiles.map(profile => (profile.userId, profile)))
        .map { case (userId, (analytics, profile)) =>
          s"User ${profile.username} (${profile.age}): ${analytics.behaviorCount} behaviors, " +
          s"${analytics.totalDuration} seconds, tags: ${profile.tags.mkString(", ")}"
        }

      // 6. 收集并打印结果
      println("=== 用户行为分析结果 ===")
      enrichedResults.collect().foreach(println)

      // 7. 演示DataFrame操作(DataFrame有自己的序列化机制)
      val behaviorsDF = persistedBehaviors.toDF()
      val profilesDF = persistedProfiles.toDF()
      
      println("\n=== DataFrame操作结果 ===")
      behaviorsDF.show()
      profilesDF.show()

      // 8. 性能对比:计算处理时间
      val startTime = System.currentTimeMillis()
      
      val complexOperation = persistedBehaviors
        .filter(_.duration > 100)
        .map(b => (b.userId, b.duration))
        .reduceByKey(_ + _)
        .collect()
      
      val endTime = System.currentTimeMillis()
      println(s"\n=== 性能统计 ===")
      println(s"复杂操作执行时间: ${endTime - startTime} ms")
      println(s"用户数量: ${persistedProfiles.count()}")
      println(s"行为记录数量: ${persistedBehaviors.count()}")

    } finally {
      // 关闭SparkSession
      spark.stop()
    }
  }
}