Spark job, stage, task, partition相关问题

 我们先看官网的定义:

Task A unit of work that will be sent to one executor
Job A parallel computation consisting of multiple tasks that gets spawned in response to a Spark action (e.g. savecollect); you'll see this term used in the driver's logs.
Stage Each job gets divided into smaller sets of tasks called stages that depend on each other (similar to the map and reduce stages in MapReduce); you'll see this term used in the driver's logs.
Job简单理解就是对应一个action,一个action就会有一个JOB,什么是action ? 从方法的表面上看,很难区分,因为有些你认为是转换,但实际是action, 比如sortByKey.

stage是把每个job又拆分成stage,这些stage有对应的依赖关系,在spark里面实际上是需要经过shuffle的过程就会划分为一个stage, 比如map,它不需要经过shuffle,那么它就不会被拆分为stage, 比如reduceByKey需要经过shuffle,那么一个JOB就会根据reduceByKey来拆分,前面作为一个stage, reduce之后又最为一个stage,并且前后依赖。

task: task实际上对应partition, partition是 spark的最小处理集,简单理解partition就是数据集,一个很大的文件,你定义100个分区,那么这100个分区就是100个数据集,也对应100个task, task和partition是一一对应的。 虽然是一一对应,但是这里要说明清楚,有时候你定义100个分区,实际上数据只有90个分区,剩余10个分区是没有数据的,这个时候仍然会有100个task,但是很显然会有10个task没有数据处理。所以在定义partition的时候,要搞清楚自己的分区方式,默认是spark提供了hash, range,你也可以自己定义partition方法。

上面实际有一个新概念就是shuffle, 到底什么是shuffle ?  官网的解释其实很清楚,这里稍作翻译及解释,比如reduceByKey, 我们知道一个task对应一个partition, 现在我们需要根据key做reduce操作,那么就意味着所有相同的key必须在一个partition里,否者一个task怎么来处理所有的key ?   因此spark必须查询reduce之前所有的partition数据,然后把这些相同的key扔到新的partition , 这2个操作成为shuffle write / shuffle read ,  整个过程就是shuffle, 这也是为什么spark会根据shuffle来划分stage, reduce依赖之前的所有处理,比如map, 你map处理完成之后的数据并不适合直接提交给reduce,因为这个时候的数据还是乱的,partition并不会保证相同key在一个partition, 只有在经过shuffle之后才可用。 从我们人为的思考也很清晰了,shuffle前作为一个stage,可以理解为准备工作,而shuffle后作为一个stage,可以理解为真正处理了,要准备出结果了。


接下来我们做一些测试:

1)  

代码如下:

val line = sc.textFile("/tmp/mapreduce_dir/spark/a",3)

扫描二维码关注公众号,回复: 902973 查看本文章
val rdd = line.map { x => (x.split(",")(1), x.split(",")(0)) }.foreach(x => println(x))



task是4个,因为我分了4个区(0-3),2个操作,一个map转化原始文件,然后输出,此处就是一个JOB,只有一个foreach action,没有shuffle,那么整个JOB也就一个stage.

从结果看,也是毫无顺序可言。 

(0,panda)
(3,pink)
(3,pirate)
(1,panda)
(4,pink)
(3,coffee)
(2,dog)
(3,dog)


2.  

代码如下,添加了reduceByKey:

val rdd = line.map { x => (x.split(",")(1), x.split(",")(0)) }.reduceByKey((a,b) => a + b).foreach(x => println(x))



上面还是一个JOB, 因为只有一个action, 但是有2个stage, 因为reduceByKey是一个shuffle操作, 2个stage的task是4个。 这里要阐述一下,map肯定是4个task,因为我们的文件分区是4个partition, 但是reduceByKey我默认没有去设置partition ,按照理论来说,默认是2个task, 实际测试会发现,有时候多个stage的之间如果没有去设置task数量,会根据上一个stage task的数字。 也就是说因为map是4个,那么接下来的操作也是4个。


看上面的task数量,我换成了6,再看shuffle read/shuffle write,他们的数据大小相等。


3.  

代码如下,添加了sortByKey, 这里要仔细看,有很多新东西出来了:

    val rdd = line.map { x => (x.split(",")(1), x.split(",")(0)) }.reduceByKey((a,b) => a + b,6).sortByKey().foreach(x => println(x))

添加了sortByKey, 已经有2个JOB了,不是一个,根据之前的理论,一个action一个job,那么添加的sortByKey是action ?    再看看stage01 的截图:

stage02的截图:


大家看明白了吗? 2个stage,如果说sortBey是action, 那么2个stage就是正常的了,2个JOB也就是正常的。 之前提到过,从表面上很难区分什么是action什么是shuffle就是想表达这个意思,我之前一直以为sortByKey就是shuffle. 另外,整个spark的执行顺序好像乱了,我的代码明明是先map => reduce => sort => foreach , 但是从stage的顺序来看,变成了map => sort => reduce => foreach. 而且还有一个skip task. 为什么spark要自动改变我们的顺序呢?非要先sort再reduce ? 为什么还有一个skip比较容易理解,之前我们是map=>reduce,现在是map=> sort, 所以spark明显的标记出,stage 02是不需要执行map的,因为stage 01已经执行了。

这个地方如果非要说能够理解,也很简单的能自圆其说,我可以认为,因为reduce本身就需要查询所有的数据,然后扔到相同的partition , sort本身就是排序,先排序,把结果再给reduce也符合情理。

我一直是这么认为的,很多东西的研究是不一定要去看源代码的,在我做ORACLE DBA的时候,哪里有源码给你看? 但是ORACLE大师级的人还是很多,测试的过程你能够从结果看出一个产品的实际表现,就好比说sortByKey到底是action还是shuffle, 不需要去看代码,结合官方文档的解释,再测试一下就明白了,它是action .  现在网上很多人动不动就贴源代码,很烦。


顺带发一些spark常用的一些函数。


1. map, mapVlues  

一个是针对key来做操作,一个是针对value来做操作。

文件:

/tmp/mapreduce_dir/spark/a
数据:

panda,0
pink,3
pirate,3
panda,1
pink,4
coffee,3
dog,2
dog,3

根据key求value的平均值

 
    line.map { line => (line.split(",")(0), line.split(",")(1))}.mapValues { x => (x.toDouble,1) }
    .reduceByKey((a,b) => (a._1 + b._1 , a._2 + b._2 )).mapValues(x => x._1/x._2).foreach(x => println(x._1 + "," + x._2))

2. foreachPartition, mapparition

一个RDD可以针对单个元素操作,也可以针对parition来操作,优势在于,比如你要把RDD的每个元素插入到数据库,如果针对单个元素操作,每个元素会建立一个连接,这对数据库来说不可思议,如果针对partition来操作就会少很多了。


foreachPartition:

 bankRDD.foreachPartition { x =>
        var count = 0
        val hbaseconf = HBaseConfiguration.create()
        hbaseconf.set("hbase.zookeeper.quorum", "datanode01.isesol.com,datanode02.isesol.com,datanode03.isesol.com,datanode04.isesol.com,cmserver.isesol.com")
        hbaseconf.set("hbase.zookeeper.property.clientPort", "2181")
        hbaseconf.set("maxSessionTimeout", "6")
        val myTable = new HTable(hbaseconf, TableName.valueOf(table))
        // myTable.setAutoFlush(true)
        myTable.setWriteBufferSize(3 * 1024 * 1024)
        x.foreach { y =>
          {

            val rowkey = System.currentTimeMillis().toString()
            
            val p = new Put(Bytes.toBytes(machine_tool + "#" + cur_time(3) + "#" + fileNum + "#" + rowkey))

            for (i <- 0 to hbaseCols_scala.size - 1) {
              p.add(Bytes.toBytes("cf"), Bytes.toBytes(hbaseCols_scala(i)), Bytes.toBytes(y(i)))
            }
            
            myTable.put(p)

          }

        }
        myTable.flushCommits()
        myTable.close()
      }
    } catch {
      case ex: Exception => println("can not connect hbase")
    }
  }


mapPartition :

   val line = sc.textFile("/tmp/mapreduce_dir/spark/a",10)
    val rdd = line.map { x => (x.split(",")(0),x.split(",")(1))}.mapPartitions { x =>
      {
        var res = List[(String,Int)]()
        while(x.hasNext){
          val b = x.next()
          res.::=(b._1, b._2.toInt.*(2))
        }
        
        res.iterator

      }

    }
    rdd.foreach { x => println(x) }
  }

3. sortByKey, groupByKey

根据key排序和分组

sortByKey:

object mappartition {
  def main(args: Array[String]) {
    val conf = new SparkConf()
    conf.setMaster("local").setAppName("this is for spark SQL")
    val sc = new SparkContext(conf)
    val line = sc.textFile("/tmp/mapreduce_dir/spark/a",10)
    line.map { x => (x.split(",")(0),x.split(",")(1)) }.sortByKey(true, 1).foreach(x => println(x))
  }
}

结果已经根据key排序了:

(coffee,3)
(dog,2)
(dog,3)
(panda,0)
(panda,1)
(pink,3)
(pink,4)
(pirate,3)

groupByKey:


object mappartition {
  def main(args: Array[String]) {
    val conf = new SparkConf()
    conf.setMaster("local").setAppName("this is for spark SQL")
    val sc = new SparkContext(conf)
    val line = sc.textFile("/tmp/mapreduce_dir/spark/a",1)
    line.map { x => (x.split(",")(0),x.split(",")(1))}.groupByKey().foreach(x => println(x))
  }
}

结果如下:

(coffee,CompactBuffer(3))
(panda,CompactBuffer(0, 1))
(dog,CompactBuffer(2, 3))
(pirate,CompactBuffer(3))
(pink,CompactBuffer(3, 4))

猜你喜欢

转载自blog.csdn.net/tom_fans/article/details/78492335