关于Spark的几个问题

       看了小半年的Spark的源码,也在工作中用它做了不少数据清洗,ETL的工作,今天提3个不太明白的问题,仅供自己记录学习,感觉这些也是同样容易让大家感到困惑的地方,如果对于大家有所帮助,我认为那是更好的了。

       先把问题列出,这里谈的Spark的运行模式均为Standalone模式:

 

问题1、 spark-shell 命令driver运行在本机还是master, spark-submit呢?

问题2、 SparkEnv 在Executor中是如何创建? 

问题3、 写出spark版的K-Means,这也是我参加某大公司数据组面试的题目,当时没有答出来

 

       其实这3个问题都是比较简单的,只要去源码找到相关代码就能解释,我也鼓励大家多看源代码以及多做实践。

 

提示1、 从运行命令的shell文件着手很快能找到答案。 

提示2、 driver中的CoarseGrainedSchedulerBackend类似于AppManager, CoarseGrainedExecutorBackend类似于container, 而SparkEnv不可能启动在worker上,只可能在CoarseGrainedExecutorBackend,顺着这个思路在CoarseGrainedExecutorBackend相关代码中找答案

提示3、 平时写spark的程序一般是数据处理,也就是map->map->reduceByKey...->saveToTextFile,写算法在面试中有一定难度,思考不出的情况下可以参考mllib中的实现。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

        这里插一些额外的话,也是希望大家能独立思考,Spark是我独立学习的第一个完整的项目,我在过程中也遇到很多问题,最好的情况是有大牛能随时问问题,其次是参考高质量的视频和文章,现在Spark的相关文章挺多的,原因也得益于它的源代码相对于Hadoop易读。最重要的是保持学习兴趣。 下面是我的答案,仅供参考。 

 

答案1、 spark-shell 调用的也是spark-submit , 所以直接看spark-submit文件, 

if [ "$1" = "--deploy-mode" ]; then
    SPARK_SUBMIT_DEPLOY_MODE=$2
#  省略若干行 
# For client mode, the driver will be launched in the same JVM that launches
# SparkSubmit, so we may need to read the properties file for any extra class
# paths, library paths, java options and memory early on. 
export SPARK_SUBMIT_DEPLOY_MODE=${SPARK_SUBMIT_DEPLOY_MODE:-"client"}

        可以看出deploymode默认是client,而client模式下driver是运行在执行SparkSubmit的JVM中的。接着spark-submit执行SparkSubmit.scala。

    case (_, CLUSTER) if isShell(args.primaryResource) => printErrorAndExit("Cluster deploy mode is not applicable spark shells")

        可以看出spark-shell 是client模式下运行的。

 // In client mode, launch the application main class directly
    // In addition, add the main application jar and any added jars (if any) to the classpath
    if (deployMode == CLIENT) {
      childMainClass = args.mainClass
 
// In standalone-cluster mode, use Client as a wrapper around the user class
    if (clusterManager == STANDALONE && deployMode == CLUSTER) {
      childMainClass = "org.apache.spark.deploy.Client"
        可以看出client模式直接运行main class,driver运行在本机,而cluster模式有一个wrapper,执行的是上述的Client类,这个client是一个Actor,和Master交互,发送RequestSubmitDriver注册driver,driver最终运行在Worker上。

答案2、

private val env = {
    if (!isLocal) {
      val port = conf.getInt("spark.executor.port", 0)
      val _env = SparkEnv.createExecutorEnv(
        conf, executorId, slaveHostname, port, numCores, isLocal, actorSystem)
      SparkEnv.set(_env)
      _env.metricsSystem.registerSource(executorSource)
      _env.blockManager.initialize(conf.getAppId)
      _env
    } else {
      SparkEnv.get
    }
  }

       这个比较简单,直接看object CoarseGrainedExecutorBackend,它是ExecutorRunner调用fetchAndExecute 执行的,会new CoarseGrainedExecutorBackend对象,然后注册给CoarseGrainedSchedulerBackend,注册成功之后,new Executor对象,此时SparkEnv.create()

 

答案3、mllib中的实现会选取N组初始中心,最后评估出cost最小的那组中心结果作为模型结果,cost为所有点到中心距离的和,大致代码实现如下: 

class KMeansImpl(
  private var iter: Int,
  private var k: Int
  ) extends Serializable with Logging{
  private def L2_Distance(s1:Array[Double],s2:Array[Double]):Double={
    val dif = s1.zip(s2).map(l=>(l._1-l._2)*(l._1-l._2)).fold(0.0)(_+_)
    Math.sqrt(dif)
  }
  def run(data: RDD[Array[Double]]): KMeansModel={
    val sc = data.sparkContext
    val initialCentres = data.takeSample(false,k)
    var centres = initialCentres
    var i = 0
    var converged = false
    while(i<iter&& !converged){
      val bc_centres = sc.broadcast(centres).value
      val groupedPoints = data.map(point=>{
        val distpair = bc_centres.zipWithIndex.map(p=>{
          val distance = L2_Distance(p._1,point)
          (p._2,distance)
        })
        val closest = distpair.sortBy(_._2).reverse
        val point2 = Array(1.0)++point
        (closest(0)._1,point2)
      })
      val newCentres = groupedPoints.reduceByKey((v1,v2)=>v1.zip(v2).map(l=>l._1+l._2))
        .sortByKey()
        .map(l=>{val tmp=l._2.drop(1).map(vi=>(vi/l._2(0))); (l._1,tmp)})
        .map(_._2)
        .collect()

      val sum= centres.zip(newCentres).foldRight(0.0)((l,a)=>a+L2_Distance(l._1,l._2))
      converged = sum ==0.0
      if(converged) {
        val ret = new KMeansModel(newCentres.map(ctr=>new DenseVector(ctr)))
        return ret
      }
      i=i+1
      centres = newCentres
    }
    val ret = new  KMeansModel(centres.map(ctr=>new DenseVector(ctr)))
    ret
  }

}
object KMeansImpl{
  def main(args: Array[String]): Unit ={
      val conf = new SparkConf()
      conf.setAppName("KMeansImpl")
      val sc = new SparkContext(conf)
      val rawData = sc.textFile("/some/path/hold/data")
      val data = rawData.map(_.split("\t")).map(s=>s.map(_.toDouble))
      val iter = args(1).toInt
      val k = args(2).toInt
      val km = new KMeansImpl(iter,k)
      km.run(data)

  }
}

 

 

 

 

下一篇文章将是关于Spark Shuffle的一些问题。

 

 

 

 

 

猜你喜欢

转载自desmoon.iteye.com/blog/2187673