Spark任务划分、代码执行位置、创建Connect连接的最佳实践

鸣谢:如果您觉得本文对您有帮助,请点赞和收藏,Thanks。

Spark任务划分

在RDD中,任务可切分为:Application、Job、Stage、Task

Application:初始化一个SparkContext即生成一个Application,通常一个main函数就是一个Application;

Job:一个Action算子就会生成一个Job,常用Action算子包括reduce、aggregate、collect、first、take、foreach、saveAsTextFile:saveAsTextFile、countByKey 、collectAsMap等等;

Stage:根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖则划分一个Stage,宽依赖指一个分区的数据会被划分到多个分区中去,这种会引发shuffle,比如groupBy操作;

Task:Stage是一个TaskSet(任务集),将Stage划分的结果发送到不同的Executor执行即为一个Task。

Application->Job->Stage-> Task
每一层都是1对n的关系。

Spark代码运行位置

Spark代码运行,分为Driver(驱动器)Executor(执行器)
通常来说,rdd算子中的代码在excuter执行,而其他代码都是在driver执行,并且只执行一次。
Spark任务划分、代码执行位置、创建Connect连接的最佳实践_第1张图片

创建Connect连接的最佳实践

此处,以kafka流处理为例:消费spark streamign 消费kafka后,会产生一个DStream,要把数据输出到外面,创建连接对象的位置显示尤为重要。
通常,新手容易犯下以下错误:

1.在driver创建连接对象

dstream.foreachRDD { rdd =>
  val connection = createNewConnection()  // executed at the driver
  rdd.foreach { record =>
    connection.send(record) // executed at the worker
  }
}

这是错误的做法,因为这要求将连接对象序列化并从driver驱动程序发送给worker工作程序。这样的连接对象很少能在机器之间转移。此错误可能表现为序列化错误(连接对象不可序列化:connection object not serializable),初始化错误(连接对象需要在工作程序中初始化:connection object needs to be initialized at the workers)等。

2.在excuter创建对象

dstream.foreachRDD { rdd =>
 rdd.foreach { record =>
   val connection = createNewConnection()
   connection.send(record)
   connection.close()
 }
}

这种做法没有错,但是,创建连接对象会浪费时间和资源。因此,为每个记录创建和销毁连接对象会导致不必要的高开销,并且会大大降低系统的整体吞吐量。所以,不要使用这种办法创建连接对象。

正确创建连接的办法:

stream.foreachRDD { rdd =>
  rdd.foreachPartition { partitionOfRecords =>
    val connection = createNewConnection()
    partitionOfRecords.foreach(record => connection.send(record))
    connection.close()
  }
}

这种做法使用 rdd.foreachPartition创建单个连接对象,就是一个分区创建一个连接对象。
当然,还可以做以下优化:
建一个静态的、慢加载的连接对象池,按需延迟创建池中的连接,如果一段时间不使用,则归还连接对象。

dstream.foreachRDD { rdd =>
  rdd.foreachPartition { partitionOfRecords =>
    // ConnectionPool is a static, lazily initialized pool of connections
    val connection = ConnectionPool.getConnection()
    partitionOfRecords.foreach(record => connection.send(record))
    ConnectionPool.returnConnection(connection)  // return to the pool for future reuse
  }
}

备注:通过SparkSQL/SparkStreaming读写Hive/Mysql/Hbase/Kafka的代码模板可参考我的这篇文章
SparkSQL/SparkStreaming读写Hive/Mysql/Hbase/Kafka

你可能感兴趣的:(spark,spark)