Spark 之基本概念及常用算子

基本概念

官网地址 http://spark.apache.org/

  • Spark是类MapReduce的通用并行计算框架,Spark拥有MR所具有的有点,但不同于R的是Job中间的输出结果可以保存到内存中,从而不再需要读写HDFS,因此Spark能更好的适用于数据挖掘于机器学习等需要迭代的MR的算法。
    • 分布式计算框架
    • 基于内存
    • 由Scala开发,便于快速开发
    • 微批次(Spark Core),准实时,流式处理(Spark Streaming),即席查询(Spark SQL)

运行模式

  • Local(多用于测试)
  • Standalone
  • YARN(Yarn作为资源调度)
  • Mesos

SparkCore

RDD

概念

  1. Resilient Distributed Dataset 弹性分布式数据集,最小单位partition(分区)
  2. shuffle:父RDD指向多个子RDD partition
  3. 宽依赖:父RDD与子RDD partition之间的关系是一对多(有shuffle产生)
  4. 窄依赖:父RDD与子RDD partition之间的关系是一对一/多对一
  5. DAG:有向无环图,按宽窄依赖切分stage(一组并行的Task组成)
  6. 推测执行:如果有任务执行特别缓慢,则会重新启动一个相同的task,哪个先执行完,则以执行完的结果为准,如果对于ETL程序,关闭推测执行,会造成数据重复写入。如果数据倾斜并开启了推测执行(spark.speclation,默认关闭false),则程序会一直执行下去。
  7. 数据倾斜:HDFS中某block数据量大,其他的小,RDD中某个partition数据量大,其余的数据量小。

宽窄依赖

五大特性

  1. RDD是由一系列的partition组成
  2. 算子是作用在Partiitons上的
  3. RDD之间有依赖关系(宽窄依赖)
  4. 分区器是作用在K,V(Tuple)格式的RDD上的(分区器:在有shuffle的时候产生)
  5. partition提供一系列的最佳计算位置,利于task数据处理本地化(计算向数据移动)

Q&A

  1. 哪里体现了RDD的弹性(容错)?
    • RDD之间有依赖关系
    • partition的个数可多可少
  2. 哪里体现了RDD 的分布式?
    • partition 是分布在集群节点中的
  3. Spark中的textFile底层调用的多是MR读取HDFS的方法,当读取文件的时候首先Split(128MB)
  4. RDD中实际上不存数据的。

Lineage

概念

  • 血缘,一系列的RDD依赖关系形成的
1. Master(Standalone): 资源管理主节点(进程)
2. Cluster Manager: 在集群上获取资源的外部服务(例:standalone,Mesos,Yarn)
3. Worker Node: 资源管理的从节点(进程)或者说是管理本机资源的进程
4. Application: 基于Spark的用户程序,包含了driver程序和运行在集群上的Executor程序
5. Driver Program: 用来连接工作进程(Worker)的程序
6. Executor: 是在一个Worker进程所管理的节点上为某个Application启动的一个进程,该进程负责运行任务,并且负责将数据存在内存或磁盘上,每个应用都有各自独立的Executor`
7. Task: 被送到某个Executor上的工作单元
8. Job: 包含很多任务(Task)的并行计算,每个Action算子触发一个Job
9. Stage: 一个Job会被拆分成很多组任务,每组任务被成为Stage(一组并行的task)(Yarn相关)
10. Resource Manager: 
11. Node Manager:

在这里插入图片描述

Spark任务执行流程

在这里插入图片描述

解释:

一个Spark程序启动,则对应一个Jvm进程
Drive和Worker之间的通信(发送task,回收结果,Driver和Worker都是节点上的进程)
Master是资源调度的主节点,Worker是资源管理的从节点。
回收的结果放在Jvm内存中,有OOM风险

算子

Transformation算子及代码

Transformation 类算子,延迟执行,要用Action类算子出发执行 RDD => RDD

filter

object TestSpark {
  def main(args: Array[String]): Unit = {
    /**
      * conf 可以配置application运行的参数
      * 1.指定运行模式
      * 2.指定appName
      * 3.指定程序运行时的运行参数
      */

    val conf = new SparkConf()
    conf.setMaster("local").setAppName("test")

    /**
      * SparkContext 是通往集群的唯一通道
      */
    val sc = new SparkContext(conf)

    val lines = sc.textFile("./score")

    //filter,true的会留下,false的过滤掉

    val result = lines.filter(line =>{
      line.contains("liming")
    })

    result.foreach(print)

    sc.stop()
  }
}

flatMap

scala
object TestSpark {
  def main(args: Array[String]): Unit = {
    /**
      * conf 可以配置application运行的参数
      * 1.指定运行模式
      * 2.指定appName
      * 3.指定程序运行时的运行参数
      */

    val conf = new SparkConf()
    conf.setMaster("local").setAppName("test")

    /**
      * SparkContext 是通往集群的唯一通道
      */
    val sc = new SparkContext(conf)

    val lines = sc.textFile("./score")
	
	//flatMap 按行读取
    val words = lines.flatMap({
      _.split(",")
    })
    words.foreach(println(_))
	//println(words.conut())
    sc.stop()
  }
}


java
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.api.java.function.VoidFunction;


import java.util.Arrays;
import java.util.Iterator;

public class TestJavaSpark {
    public static void main(String[] args) {
        SparkConf conf = new SparkConf();
        conf.setMaster("local[4]").setAppName("test");
        JavaSparkContext sc = new JavaSparkContext(conf);

        JavaRDD<String> stringJavaRDD = sc.textFile("./score");

        JavaRDD<String> words = stringJavaRDD.flatMap(new FlatMapFunction<String, String>() {

            @Override
            public Iterator<String> call(String line) throws Exception {
                return  Arrays.asList(line.split(",")).iterator();
            }
        });

        words.foreach(new VoidFunction<String>() {
            @Override
            public void call(String s) throws Exception {
                System.out.println(s);
            }
        });
        
        //sc.close();
        sc.stop();
    }
}


map

scala
object TestSpark {
  def main(args: Array[String]): Unit = {
    /**
      * conf 可以配置application运行的参数
      * 1.指定运行模式
      * 2.指定appName
      * 3.指定程序运行时的运行参数
      */

    val conf = new SparkConf()
    conf.setMaster("local").setAppName("test")

    /**
      * SparkContext 是通往集群的唯一通道
      */
    val sc = new SparkContext(conf)

    val lines = sc.textFile("./score")

    val words = lines.flatMap({
      _.split(",")
    })
	//一进一出
    val result = words.map(word =>{
      new Tuple2(word, 1)
    })

    result.foreach(println(_))
    sc.stop()
  }
}


运行结果:(word count) 
(1,1)
(liming,1)
(100,1)
(2,1)
(zhangsan,1)
(200,1)
(3,1)
(lisi,1)
(300,1)


java
public class TestJavaSpark {
    public static void main(String[] args) {
        SparkConf conf = new SparkConf();
        conf.setMaster("local[4]").setAppName("test");
        JavaSparkContext sc = new JavaSparkContext(conf);

        JavaRDD<String> stringJavaRDD = sc.textFile("./score");

        JavaRDD<String> words = stringJavaRDD.flatMap(new FlatMapFunction<String, String>() {

            @Override
            public Iterator<String> call(String line) throws Exception {
                return  Arrays.asList(line.split(",")).iterator();
            }
        });


        /**
         * 参数解析
         * 1.第一个String,输入的类型,对应call中的类型
         * 2.第二个String,Integer 返回值类型,对应Tuple中的返回值类型
         *
         */
        JavaPairRDD<String, Integer> word = words.mapToPair(new PairFunction<String, String, Integer>() {
            @Override
            public Tuple2<String, Integer> call(String s) throws Exception {
                return new Tuple2<>(s, 1);
            }
        });


        word.foreach(new VoidFunction<Tuple2<String, Integer>>() {
            @Override
            public void call(Tuple2<String, Integer> stringIntegerTuple2) throws Exception {
                System.out.println(stringIntegerTuple2);
            }
        });
        //sc.close();
        sc.stop();
    }
}


运行结果:
(1,1)
(1,1)
(liming,1)
(liming,1)
(100,1)
(100,1)
(2,1)
(2,1)
(zhangsan,1)
(zhangsan,1)
(200,1)
(200,1)
(3,1)
(lisi,1)
(3,1)
(300,1)
(lisi,1)
(300,1)



reduceByKey

Scala WordCount
object TestSpark {
  def main(args: Array[String]): Unit = {
    /**
      * conf 可以配置application运行的参数
      * 1.指定运行模式
      * 2.指定appName
      * 3.指定程序运行时的运行参数
      */

    val conf = new SparkConf()
    conf.setMaster("local").setAppName("test")

    /**
      * SparkContext 是通往集群的唯一通道
      */
    val sc = new SparkContext(conf)

    val lines = sc.textFile("./score")

    val words = lines.flatMap({
      _.split(",")
    })

    val result = words.map(word =>{
      Tuple2(word, 1)
    })
	

	//sortBy(_._2)按出现频率排序 
	//sortByKey()按key字典序排序 = sortBy(_._1)
	//result.map(tuple =>{
    //  Tuple2(tuple._2, tuple._1)
    //}).map(tuple =>{
    //  Tuple2(tuple._2, tuple._1)
    //})   =    sortBy(_._2)
    result.reduceByKey((a: Int, b: Int) =>{
      a + b
    }).sortByKey().foreach(x => {
      println( s"${x._1} appears ${x._2} times")
    })
    sc.stop()
  }
}

运行结果:
1 appears 1 times
100 appears 1 times
2 appears 1 times
200 appears 1 times
3 appears 1 times
300 appears 1 times
liming appears 1 times
lisi appears 1 times
zhangsan appears 1 times

Java Wordcount
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.api.java.function.*;
import scala.Tuple2;


import java.util.Arrays;
import java.util.Iterator;

public class TestJavaSpark {
    public static void main(String[] args) {
        SparkConf conf = new SparkConf();
        conf.setMaster("local[4]").setAppName("test");
        JavaSparkContext sc = new JavaSparkContext(conf);

        JavaRDD<String> stringJavaRDD = sc.textFile("./score");

        JavaRDD<String> words = stringJavaRDD.flatMap(new FlatMapFunction<String, String>() {

            @Override
            public Iterator<String> call(String line) throws Exception {
                return  Arrays.asList(line.split(",")).iterator();
            }
        });


        /**
         * 参数解析
         * 1.第一个String,输入的类型,对应call中的类型
         * 2.第二个String,Integer 返回值类型,对应Tuple中的返回值类型
         *
         */
        JavaPairRDD<String, Integer> pairRDD = words.mapToPair(new PairFunction<String, String, Integer>() {
            @Override
            public Tuple2<String, Integer> call(String s) throws Exception {
                return new Tuple2<>(s, 1);
            }
        });

        /**
         * 参数解析
         *
         *
         */

        JavaPairRDD<String, Integer> reduceRDD = pairRDD.reduceByKey(new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer v1, Integer v2) throws Exception {
                return v1 + v2;
            }
        });

        reduceRDD.sortByKey().foreach(new VoidFunction<Tuple2<String, Integer>>() {
            @Override
            public void call(Tuple2<String, Integer> stringIntegerTuple2) throws Exception {
                System.out.println(stringIntegerTuple2._1 + " appears " + stringIntegerTuple2._2+" times ");
            }
        });
        
        //sc.close();
        sc.stop();
    }
}

运行结果:

300 appears 2 times 
1 appears 2 times 
liming appears 2 times 
100 appears 2 times 
lisi appears 2 times 
2 appears 2 times 
zhangsan appears 2 times 
200 appears 2 times 
3 appears 2 times 

sample

抽样

object TestSpark {
  def main(args: Array[String]): Unit = {
    /**
      * conf 可以配置application运行的参数
      * 1.指定运行模式
      * 2.指定appName
      * 3.指定程序运行时的运行参数
      */

    val conf = new SparkConf()
    conf.setMaster("local").setAppName("test")

    /**
      * SparkContext 是通往集群的唯一通道
      */
    val sc = new SparkContext(conf)

    val lines = sc.textFile("./score")


    /**
      * 参数
      * 1.true 有放回的抽取,false 无放回的抽取
      * 2.抽样比例
      * 3.seed for the random number generator,同一批数据,运行结果一样
      */
    lines.sample(true,0.2)

    sc.stop()
  }
}


Action算子

Action算子出发转换算子执行,有一个Action算子,这个Application就有一个job RDD=>else

take


object TestSpark {
  def main(args: Array[String]): Unit = {
    /**
      * conf 可以配置application运行的参数
      * 1.指定运行模式
      * 2.指定appName
      * 3.指定程序运行时的运行参数
      */

    val conf = new SparkConf()
    conf.setMaster("local").setAppName("test")

    /**
      * SparkContext 是通往集群的唯一通道
      */
    val sc = new SparkContext(conf)

    val lines = sc.textFile("./score")

	//返回数组
    val strings = lines.take(2)
	
    for(elem <- strings){
      println(elem)
    }

    sc.stop()
  }
}


first


object TestSpark {
  def main(args: Array[String]): Unit = {
    /**
      * conf 可以配置application运行的参数
      * 1.指定运行模式
      * 2.指定appName
      * 3.指定程序运行时的运行参数
      */

    val conf = new SparkConf()
    conf.setMaster("local").setAppName("test")

    /**
      * SparkContext 是通往集群的唯一通道
      */
    val sc = new SparkContext(conf)

    val lines = sc.textFile("./score")

	val strings = lines.first()
	
	println(strings)
	
    sc.stop()
  }
}


collect

数据量大的时候不建议使用。


object TestSpark {
  def main(args: Array[String]): Unit = {
    /**
      * conf 可以配置application运行的参数
      * 1.指定运行模式
      * 2.指定appName
      * 3.指定程序运行时的运行参数
      */

    val conf = new SparkConf()
    conf.setMaster("local").setAppName("test")

    /**
      * SparkContext 是通往集群的唯一通道
      */
    val sc = new SparkContext(conf)

    val lines = sc.textFile("./score")

    //将结果拉回到Driver端
    val strings = lines.collect()

    for(elem <- strings){
      println(elem)
    }

    sc.stop()
  }
}


运行结果:
1,liming,100
2,zhangsan,200
3,lisi,300

持久化算子

  • cache(懒执行,保存到内存中,持久化的单位是partition)
  • persist(懒执行,持久化的单位是partition)
  • checkpoint

cache

public class TestCache {
    public static void main(String[] args) throws MalformedURLException {
        SparkConf conf = new SparkConf();
        conf.setMaster("local[4]").setAppName("test");
        JavaSparkContext jsc = new JavaSparkContext(conf);



        JavaRDD<String> text = jsc.textFile("./data-test-4.txt");
        /**
         *缓存需要action算子才能触发
         */
        JavaRDD<String> cache = text.cache();
        long begin = System.currentTimeMillis();
        long count = cache.count();
        long end = System.currentTimeMillis();
        System.out.println( "count is " + count + ",time is :" + (end - begin));


		//JavaRDD<String> cache = text.cache();//如果放在这里则两次执行时间相近,则证明是需要触发执行的
        long begin1 = System.currentTimeMillis();
        long count1 = cache.count();

        long end1 = System.currentTimeMillis();
        System.out.println("count1 is " + count1 +", time2 is :" + (end1 - begin1));


        jsc.stop();
    }
}

运行结果:
count is 128000,time is :2576
count1 is 128000, time2 is :74

persist

可以指定持久化的级别


public class TestCache {
    public static void main(String[] args) throws MalformedURLException {
        SparkConf conf = new SparkConf();
        conf.setMaster("local[4]").setAppName("test");
        JavaSparkContext jsc = new JavaSparkContext(conf);

        JavaRDD<String> text = jsc.textFile("E:\\A-2018-05-04\\streamingwork\\data-test-4.txt");
//        JavaRDD<String> cache = text.cache();
        /**
         * 懒执行
         * class StorageLevel private(
         *  private var _useDisk: Boolean,
         *  private var _useMemory: Boolean,
         *  private var _useOffHeap: Boolean,
         *  private var _deseriaLized: Boolean,//不序列化
         *  private var _replication: Int = 1,
         * )
         * 
         * 常用级别,val MEMORY_ONLY = new StorageLevel(false, true, false, true)
         * val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
         */
        JavaRDD<String> persist = text.persist(new StorageLevel());
        //JavaRDD<String> persist = text.persist(StorageLevel.MEMORY_ONLY());
        long begin = System.currentTimeMillis();
        long count = persist.count();
        long end = System.currentTimeMillis();
        System.out.println( "count is " + count + ",time is :" + (end - begin));

//        JavaRDD<String> persist = text.persist(new StorageLevel());
        long begin1 = System.currentTimeMillis();
        long count1 = persist.count();
        long end1 = System.currentTimeMillis();
        System.out.println("count1 is " + count1 +", time2 is :" + (end1 - begin1));


        jsc.stop();
    }
}

运行结果:
count is 128000,time is :1943
count1 is 128000, time2 is :178

checkpoint

可以将RDD持久化到磁盘,还可以切断RDD之间的依赖关系

public class TestCache {
    public static void main(String[] args) throws MalformedURLException {
        SparkConf conf = new SparkConf();
        conf.setMaster("local[4]").setAppName("test");
        JavaSparkContext jsc = new JavaSparkContext(conf);

        JavaRDD<String> text = jsc.textFile("E:\\A-2018-05-04\\streamingwork\\data-test-4.txt");

        //设置目录
        jsc.setCheckpointDir("./checkpointDir");
        //缓存优化,因为checkpoint是在lineage执行完之后重新启动一个job,追溯到相应节点之后持久化
        //cache之后会加快执行速度
        text.cache();
        text.checkpoint();
        text.collect();


        jsc.stop();
    }
}


广播变量

val br = sc.broadcast(value)
使用:val brList = br.value
1.会将变量广播到每个Executor端中,每个Executor中单独有一份(不同的jvm进程)。
2.广播变量不能广播RDD(SparkStreaming中有算子可以),因为RDD中不存储数据。想广播RDD,可以广播rdd.collect()
3.只能在Driver端定义、修改,在Executor端使用,不能再Executor端改变广播变量的值,会造成线程不安全
4.Java中定义广播变量要定义为final(匿名内部类中使用变量)

累加器

在Driver端定义变量,Executor累加计数,最后在Driver端输出的变量还是初始定义的值,所以就引进了累加器,累加器会更新Driver端的变量值。

//伪代码

val rdd = sc.textFile("")
val sum = 0 
rdd.map({
	sum += 1
}).foreach{...}

println( " sum  = " + sum )

运行结果:
	sum = 0
--------------------------line one-----------------------------
累加器:
val rdd = sc.mkrdd(Array("", "", ""))
val accumulator = sc.accumulator(0)

rdd.map({
	accumulator.add(1)
	.....
}).collect()

println ("accumulator = " + accumulator.value )

//只能在Driver端定义,在Executor端使用,不能再Executor端取值(accumulator.value)
//但是可以直接打印 accumulator 对象

-----------------------------line two-------------------------------------
Demo:

import org.apache.spark.{SparkConf, SparkContext}

object ScalaWC {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
    conf.setMaster("local[4]").setAppName("wc")
    val sc = new SparkContext(conf)

    //累加器
    val value = sc.accumulator(0)
    val list = List[String]("234")

    /** 广播变量
      * 不能广播rdd(SparkStreaming可以),因为rdd不存数据,方法:sc.broadcast(rdd.collect())
      * 广播变量只能在driver端定义,在Executor端使用。
      * Driver端可以修改广播变量的值,Executor端无法修改
      */

    val br = sc.broadcast(list)

    val rdd = sc.makeRDD(Array("123", "234", "345"))

    //获取值 br.value(),不同的Executor间不能共用一个广播变量,都是单独的 n Executor = n broadcast
    rdd.filter({
      value.add(1)
      !br.value.contains(_)}).foreach({
      println("value = " + value)
      println(_)})

    sc.stop()
  }
}

Spark shell

只支持Scala,能进行简单的开发。

Spark shuffle

磁盘小文件,OOM,频繁GC(频繁GC引发full GC,整个程序等待GC完成)

shuffle write

上一个stage的每个map task 必须保证自己处理的当期那分区中的数据相同的key写入同一个相同的分区文件中,可能会写入多个不同的分区文件中

shuffle read

reduce task 或从上一个stage的所有task所在的机器上寻找属于自己的分区文件,这样就可以保证每个key所对应的value都会汇聚到同一个节点上去处理和聚合

hash shuffle

默认分区器,HashPartitioner

sort shuffle

默认分区器RangePartitioner

猜你喜欢

转载自blog.csdn.net/yswhfd/article/details/85052938
今日推荐