spark

一 Spark快速入门

1 spark官网

https://spark.apache.org/

2 Spark介绍

    Apache Spark 是一个快速的,通用的集群计算系统。它对 Java,Scala,Python 和 R 提供了的高层 API,并有一个经优化的支持通用执行图计算的引擎。它还支持一组丰富的高级工具,包括用于 SQL 和结构化数据处理的 Spark SQL,用于机器学习的 MLlib,用于图计算的 GraphX 和 Spark Streaming。
    spark官网地址:http://spark.apache.org
    Spark是一个通用的可扩展的处理海量数据集的计算引擎。

2.1 Spark特点

2.1.1 快

相比给予MR,官方说,基于内存计算spark要快mr100倍,基于磁盘计算spark要快mr10倍。

快的原因:

  1. 基于内存计算
  2. 计算和数据的分离
  3. 基于DAGScheduler的计算划分
  4. 只有一次的Shuffle输出操作

2.1.2 易用

Spark提供超过80多个高阶算子函数,来支持对数据集的各种各样的计算,使用的时候,可以使用java、scala、python、R,非常灵活易用。

df = spark.read.json("logs.json") 
df.where("age > 21")
    .select("name.first")
    .show()

2.1.3 通用

2.1.4 随处运行

2.1.5 总结

什么是Spark呢?它就是一个集成离线计算,实时计算,SQL查询,机器学习,图计算为一体的通用的计算框架。

何为通用?就是在一个项目中,既可以使用离线计算,也可以使用其他比如,SQL查询,机器学习,图计算等等,而这时Spark最最最强大的优势,没有之一。

而这一切的基础是SparkCore,速度比传统的mr快的原因就是基于内存的计算。

Spark开发过程中,使用到的模型——RDD(Resilient Distributed Dataset, 弹性式分布式数据集),在编程中起到了非常重要的作用。

2.2 RDD

何为RDD,其实RDD就是一个不可变的scala集合。

Spark的核心概念就是RDD,指的是一个只读的,可分区的分布式数据集,这个数据的全部或者部分可以缓存在内存中,在多次计算间被重用。

RDD在抽象来说是一种元素集合,包含了数据。他是被分区的,分为多个分区,每个分区分布在集群中的不同worker节点上面,从而让RDD中的数据可以被并行操作。

RDD通常通过Hadoop上的文件,即HDFS文件或者Hive表来进行创建;也可以通过RDD的本地创建转换而来。

传统的MapReduce虽然具有自动容错、平衡负载和可拓展性的优点,但是其最大缺点是采用非循环式的数据流模型,使得在迭代计算式要进行大量的磁盘IO操作。RDD正式解决这个缺点的抽象方法。

RDD最重要的特性就是,提供了容错性,可以自动从节点失败中恢复过来。即如果某个节点上的RDD Partition,因为节点故障,导致数据丢失,那么RDD会自动通过自己的数据来源重新计算该Partition。这一切对使用者是透明的,这一切的背后工作都是通过RDD的lineage特性来实现的。

RDD的数据默认情况下是存放在内存中的,但是内存资源不足的时候,Spark会自动将RDD数据溢出到磁盘(弹性)。

2.3 RDD特点

通过上述的描述,我们可以从以下几个方面来描述RDD

  1. 弹性:如果内存充足,那集合数据的存储和计算,就都在内存中完成;如果内存不足,需要有一部分数据溢出到磁盘,然后在磁盘完成存储和计算。
  1. 分布式:就和之前学习的分布式概念一样,一个集合的数据被拆分成多个部分,这每一个部分被称之为一个分区partition,还是一个scala的不可变的集合。默认情况下,partition是和hdfs中data-block块对应的,spark加载hdfs文件时,一个data-block块对应一个partition。
所以,对RDD的操作,本质上是对着每一个RDD对应分区partition的操作。
  1. 数据集:存放数据的集合
而Spark就是对这个RDD及其集合功能算子的实现。



RDD,弹性式分布式数据集,是Spark的第一代编程模型,spark预计将在3.0中让rdd光荣退休,转而使用Dataset来完成相应的功能。



说白了RDD就是一个抽象数据类型:ADT。
  1. RDD之间是存在依赖关系的
这些RDD之间的依赖关系,就形成了一个RDD的有向无环图DAG,RDD串儿,称之为RDD血缘关系或者血统,因为lineage。



依赖关系呢,分为了两种:窄依赖和宽依赖。具体我们会在spark stage阶段划分的时候进行具体说明。
  1. 移动计算优于移动数据
partition提供的最佳计算位置,利于数据处理的本地化即计算向数据移动而不是移动数据
  1. 总结
说白了这种分布式的计算,必然开始的时候,数据和计算它的代码是分开的,要想完成计算,要么数据到计算的位置上去,要么计算到数据的位置上去,综合大数据特点,spark选择计算到数据的位置上去更优(移动计算优于移动数据)。



基于数据和计算的距离,分为了如下几个级别:



PROCESS_LOCAL:进程级别,数据和计算在同一个进程中



NODE_LOCAL:  节点级别,数据和计算在不同节点或者同一节点的不同进程



RACK_LOCAL:机架,说白了就是同一机架上



ANY:任意级别

2.4 RDD在Spark中的地位和作用

  1. 为什么会有Spark
因为传统的并行计算模型无法有效的进行交互式计算;二Spark的使命便是解决这两个问题,这也是它存在的价值和理由。
  1. Spark如何解决迭代计算
其主要实现思想就是RDD,把所有计算的数据保存在分布式的内存中。迭代计算通常情况下都是对同一个数据集做反复的迭代计算,数据在内存中将大大提升IO操作。这也是Spark设计的核心:内存计算。
  1. Spark如何实现交互式计算
因为Spark是用scala语言实现的,Spark和scala能够紧密的集成。所以Spark可以完美的运用scala的解释器,使得其中的scala可以向操作本地集合对象一样轻松的操作分布式数据集。
  1. Spark和RDD的关系
可以理解为:RDD是一种具有容错性,基于内存的集群计算抽象方法,Spark则是这个抽象方法的实现。

3 安装spark

3.0 安装说明

使用的Spark的版本是2.2.0,最新的版本应该2.4.4。

下载地址:https://archive.apache.org/dist/spark/spark-2.2.0/

提供的安装包:

spark-2.2.0.tgz ---->源码包

spark-2.2.0-bin-hadoop2.7.tgz ---->安装包

3.1 windows安装

1.解压安装包(和linux使用的同一个安装包即可)

2.配置环境变量即可

3.2 测试windows版的spark

3.2.1 代码

1. 运行bin目录spark-shell2.cmd

2. 执行
scala> sc.textFile("i://wc.txt",1).flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_+_).foreach(println)
(hehe,1)
(hello,4)
(xxx,1)
(everybody,1)
(world,1)

3. 通过:ip:4040去查看你的webui

3.2.2 同时观察到了web-ui的变化

能够得出的基本结论是什么?

  1. Spark的application,可以有非常多的job作业,和mr不同,一个应用就提交一个job就行。
  2. job的执行,好像得需要某些操作触发,否则不会执行,触发的操作就是spark作业执行的动因。
  3. spark job作业的执行是分stage阶段的
  4. spark job作业的执行stage阶段形成了一个stage的DAG有向无环图

3.3 spark的全分布式安装

3.3.0 前提

1. JAVA_HOME/SCALA_HOME
2. HADOOP_HOME

3.3.1 安装scala

[root@lihl01 software]# tar -zxvf scala-2.11.8.tgz -C /opt/apps/
[root@lihl01 scala-2.11.8]# vi /etc/profile

#envrioment
export JAVA_HOME=/opt/apps/jdk1.8.0_45
export HADOOP_HOME=/opt/apps/hadoop-2.6.0-cdh5.7.6
export SCALA_HOME=/opt/apps/scala-2.11.8
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$SCALA_HOME/bin

3.3.2 安装spark

//1. 解压以及配置环境变量
[root@lihl01 software]# tar -zxvf spark-2.2.0-bin-hadoop2.7.tgz -C /opt/apps/
[root@lihl01 apps]# mv spark-2.2.0-bin-hadoop2.7/ spark-2.2.0
[root@lihl01 scala-2.11.8]# vi /etc/profile

#envrioment
export JAVA_HOME=/opt/apps/jdk1.8.0_45
export HADOOP_HOME=/opt/apps/hadoop-2.6.0-cdh5.7.6
export SCALA_HOME=/opt/apps/scala-2.11.8
export SPARK_HOME=/opt/apps/spark-2.2.0
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$SCALA_HOME/bin
export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin

//2. 修改spark的配置
[root@lihl01 conf]# mv spark-env.sh.template spark-env.sh
[root@lihl01 conf]# mv slaves.template slaves
//2.1 从机的ip
[root@lihl01 conf]# vi slaves

# A Spark Worker will be started on each of the machines listed below.
lihl01
lihl02
lihl03

//2.2 启动环境
[root@lihl01 conf]# vi spark-env.sh

export JAVA_HOME=/opt/apps/jdk1.8.0_45
export SCALA_HOME=/opt/apps/scala-2.11.8
export SPARK_MASTER_IP=lihl01
export SPARK_MASTER_PORT=7077 ##rpc通信端口,类似hdfs的9000端口,不是50070
export SPARK_WORKER_CORES=2 ##每一个spark的worker从节点拥有几个cpu的core
export SPARK_WORKER_INSTANCES=1 ##每一台spark服务器上面拥有几个worker的实例
export SPARK_WORKER_MEMORY=1g ##每一个worker节点的内存资源
export HADOOP_CONF_DIR=/opt/apps/hadoop-2.6.0-cdh5.7.6/etc/hadoop

//3. 同步到其他两台节点
//3.1 同步scala
[root@lihl01 apps]# scp -r scala-2.11.8/ lihl02:/opt/apps/
[root@lihl01 apps]# scp -r scala-2.11.8/ lihl03:/opt/apps/

//3.2 同步spark
[root@lihl01 apps]# scp -r spark-2.2.0/ lihl02:/opt/apps/
[root@lihl01 apps]# scp -r spark-2.2.0/ lihl03:/opt/apps/

//3.3 同步环境变量
[root@lihl01 apps]# scp -r /etc/profile lihl02:/etc/profile
[root@lihl01 apps]# scp -r /etc/profile lihl03:/etc/profile

//3.4 修改lihl01的启动和关闭spark集群的脚本名称,因为他和hadoop的启动脚本同名了
[root@lihl01 sbin]# mv start-all.sh start-all-spark.sh
[root@lihl01 sbin]# mv stop-all.sh stop-all-spark.sh

3.3.3 启动spark集群

[root@lihl01 sbin]# start-all-spark.sh
//2. 测试集群
http://192.168.49.111:8081/
//3. 停止集群
[root@lihl01 sbin]# stop-all-spark.sh

3.4 spark的HA安装

3.4.1 安装zookeeper集群:此处省略1w字

3.4.2 修改spark-env.sh

export JAVA_HOME=/opt/apps/jdk1.8.0_45
export SCALA_HOME=/opt/apps/scala-2.11.8
##export SPARK_MASTER_IP=lihl01
##export SPARK_MASTER_PORT=7077 ##rpc通信端口,类似hdfs的9000端口,不是50070
export SPARK_WORKER_CORES=2 ##每一个spark的worker从节点拥有几个cpu的core
export SPARK_WORKER_INSTANCES=1 ##每一台spark服务器上面拥有几个worker的实例
export SPARK_WORKER_MEMORY=1g ##每一个worker节点的内存资源
export HADOOP_CONF_DIR=/opt/apps/hadoop-2.6.0-cdh5.7.6/etc/hadoop
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=lihl01,lihl02,lihl03 -Dspark.deploy.zookeeper.dir=/spark"

3.4.3 分发并启动

[root@lihl01 conf]# scp spark-env.sh lihl02:$SPARK_HOME/conf/spark-env.sh
[root@lihl01 conf]# scp spark-env.sh lihl03:$SPARK_HOME/conf/spark-env.sh

[root@lihl01 logs]# zkServer.sh start
[root@lihl01 logs]# zkServer.sh start
[root@lihl01 logs]# zkServer.sh start
[root@lihl01 logs]# start-all-spark.sh

lihl02也启动master
[root@lihl02 sbin]# start-master.sh

3.4.4 查看两个master的状态

4 Spark的常用组件介绍

1. ClusterManager
ClusterManager:在Standalone(上述安装的模式,也就是依托于spark集群本身)模式中即为Master(主节点),控制整个集群,监控Worker。在YARN模式中为资源管理器ResourceManager(国内spark主要基于yarn集群运行,欧美主要基于mesos来运行)。

2. Worker
Worker:从节点,负责控制计算节点,启动Executor。在YARN模式中为NodeManager,负责计算节点的控制,启动的进程叫Container。

3. Driver
运行Application的main()函数并创建SparkContext(是spark中最重要的一个概念,是spark编程的入口,作用相当于mr中的Job)。

4. Executor
执行器,在worker node上执行任务的组件、用于启动线程池运行任务。每个Application拥有独立的一组Executors。

5.SparkContext
整个应用的上下文,控制应用的生命周期,是spark编程的入口。

6.RDD
Spark的基本计算单元,一组RDD可形成执行的有向无环图RDD Graph。
RDD是弹性式分布式数据集,理解从3个方面去说:弹性、数据集、分布式。
是Spark的第一代的编程模型。

7. DAGScheduler
实现将Spark作业分解成一到多个Stage,每个Stage根据RDD的Partition个数决定Task的个数,然后生成相应的Task set放到TaskScheduler中。

8. TaskScheduler
将任务(Task)分发给Executor执行

9. Stage
一个Spark作业一般包含一到多个Stage。

10. Task
一个Stage包含一到多个Task,通过多个Task实现并行运行的功能。
task的个数由rdd的partition分区决定,spark是一个分布式计算程序,所以一个大的计算任务,就会被拆分成多个小的部分,同时进行计算。

11. Transformations
转换(Transformations) (如:map, filter, groupBy, join等),Transformations操作是Lazy的,也就是说从一个RDD转换生成另一个RDD的操作不是马上执行,Spark在遇到Transformations操作时只会记录需要这样的操作,并不会去执行,需要等到有Actions操作的时候才会真正启动计算过程进行计算。

12. Actions
操作/行动(Actions)算子 (如:count, collect, foreach等),Actions操作会返回结果或把RDD数据写到存储系统中。Actions是触发Spark启动计算的动因。

13. SparkEnv
线程级别的上下文,存储运行时的重要组件的引用。SparkEnv内创建并包含如下一些重要组件的引用。

14. MapOutPutTracker
负责Shuffle元信息的存储。

15. BroadcastManager
负责广播变量的控制与元信息的存储。

16. BlockManager
负责存储管理、创建和查找块。

17.MetricsSystem
监控运行时性能指标信息。

18. SparkConf
负责存储配置信息。作用相当于hadoop中的Configuration。

5 官网的组件调度说明

    Spark应用程序在群集上作为独立的进程集运行,由主程序(称为Driver Program)中的SparkContext对象协调。
    具体来说,要在集群上运行,SparkContext可以连接到几种类型的集群管理器(Spark自己的独立集群管理器Mesos或YARN),它们可以在应用程序之间分配资源。 连接后,Spark会在集群中的节点上获取执行程序,这些节点是运行计算并为您的应用程序存储数据的进程。 接下来,它将您的应用程序代码(由传递给SparkContext的JAR或Python文件定义)发送给Executor。 最后,SparkContext将任务发送给Executor以运行。
    关于此体系结构,有几点有用的注意事项:
    每个应用程序都有其自己的Executor程序进程,该进程在整个应用程序期间保持不变,并在多个线程中运行任务。这样的好处是可以在调度方面(每个Driver程序调度自己的任务)和Executor方面(来自不同应用程序的任务在不同JVM中运行)彼此隔离应用程序。但是,这也意味着,如果不将数据写入外部存储系统,则无法在不同的Spark应用程序(SparkContext实例)之间共享数据。
    Spark与基础群集管理器无关。只要它可以获取执行程序进程,并且它们彼此通信,即使在还支持其他应用程序(例如Mesos / YARN)的集群管理器上运行它也相对容易。
    Driver程序在其整个生命周期内必须侦听并接受其执行程序的传入连接(例如,请参见网络配置部分中的spark.driver.port)。这样,Driver程序必须是可从Worker节点访问的网络。
    由于驱动程序在群集上调度任务,因此应在Worker节点附近运行,最好在同一局域网上运行。如果您想将请求远程发送到集群,最好是在驱动程序中打开RPC,并让它在附近提交操作,而不是在远离工作节点的地方运行驱动程序。

二 Spark核心编程

1 创建idea的聚合工程(+++++)

1.1 创建一个maven的父工程

1.1.1 修改本地的maven工具

1.1.2 删除父工程的src目录

1.1.3 修改pom.xml



    4.0.0

    cn.lihl.spark
    day34-spark-parent
    1.0-SNAPSHOT
    pom



1.2 在父工程中创建子模块

1.2.1 创建子模块

1. 自己手动的创建scala目录,并设置为可编写代码的颜色
2. 需要自定删除global lib的scala sdk,然后重新添加

1.3 导入依赖管理

1.3.1 parent的父工程



    4.0.0

    cn.lihl.spark
    day34-spark-parent
    1.0-SNAPSHOT
    
        spark-core
        spark-sql
    
    pom

    
        
        2.11.8

        
        2.2.0
    

    
    
        
            
                org.scala-lang
                scala-library
                ${scala-version}
            

            
                org.apache.spark
                spark-core_2.11
                ${spark-version}
            
        
    

1.3.2 spark-common工程



    
        day34-spark-parent
        cn.lihl.spark
        1.0-SNAPSHOT
    
    4.0.0

    spark-common
    jar

    
        
            org.scala-lang
            scala-library
        
    

1.3.3 spark-core工程



    
        day34-spark-parent
        cn.lihl.spark
        1.0-SNAPSHOT
    
    4.0.0

    spark-core
    jar

    
        
        
            cn.lihl.spark
            spark-common
            1.0-SNAPSHOT
        

        
        
            org.apache.spark
            spark-core_2.11
        
    

2 wordcount案例

2.1 Java的原始代码来完成wordcount

package cn.lihl.spark.core.day1;

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.FlatMapFunction;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
import scala.Tuple2;

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

public class Demo1_Java {
    public static void main(String[] args) {
        //1. 获取到入口
        SparkConf conf = new SparkConf();
        conf.setAppName(Demo1_Java.class.getSimpleName());
        conf.setMaster("local[*]");

        JavaSparkContext context = new JavaSparkContext(conf);

        //2. 加载文件
        JavaRDD lineRDD = context.textFile("i://wc.txt");

        System.out.println("lineRDD" + "--------------------------------------------");
        lineRDD.foreach(new VoidFunction() {
            public void call(String s) throws Exception {
                System.out.println(s);
            }
        });

        //3. 切割
        JavaRDD wordRDD = lineRDD.flatMap(new FlatMapFunction() {
            public Iterator call(String line) throws Exception {
                return Arrays.asList(line.split("\\s+")).iterator();
            }
        });

        System.out.println("wordRDD" + "--------------------------------------------");
        wordRDD.foreach(new VoidFunction() {
            public void call(String s) throws Exception {
                System.out.println(s);
            }
        });


        //4. 生成(word,1)
        JavaPairRDD retRDD = wordRDD.mapToPair(new PairFunction() {
            public Tuple2 call(String word) throws Exception {
                Tuple2 tuple2 = new Tuple2(word, 1);
                return tuple2;
            }
        });

        System.out.println("retRDD" + "--------------------------------------------");
        retRDD.foreach(new VoidFunction>() {
            public void call(Tuple2 tuple2) throws Exception {
                System.out.println(tuple2._1 + "-->" + tuple2._2);
            }
        });

        //5. 聚合:reduceByKey
        JavaPairRDD wordCountRDD = retRDD.reduceByKey(new Function2() {
            public Integer call(Integer v1, Integer v2) throws Exception {
                return v1 + v2;
            }
        });

        System.out.println("wordCountRDD" + "--------------------------------------------");
        wordCountRDD.foreach(new VoidFunction>() {
            public void call(Tuple2 tuple2) throws Exception {
                System.out.println(tuple2._1 + "-->" + tuple2._2);
            }
        });
    }
}

2.2 Java lambda表达式

//1. 修改idea的编译器版本
package cn.lihl.spark.core.day1;

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.FlatMapFunction;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
import scala.Tuple2;

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

public class Demo2_Lamda {
    public static void main(String[] args) {
        //1. 获取到入口
        SparkConf conf = new SparkConf();
        conf.setAppName(Demo2_Lamda.class.getSimpleName());
        conf.setMaster("local[*]");

        JavaSparkContext context = new JavaSparkContext(conf);

        //2. 加载文件
        JavaRDD lineRDD = context.textFile("i://wc.txt");

        System.out.println("lineRDD" + "--------------------------------------------");
        lineRDD.foreach(s -> System.out.println(s));

        //3. 切割
        JavaRDD wordRDD = lineRDD.flatMap(line -> Arrays.asList(line.split("\\s+")).iterator());

        System.out.println("wordRDD" + "--------------------------------------------");
        wordRDD.foreach(s -> System.out.println(s));
        
        //4. 生成(word,1)
        JavaPairRDD retRDD = wordRDD.mapToPair(word -> new Tuple2(word, 1));

        System.out.println("retRDD" + "--------------------------------------------");
        retRDD.foreach(tuple2 -> System.out.println(tuple2._1 + "-->" + tuple2._2));

        //5. 聚合:reduceByKey
        JavaPairRDD wordCountRDD = retRDD.reduceByKey((Integer v1, Integer v2) -> v1 + v2);

        System.out.println("wordCountRDD" + "--------------------------------------------");
        wordCountRDD.foreach(tuple2 -> System.out.println(tuple2._1 + "-->" + tuple2._2));
    }
}

2.3 scala

package cn.lihl.spark.core.day1

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

object Demo1_Scala {
    def main(args: Array[String]): Unit = {
        //1. 获取到入口
        val sparkContext = new SparkContext(
            new SparkConf()
                .setAppName(Demo1_Scala.getClass.getSimpleName)
                .setMaster("local[*]")
        )

        //2. 加载文件,flatmap,map,reducebyKey
        sparkContext.textFile("i://wc.txt").flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_+_).foreach(println)
    }
}

3 Master URL说明(++++)

首先在编程过程中,至少需要给spark程序传递一个参数master-url,通过sparkConf.setMaster来完成。改参数,代表的是spark作业的执行方式,或者指定的spark程序的cluster-manager的类型。

master 含义
local 程序在本地运行,同时为本地程序提供一个线程来处理
local[M] 程序在本地运行,同时为本地程序分配M个工作线程来处理
local[*] 程序在本地运行,同时为本地程序分配机器可用的CPU core的个数工作线程来处理
local[M, N] 程序在本地运行,同时为本地程序分配M个工作线程来处理,如果提交程序失败,会进行最多N次的重试
spark://ip:port 基于standalone的模式运行,提交撑到ip对应的master上运行
spark://ip1:port1,ip2:port2 基于standalone的ha模式运行,提交撑到ip对应的master上运行
yarn/启动脚本中的deploy-mode配置为cluster 基于yarn模式的cluster方式运行,SparkContext的创建在NodeManager上面,在yarn集群中
yarn/启动脚本中的deploy-mode配置为client 基于yarn模式的client方式运行,SparkContext的创建在提交程序(文件块所在的)的那台机器上面,不在yarn集群中

4 修改日志级别

4.1 log4j.properties

# 基本日志输出级别为INFO,输出目的地为console
log4j.rootCategory=INFO, console

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n

# 输出配置的是spark提供的webui的日志级别
log4j.logger.org.spark_project.jetty=INFO
log4j.logger.org.spark_project.jetty.util.component.AbstractLifeCycle=ERROR
log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=INFO
log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=INFO
log4j.logger.org.apache.parquet=ERROR
log4j.logger.parquet=ERROR

4.2 提取了LoggerTrait的特质

package cn.lihl.spark.core.utils
import org.apache.log4j.{Level, Logger}
trait LoggerTrait {
    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
    Logger.getLogger("org.apache.hadoop").setLevel(Level.WARN)
    Logger.getLogger("org.spark_project").setLevel(Level.WARN)
}

4.3 想要使用日志处理的类中继承这个特质即可

package cn.lihl.spark.core.day1

import cn.lihl.spark.core.utils.LoggerTrait
import org.apache.spark.{SparkConf, SparkContext}

object Demo1_Scala extends LoggerTrait{

    def main(args: Array[String]): Unit = {
        //1. 获取到入口
        val sparkContext = new SparkContext(
            new SparkConf()
                .setAppName(Demo1_Scala.getClass.getSimpleName)
                .setMaster("local[*]")
        )

        //2. 加载文件,flatmap,map,reducebyKey
        sparkContext.textFile("i://wc.txt")
            .flatMap(_.split("\\s+"))
            .map((_,1)).reduceByKey(_+_)
            .foreach(println)
    }
}

4.4 解决编译版本重置5的问题

4.4.1 properties的方式解决

parent工程中添加properties的配置即可


    
    
    2.11.8
    
    2.2.0
    
    1.2.17

    
    UTF-8

    
    1.8
    1.8

4.4.2 插件方式解决


    
    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                
                    ${java-compiler-version}
                    ${java-compiler-version}
                
            
        
    

5 将jar包移动到服务器中执行(+++++)

5.1 读取hdfs中的文件

object Demo1_Scala extends LoggerTrait{

    def main(args: Array[String]): Unit = {
        //1. 获取到入口
        val sparkContext = new SparkContext(
            new SparkConf()
                .setAppName(Demo1_Scala.getClass.getSimpleName)
                .setMaster("local[*]")
        )

        //2. 加载文件,flatmap,map,reducebyKey
        sparkContext.textFile("hdfs://192.168.49.111:9000/words/azkaban.txt")
            .flatMap(_.split("\\s+"))
            .map((_,1)).reduceByKey(_+_)
            .foreach(println)
    }
}

如果是高可用,可能出现如下问题:

java.lang.IllegalArgumentException: java.net.UnknownHostException: ns1

解决方案,最简单的就是将hadoop的两个配置文件core-site.xml和hdfs-site.xml添加到项目的classpath中即可。
但是此时在此基础之上,将textFile去加载本地的文件

sc.textFile("E:/data/hello.txt")
则会报错:

java.lang.IllegalArgumentException: Pathname /E:/data/hello.txt from hdfs://ns1/E:/data/hello.txt is not a valid DFS filename

原因就在于,已经在classpath下面加载了hdfs-site.xml和core-site.xml的配置文件,则会自动理解输入的文件路径为hdfs的,自然会报错。所以,在此情况下还想加载本地文件,那么就告诉机器以本地文件的格式或者协议读取即可
val linesRDD:RDD[String] = sc.textFile("file:/E:/data/hello.txt")

5.2 提交spark程序到集群中

5.2.1 parent中添加打包插件



    4.0.0

    cn.lihl.spark
    day34-spark-parent
    1.0-SNAPSHOT
    
        spark-core
        spark-sql
        spark-common
    
    pom

    
        
        
        2.11.8
        
        2.2.0
        
        1.2.17

        
        1.8

        
        2.15.2

        
        3.2.0

        
        UTF-8

        
        1.8
        1.8
    
    
    
        
            
                org.scala-lang
                scala-library
                ${scala-version}
            

            
                org.apache.spark
                spark-core_2.11
                ${spark-version}
            

            
                log4j
                log4j
                ${log4j-version}
            
        
    

    
        
        
            
                
                
                    org.apache.maven.plugins
                    maven-compiler-plugin
                    
                        ${java-compiler-version}
                        ${java-compiler-version}
                    
                
                
                
                    maven-assembly-plugin
                    ${assembly-plugin-version}
                    
                        
                            
                            jar-with-dependencies
                        
                    
                    
                        
                            make-assembly
                            package
                            
                                single
                            
                        
                    
                
                
                
                    org.scala-tools
                    maven-scala-plugin
                    ${scala-tools-version}
                    
                        
                            
                                compile
                                testCompile
                            
                        
                    
                    
                        ${scala-version}
                        
                            -target:jvm-1.8
                        
                    
                
            
        
    

5.2.2 core



    
        day34-spark-parent
        cn.lihl.spark
        1.0-SNAPSHOT
    
    4.0.0

    spark-core
    jar

    
        
        
            cn.lihl.spark
            spark-common
            1.0-SNAPSHOT
        

        
        
            org.apache.spark
            spark-core_2.11
            provided
        
    

    
        
        src/main/scala

        
            
            
                maven-assembly-plugin
                
                    
                        
                        jar-with-dependencies
                    
                
                
                    
                        make-assembly
                        package
                        
                            single
                        
                    
                
            
            
            
                org.scala-tools
                maven-scala-plugin
                
                    
                        
                            compile
                            testCompile
                        
                    
                
                
                    
                        -target:jvm-1.8
                    
                
            
        
    

5.2.3 common



    
        day34-spark-parent
        cn.lihl.spark
        1.0-SNAPSHOT
    
    4.0.0

    spark-common
    jar

    
        
        
            org.scala-lang
            scala-library
            provided
        

        
        
            log4j
            log4j
            provided
        
    

    
        
        src/main/scala

        
            
            
                maven-assembly-plugin
                
                    
                        
                        jar-with-dependencies
                    
                
                
                    
                        make-assembly
                        package
                        
                            single
                        
                    
                
            
            
            
                org.scala-tools
                maven-scala-plugin
                
                    
                        
                            compile
                            testCompile
                        
                    
                
                
                    
                        -target:jvm-1.8
                    
                
            
        
    

5.2.4 打包,然后将core的jar包上传到服务器即可

5.3 提交

5.3.1 重构代码

object Demo1_Scala extends LoggerTrait{

    def main(args: Array[String]): Unit = {

        //0. 处理参数
        if (args == null || args.length != 1) {
            return
            """
              |Usage 
              |""".stripMargin
        }

        //1. 获取到入口
        val sparkContext = new SparkContext(
            new SparkConf()
                .setAppName(Demo1_Scala.getClass.getSimpleName)
                .setMaster("spark://192.168.49.111:7077")
//                .setMaster("local[*]")
        )

        //2. 加载文件,flatmap,map,reducebyKey
        sparkContext.textFile("hdfs://192.168.49.111:9000/" + args(0))
            .flatMap(_.split("\\s+"))
            .map((_,1)).reduceByKey(_+_)
            .foreach(println)
         //3. 释放资源
        sparkContext.stop()
    }
}

5.3.2 使用spark-shell连接到master

执行

[root@lihl01 jars]# spark-shell --master spark://lihl01:7077

scala> sc.textFile("/words/azkaban.txt").flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_ + _).foreach(println)

scala>

查看结果

1. 得到你的spark的集群的安装目录下work目录,查找对应的app,在app下面查看日志文件:
StdErr : 错误
Stdout : 标准输出

2. collect的action算子将worker的数据拷贝到本地的数组中

scala> val retRDD = sc.textFile("/words/azkaban.txt").flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_ + _)
retRDD: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[4] at reduceByKey at :24

scala> val array = retRDD.collect
array: Array[(String, Int)] = Array((22,4), (33,1), (11,4))

scala> array.foreach(println)
(22,4)
(33,1)
(11,4)

tip:
这种做法在生产环境中是严格杜绝的,因为我们大数据的理念,移动算法/计算,不移动数据

参考流程

1. 通过命令连接到spark的master
[root@hbase1 home]#  spark-shell --master spark://hbase1:7077

2. 观察zookeeper产生进程信息
ls /spark/master_status
[worker_worker-20200409173835-192.168.49.201-39142, worker_worker-20200409173835-192.168.49.202-36402, worker_worker-20200409173832-192.168.49.200-35849, app_app-20200409175024-0000]

3. 通过jps观察进程,发现多台机器中都有如下进程,因为spark要占用多个core
[root@hbase3 ~]# jps
1238 DataNode
1435 CoarseGrainedExecutorBackend
1467 Jps
1326 QuorumPeerMain
1375 Worker

4. 查询对应的有spark进程的目录发现如下:
[root@hbase3 ~]# cd /opt/apps/spark-2.2.0/work/
[root@hbase3 work]# ll
总用量 0
drwxr-xr-x. 5 root root 33 4月   8 22:07 app-20200408220322-0000
drwxr-xr-x. 3 root root 15 4月   9 17:50 app-20200409175024-0000

5. 发现日志
[root@hbase3 work]# cd app-20200409175024-0000/
[root@hbase3 app-20200409175024-0000]# ll
总用量 0
drwxr-xr-x. 2 root root 34 4月   9 17:50 0
[root@hbase3 app-20200409175024-0000]# cd 0
[root@hbase3 0]# ll
总用量 4
-rw-r--r--. 1 root root 3736 4月   9 17:50 stderr
-rw-r--r--. 1 root root    0 4月   9 17:50 stdout

6. 切回到saprk shell登录的界面执行之前的代码
import org.apache.spark.{SparkConf, SparkContext}
val conf = new SparkConf().setAppName("SparkWordCount")
val ret = sc.textFile("/wc.txt").flatMap(_.split("\\s+")).map((_,1)).reduceByKey(_ + _)
ret.foreach(println(_))

7. 在指定的日志中查看结果
cat 

[root@hbase1 1]# cat stdout
(cccc,3)
(aa,2)
(ss,1)
(aaaa,2)
(dd,2)
(bbbb,2)
(bb,1)
(cc,2)
(cccc,3)
(aa,2)
(ss,1)
(aaaa,2)
(dd,2)
(bbbb,2)
(bb,1)
(cc,2)

8. 因为现在的具体的操作是交给worker执行的,所以要把结果数据拉取到本地

scala> val array = ret.collect
array: Array[(String, Int)] = Array((cccc,3), (aa,2), (ss,1), (aaaa,2), (dd,2), (bbbb,2), (bb,1), (cc,2))

scala> array.foreach(println)

5.3.3 提交jar包

//1. 编写脚本用于提交我们的spark程序
[root@lihl01 home]# vi spark-submit-wc.sh

#!/bin/bash

path=$1

SPARK_HOME=/opt/apps/spark-2.2.0
echo "spark local application start -------------------------"
$SPARK_HOME/bin/spark-submit \
--master local \
--class cn.lihl.spark.core.day2.Demo1_Scala \
/home/wc.jar $path
echo "spark local application end -------------------------"

//2. 授权
[root@lihl01 home]# chmod +x spark-submit-wc.sh

//3. 提交执行
[root@lihl01 home]# ./spark-submit-wc.sh /words/azkaban.txt

5.3.4 local模式

#!/bin/bash

path=$1

SPARK_HOME=/opt/apps/spark-2.2.0
echo "spark local application start -------------------------"
echo $path

$SPARK_HOME/bin/spark-submit \
--master local \
--class cn.lihl.spark.core.day2.Demo1_Scala \
--deploy-mode client \
/home/wc.jar \
$path
echo "spark local application end -------------------------"

tip:
./spark-submit-wc.sh /words/azkaban.txt

5.3.5 standalone模式

#!/bin/bash

path=$1

SPARK_HOME=/opt/apps/spark-2.2.0
echo "spark local application start -------------------------"
echo $path

$SPARK_HOME/bin/spark-submit \
--master spark://lihl01:7077 \
--class cn.lihl.spark.core.day2.Demo1_Scala \
--deploy-mode client \
--total-executor-cores 2 \
--executor-cores 1 \
--executor-memory 600M \
/home/wc.jar \
$path
echo "spark local application end -------------------------"

tip:
./spark-submit-wc.sh /words/azkaban.txt

5.3.6 yarn模式来运行

#!/bin/bash

jar_path=$1
hdfs_path=$2

SPARK_HOME=/opt/apps/spark-2.2.0
echo "spark local application start -------------------------"
echo $path

$SPARK_HOME/bin/spark-submit \
--master yarn \
--class cn.lihl.spark.core.day2.Demo1_Scala \
--deploy-mode client/cluster \
--executor-cores 1 \
--num-executors 1 \
--executor-memory 600M \
$jar_path \
$hdfs_path
echo "spark local application end -------------------------"

tip:
    我们的运行结果不在spark/work下去寻找,而应该是到hadoop/log/userlogs/。因为现在使用yarn平台来执行我们的spark程序,所以产生的日志应该在hadoop的日志中

5.3.6 小结

1. 申明代码
1.1 要写参数判定的逻辑
1.2 释放资源

2. 打包
2.1 一定得将我们的需要依赖的第三方jar包打包过去
2.2 服务器有的jar包我们一般都不需要打包
(导入打包插件)、(导入对scala进行打包的插件)

3. 将jar包上传到服务器中执行
3.1 输出结果是由master分配给某个worker来执行,所以结果在这个对用的worker的日志中
3.2 提交jar包使用spark-submit脚本来执行

4. 自定义个脚本
4.1 deploy-mode client 一定得指定,否则这个结果无法运行

5.3.7 一个疑问

在local的方式中,可以看到foreach的结果,但是client模式下面就看不到这个结果,这是为何?

算子操作都是在worker中执行的,包括foreach操作,所以要想看结果就应该去worker上面查看,而不是提交作业的本机。

[图片上传失败...(image-76f72b-1607674046573)]

三 shell脚本几个变量对比

#!/bin/bash

echo '$0--->'$0
echo '$1--->'$1
echo '$2--->'$2
echo '$#--->'$#
echo '$?--->'$?
echo '$@--->'$@


$0 ./echo_shell.sh
$1 aaa
$2 bbb
$# 5
$? 0 
$@ aaa bbb ccc ddd eee

./echo_shell.sh aaa bbb ccc ddd eee

四 Spark编程

1 算子

广义的讲,对任何函数进行某一项操作都可以认为是一个算子,甚至包括求幂次,开方都可以认为是一个算子
tip:
一般在函数式编程中,我们都把以前的函数视作为算子

2 wordcount的案例的代码执行流程

2.1 流程图

2.2 小结

1. 宽依赖/窄依赖
2. 有向无环图(DAG)
3. lineage(血缘)
4. 有shuffle必产生宽依赖

五 Spark Core 的RDD操作

1

2 Transformation算子(+++++)

2.0 加载数据

textFile : 加载数据文件

parallelize:加载集合数据

2.1 map算子

rdd.map[T:ClassTag] (A => T):RDD[T],对rdd集合中的每一个元素,都作用一次该func函数,之后返回值为生成元素构成的一个新的RDD。

object Demo3_Transformation {
    def main(args: Array[String]): Unit = {
        /*
         * 第一代:RDD(spark core)
         * 第二代:DataFrame
         * 第三代:DataSet
         */
        //1. 获取到sparkcontext
        val sc = new SparkContext(new SparkConf()
                .setMaster("local[*]")
                .setAppName(Demo3_Transformation.getClass.getSimpleName)
        )

        //2. 加载数据
        //2.1 加载数据文件/本地集合
//        sc.textFile()  加载数据文件
//        sc.parallelize() 本地集合
        val list:List[Int] = List(1, 2, 3, 4, 5, 6)
        val listRDD: RDD[Int] = sc.parallelize(list)
        //3. map
        val value: RDD[Double] = listRDD.map(element => element.toDouble)
    }
}

2.2 flatmap

rdd.flatMap(func):RDD ==>rdd集合中的每一个元素,都要作用func函数,返回0到多个新的元素,这些新的元素共同构成一个新的RDD。所以和上述map算子进行总结:

  • map操作是一个one-2-one的操作
  • flatMap操作是一个one-2-many的操作
val list:List[String] = List(
    "11 14 155",
    "12 15 159",
    "13 16 136",
    "13 13 159")

val listRDD: RDD[String] = sc.parallelize(list)

//4. flatmap
listRDD.flatMap(line => {
    val wordArray: Array[String] = line.split("\\s+")
    val phone = wordArray(2)
    val up = wordArray(0).toInt
    val down = wordArray(1).toInt
    (phone -> (up, down, up + down)).productIterator
})

2.3 filter

  • rdd.filter(func):RDD ==> 对rdd中的每一个元素操作func函数,该函数的返回值为Boolean类型,保留返回值为true的元素,共同构成一个新的RDD,过滤掉哪些返回值为false的元素。
val list:List[String] = List(
    "11 14 155",
    "12 15 159",
    "13 16 136",
    "13 13 159")

val listRDD: RDD[String] = sc.parallelize(list)

val fliterRDD: RDD[String] = listRDD.filter(str => str.contains("159"))
fliterRDD.foreach(println)

2.4 sample

rdd.sample(withReplacement:Boolean, fraction:Double [, seed:Long]):RDD ===> 抽样,需要注意的是spark的sample抽样不是一个精确的抽样。一个非常重要的作用,就是来看rdd中数据的分布情况,根据数据分布的情况,进行各种调优与优化。--->数据倾斜(dataskew),找到那些发生数据倾斜的key,sample算子+reduceByKey就可以知道哪一个key出现的次数最多,出现次数最多的key往往便是发生数据倾斜的key,紧接着便进行数据倾斜优化。

首先得要知道这三个参数是啥意思
withReplacement:抽样的方式,true有返回重复抽样, false为无返回重复抽样
fraction: 抽样比例,取值范围就是0~1
seed: 抽样的随机数种子,有默认值,通常也不需要传值

val list:List[String] = List(
    "11 14 155",
    "12 15 159",
    "13 16 136",
    "13 13 159",
    "13 13 158",
    "13 13 157",
    "13 13 156",
    "13 13 154",
    "13 13 153",
    "13 13 152")

val listRDD: RDD[String] = sc.parallelize(list)
val sampleRDD: RDD[String] = listRDD.sample(true, 0.2, 3)
sampleRDD.foreach(println)

2.5 union/++

rdd1.union(rdd2),联合rdd1和rdd2中的数据,形成一个新的rdd,其作用相当于sql中的union all。

2.5.1 sparkUtils

package cn.lihl.spark.utils

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

object SparkUtils {

    def getContext(appName:String):SparkContext = {
        getContext(appName, "local[*]")
    }

    def getContext(appName:String, masterUrl:String):SparkContext = {
        val sc = new SparkContext(
            new SparkConf().setMaster(masterUrl).setAppName(appName)
        )
        sc
    }

    def stop(sparkContext: SparkContext) = {
        if (null != sparkContext && !sparkContext.isStopped) {
            sparkContext.stop()
        }
    }
}
tip:
    我们在spark-common中申明的此类,因为以后spark-sql或者spark-streaming使用此类。要在spark-common中使用SparkContext类必须在spark-common中导入spark-core的依赖!!!!!!!!!!

2.5.2 union

package cn.lihl.spark.core.day3

import cn.lihl.spark.utils.{LoggerTrait, SparkUtils}
import org.apache.spark.rdd.RDD

object Demo4_Transformation extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        //1. 获取到spark core的编程入口
        val sc = SparkUtils.getContext("demo4")


        //2. 创建两个数据
        val left: List[Int] = List(1, 3, 5, 7, 9)
        val right: List[Int] = List(2, 4, 6, 8, 10)

        val leftRDD: RDD[Int] = sc.parallelize(left, 1)
        val rightRDD: RDD[Int] = sc.parallelize(right, 1)

//        val unionRDD: RDD[Int] = leftRDD.union(rightRDD) // 合并并排序
        val unionRDD: RDD[Int] = leftRDD union rightRDD
//        val unionRDD: RDD[Int] = leftRDD ++ rightRDD //合并不排序
        unionRDD.foreach(println)
    }
}

2.6 distinct

rdd元素去重

package cn.lihl.spark.core.day3

import cn.lihl.spark.utils.{LoggerTrait, SparkUtils}
import org.apache.spark.rdd.RDD

import scala.beans.BeanProperty

object Demo4_Transformation extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        //1. 获取到spark core的编程入口
        val sc = SparkUtils.getContext("demo4")

        //2. 创建两个数据
        val left: List[Int] = List(1, 3, 5, 7, 9)
        val right: List[Int] = List(2, 4, 6, 8, 10)

        val leftRDD: RDD[Int] = sc.parallelize(left, 1)
        val rightRDD: RDD[Int] = sc.parallelize(right, 1)
        //3. union
//        val unionRDD: RDD[Int] = leftRDD.union(rightRDD) // 合并并排序
        val unionRDD: RDD[Int] = leftRDD union rightRDD
//        val unionRDD: RDD[Int] = leftRDD ++ rightRDD //合并不排序
        unionRDD.foreach(println)
        println("-"*80)
        //4. distinct
        var list:List[Int] = List(1, 3, 3, 4, 9)
        val listRDD:RDD[Int] = sc.parallelize(list)
        val distinctRDD: RDD[Int] = listRDD.distinct()
        distinctRDD.foreach(println)

        println("*"*80)
        // 需求,判断两个person只要用户名和性别一样,就认为是一个人,在distinct中就能够去重
        val p1: Person = new Person("lixi", "man")
        val p2: Person = new Person("lixi", "man")
        var plist:List[Person] = List(
            p1,p2
        )
        val plistRDD: RDD[Person] = sc.parallelize(plist)
        plistRDD.distinct.foreach(println)
    }
}

class Person extends Serializable {
    @BeanProperty var name:String = _
    @BeanProperty var sex:String = _

    def this(name:String, sex:String) = {
        this
        this.name = name
        this.sex = sex
    }

    override def equals(obj: Any): Boolean = {
        var bool = false
        if (obj == null) return bool
        val that = obj.asInstanceOf[Person]
        if (this.name == that.name && this.sex == that.sex) {
            bool = true
            return bool
        }
        return bool
    }

    override def hashCode(): Int = this.name.hashCode + this.sex.hashCode
}

2.7 join

一、join就是sql中的inner join,join的效果工作7种。

从具体的写法上面有如下几种

  1. 交叉连接
    A a accross join B b;这种操作方式会产生笛卡尔积,在工作中一定要避免。
  2. 内连接
    A a [inner] join B b [where|on a.id = b.id]; 有时候也写成:A a, B b(自连接) 是一种等值连接。所谓等值连接,就是获取A和B的交集。不等值连接格式如下:
    A a inner join B b on a.id != b.id
  3. 外连接
    3.1 左外连接:以左表为主体,查找右表中能够关联上的数据,如果管理不上,显示null。
    A a left outer join B b on a.id = b.id。
    3.2 右外连接:是以右表为主体,查找左表中能够关联上的数据,如果关联不上,显示null。
    A a right outer join B b on a.id = b.id。
    3.3 全连接:就是左外连接+右外连接
    A a full outer join B b on a.id = b.id。
    3.4 半连接:一般在工作很少用

二、 Spark:sparkcore中支持的连接有:笛卡尔积、内连接join,外连接(左、右、全)

  1. spark连接:要想两个RDD进行连接,那么这两个rdd的数据格式,必须是k-v键值对的,其中的k就是关联的条件,也就是sql中的on连接条件。
    e.g. : 假设,RDD1的类型[K, V], RDD2的类型[K, W]
    1.1 内连接
    val joinedRDD:RDD[(K, (V, W))] = rdd1.join(rdd2)
    1.2 左外连接
    val leftJoinedRDD:RDD[(K, (V, Option[W]))] = rdd1.leftOuterJoin(rdd2)
    1.3 右外连接
    val rightJoinedRDD:RDD[(K, (Option[V], W))] = rdd1.rightOuterJoin(rdd2)
    1.4 全连接
    val fullJoinedRDD:RDD[(K, (Option[V], Option[W]))] = rdd1.fullOuterJoin(rdd2)
package cn.lihl.spark.core.day3

import cn.lihl.spark.utils.{LoggerTrait, SparkUtils}
import org.apache.spark.rdd.RDD

import scala.beans.BeanProperty

object Demo4_Join extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        //1. 获取到spark core的编程入口
        val sc = SparkUtils.getContext("demo4")
        //2. 准备
        //stu表:id,name,gender,age
        //score表:stuid,course, score
        val stuList = List(
            "1 刘诗诗 女 18",
            "2 欧阳娜娜 女 55",
            "3 大幂幂 女 33",
            "4 李冰冰 女 31"
        )

        val scoreList = List(
            "1 语文 59",
            "3 数学 0",
            "2 英语 60",
            "5 体育 99"
        )

        //3. 加载数据
        val stuListRDD: RDD[String] = sc.parallelize(stuList)
        val scoreListRDD: RDD[String] = sc.parallelize(scoreList)

        //4. 连接
        //4.1 内连接
        val tStuListRDD: RDD[(Int, (String, String, Int))] = stuListRDD.map(line => {
            val fields: Array[String] = line.split("\\s+")
            val id = fields(0).toInt
            val name = fields(1)
            val gender = fields(2)
            val age = fields(3).toInt
            (id, (name, gender, age))
        })

        val tScoreListRDD: RDD[(Int, (String, Double))] = scoreListRDD.map(line => {
            val fields: Array[String] = line.split("\\s+")
            val stuid = fields(0).toInt
            val course = fields(1)
            val score = fields(2).toDouble
            (stuid, (course, score))
        })

        val joinRDD: RDD[(Int, ((String, String, Int), (String, Double)))] = tStuListRDD join tScoreListRDD

//        joinRDD.foreach(println)

        joinRDD.filter(_._1 == 3).foreach(t => println(t._2._1._1))
    }
}

2.8 groupByKey

原始rdd的类型时[(K, V)]

rdd.groupByKey(),按照key进行分组,那必然其结果就肯定[(K, Iterable[V])],是一个shuffle dependency宽依赖shuffle操作,但是这个groupByKey不建议在工作过程中使用,除非必要,因为groupByKey没有本地预聚合,性能较差,一般我们能用下面的reduceByKey或者combineByKey或者aggregateByKey代替就尽量代替。

object Demo6_GroupByKey {
    def main(args: Array[String]): Unit = {
        //1. 数据
        //stu表:id, name, gender, age, class
        val stuList = List(
            "1,杨过,1,22,1904-bd-bj",
            "2,郭靖,1,19,1904-bd-bj",
            "3,令狐冲,0,27,1904-bd-sz",
            "4,韦小宝,1,27,1904-bd-bj",
            "5,胡斐,2,17,1904-bd-hz",
            "6,张无忌,0,28,1904-bd-hz"
        )

        //2. 加载数据
        SparkUtils.getContext("demo6").parallelize(stuList).map(line => {
            val fields: Array[String] = line.split(",")
            (fields(4), (fields(0), fields(1), fields(2), fields(3), fields(4)))
        }).groupByKey.foreach(println)
    }
}

2.9 reduceByKey

rdd的类型为[(K, V)]
rdd.reduceByKey(func:(V, V) => V):RDD[(K, V)] ====>在scala集合中学习过一个reduce(func:(W, W) => W)操作,是一个聚合操作,这里的reduceByKey按照就理解为在groupByKey(按照key进行分组[(K, Iterable[V])])的基础上,对每一个key对应的Iterable[V]执行reduce操作。

同时reduceByKey操作会有一个本地预聚合的操作,所以是一个shuffle dependency宽依赖shuffle操作。

object Demo1_ReduceByKey {
    def main(args: Array[String]): Unit = {
        //1. 数据
        //stu表:id, name, gender, age, class
        val stuList = List(
            "1,杨过,1,22,1904-bd-bj",
            "2,郭靖,1,19,1904-bd-bj",
            "3,令狐冲,0,27,1904-bd-sz",
            "4,韦小宝,1,27,1904-bd-bj",
            "5,胡斐,2,17,1904-bd-hz",
            "6,张无忌,0,28,1904-bd-hz"
        )

        //2. 加载数据
        val context: SparkContext = SparkUtils.getContext("Demo1_ReduceByKey")

        val stuListRDD: RDD[String] = context.parallelize(stuList)

        val tStuListRDD: RDD[(String,(String, String, String, String))] = stuListRDD.map(line => {
            val fields: Array[String] = line.split(",")
            (fields(4), (fields(1),fields(2),fields(3),fields(4)))
        })

        val reduceByKeyRDD: RDD[(String, (String, String, String, String))] = tStuListRDD.reduceByKey((previous, next) => next)
        reduceByKeyRDD.foreach(println)
    }
}

2.10 sortByKey

按照key进行排序

sortByKey(boolean): RDD[(K, V)]

object Demo1_ReduceByKey extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        //1. 数据
        //stu表:id, name, gender, age, class
        val stuList = List(
            "1,杨过,1,22,1",
            "2,郭靖,1,19,1",
            "3,令狐冲,0,27,2",
            "4,韦小宝,1,27,2",
            "5,胡斐,2,17,3",
            "6,张无忌,0,28,3"
        )

        //2. 加载数据
        val context: SparkContext = SparkUtils.getContext("Demo1_ReduceByKey")

        val stuListRDD: RDD[String] = context.parallelize(stuList)

        val tStuListRDD: RDD[(String, String)] = stuListRDD.map(line => {
            val fields: Array[String] = line.split(",")
            (fields(4), fields(1))
        })

        val reduceByKeyRDD: RDD[(String, String)] = tStuListRDD.reduceByKey(_ + "_" + _).sortByKey(false, 1)
        reduceByKeyRDD.foreach(println)
    }
}

2.11 mapPartitions(func)

是map算子的批量处理版本。map操做是每一条记录调用一次func,而mapPartitions操作,是每一个分区中的数据调用一次func函数,性能在一定程度上要比map高,但是需要注意的是OOM,因为一次要加载所有的数据到内存进行处理。

import cn.lihl.spark.core.day3.Demo3_Transformation
import cn.lihl.spark.utils.{LoggerTrait, SparkUtils}
import org.apache.spark.rdd.RDD

object Demo2 extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val sc = SparkUtils.getContext(Demo3_Transformation.getClass.getSimpleName)

        //2. 加载数据
        //2.1 加载数据文件/本地集合
        val list:List[String] = List(
            "11 14 155",
            "12 15 159",
            "13 16 136",
            "13 13 150",
            "13 13 158",
            "13 13 157",
            "13 13 156",
            "13 13 154",
            "13 13 153",
            "13 13 152")

        val listRDD: RDD[String] = sc.parallelize(list)

        val value: RDD[String] = listRDD.mapPartitions(iterator =>{
            val arr: Array[String] = iterator.toArray
            arr(0).split("\\s+").iterator
        }, true)
        value.foreach(println)
    }
}

2.12 mapPartitionWithIndex

该函数时可以获取具体的分区信息,func函数的第一个参数类型为Int,其含义时分区partition的编号
mapPartitionWithIndex(func:(Int, Iterable[T]) => Iterable[U])

val sc = SparkUtils.getContext(Demo3_Transformation.getClass.getSimpleName)

//2. 加载数据
//2.1 加载数据文件/本地集合
val list:List[String] = List(
    "11 14 155",
    "12 15 159",
    "13 16 136",
    "13 13 150",
    "13 13 158",
    "13 13 157",
    "13 13 156",
    "13 13 154",
    "13 13 153",
    "13 13 152")
//在我们的rdd的转换算子中有一个特性,叫做延迟/懒加载。你的转换算子不通过action算子调用的话,他是不会运行的
//2. mapPartitionsWithIndex
listRDD.mapPartitionsWithIndex((partition, iterator) => {
    println(s"partition is ${partition}, data is ${iterator.mkString("[", ",", "]")}")
    iterator
}).foreach(println)

2.13 coalesce和repartition

从字面意思上来看这两算子都是进行重分区的。其实repartition就是通过coalesce来实现的,coalesce默认是窄依赖,repartition是一个宽依赖;coalesce一般用于分区减少的操作,repartition一般用于分区增大的操作。

//3. 重分区
val repartitionRDD: RDD[String] = listRDD.repartition(3) // 宽依赖,多用于增加分区
val coalesceRDD: RDD[String] = listRDD.coalesce(3, true) // 默认是窄依赖,也可以是宽依赖

2.14 cogroup

可以对多3个RDD根据key进行分组,将每个key相同的元素分别聚集为一个集合返回一个新的RDD。格式为:
(K, (Iterable[V], Iterable[W])), 将两个pairRDD按key合并,返回各自的迭代,效果如下图:

object Demo3_Cogroup extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val stuList = List(
            "1 刘诗诗 女 18",
            "2 欧阳娜娜 女 55",
            "3 大幂幂 女 33",
            "4 李冰冰 女 31",
            "3 范冰冰 女 38"
        )

        val scoreList = List(
            "1 语文 59",
            "3 数学 0",
            "2 英语 60",
            "5 体育 99"
        )

        //二、 获取配置
        val context = SparkUtils.getContext("Demo3_Cogroup")

        //三、 加载RDD
        //1. 加载数据道rdd
        val stuListRDD = context.parallelize(stuList)
        val scoreListRDD = context.parallelize(scoreList)

        //2. 分离第一列数据作为key,后面的数据作为value
        val sid2StuInfoRDD = stuListRDD.map(line => {
            val sid = line.substring(0, line.indexOf(" ")).toInt
            val info = line.substring(line.indexOf(" ") + 1)
            (sid, info)
        })
        val sid2ScoreInfoRDD = scoreListRDD.map(line => {
            val sid = line.substring(0, line.indexOf(" ")).toInt
            val info = line.substring(line.indexOf(" ") + 1)
            (sid, info)
        })

        val cogroupRDD:RDD[(Int, (Iterable[String], Iterable[String]))] = sid2StuInfoRDD.cogroup(sid2ScoreInfoRDD)

        cogroupRDD.foreach {
            case (sid, (stus, scores)) => {
                println(s"sid = ${sid}, stus = ${stus.mkString("[",",","]")}, scores = ${scores.mkString("[",",","]")}")
            }
        }

       SparkUtils.stop(context)
    }
}

2.15 CombineByKey(+++++)

2.15.1 介绍

通过查看reduceByKey和groupByKey的实现,发现其二者底层都是基于一个combineByKeyWithClassTag的底层算子来实现的,包括下面的aggregateByKey也是使用该算子实现。该算子又和combineByKey有啥关系呢?

CombineByKey是CombineByKeyWithClassTag的简化版本,它使用现有的分区程序/并行度级别对生成的RDD进行哈希分区。 此方法是为了向后兼容。 它不向shuffle提供combiner的类标签信息。

通过api学习,我们了解到combineByKey是combineByKeyWithClassTag的简写的版本。

这是spark最底层的聚合算子之一,按照key进行各种各样的聚合操作,spark提供的很多高阶算子,都是基于该算子实现的。

def combineByKey[C](
 createCombiner: V => C,
 mergeValue: (C, V) => C,
 mergeCombiners: (C, C) => C,
 numPartitions: Int): RDD[(K, C)] = self.withScope {
combineByKeyWithClassTag(createCombiner, mergeValue, mergeCombiners, numPartitions)(null)
}

上述源码便是combineByKey的定义,是将一个类型为[(K, V)]的RDD聚合转化为[(K, C)]的类型,也就是按照K来进行聚合。这里的V是聚合前的类型,C聚合之后的类型。
如何理解聚合函数?切入点就是如何理解分布式计算?总--->分--->总

createCombiner: V => C, 相同的Key在分区中会调用一次该函数,用于创建聚合之后的类型,为了和后续Key相同的数据进行聚合
mergeValue: (C, V) => C, 在相同分区中基于上述createCombiner基础之上的局部聚合
mergeCombiners: (C, C) => C) 将每个分区中相同key聚合的结果在分区间进行全局聚合

所以combineByKey就是分布式计算。

2.15.2 模拟groupByKey

  • 具体执行流程
object Demo4_CombineByKey_GroupByKey extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val context = SparkUtils.getContext("Demo4_CombineByKey_GroupByKey")

        val stuList = List(
            "白普州,1904-bd-bj",
            "伍齐城,1904-bd-bj",
            "曹佳,1904-bd-sz",
            "曹莹,1904-bd-sz",
            "刘文浪,1904-bd-wh",
            "姚远,1904-bd-bj",
            "匿名大哥,1904-bd-sz",
            "欧阳龙生,1904-bd-sz"
        )
        val stuRDD: RDD[String] = context.parallelize(stuList, 3)

        val class2InfoRDD: RDD[(String, String)] = stuRDD.mapPartitionsWithIndex {
            case (partition, iterator) => {
                val array = iterator.toArray
                println(s"${partition} : ${array.mkString("[", ",", "]")}")
                array.map(line => {
                    val dotIndex = line.lastIndexOf(",")
                    val className = line.substring(dotIndex + 1)
                    val info = line.substring(0, dotIndex)
                    (className, info)
                }).toIterator
            }
        }

        /*
           1 : [曹佳,1904-bd-sz,曹莹,1904-bd-sz,刘文浪,1904-bd-wh]
           2 : [姚远,1904-bd-bj,匿名大哥,1904-bd-sz,欧阳龙生,1904-bd-sz]
           0 : [白普州,1904-bd-bj,伍齐城,1904-bd-bj]
        */
        val combineByKeyRDD: RDD[(String, ArrayBuffer[String])] = class2InfoRDD.combineByKey(createCombiner, mergeValue, mergeCombiners)
        println("*" * 80)
        combineByKeyRDD.foreach(println)
    }

    def createCombiner(value:String) = {
        println("============createCombiner<" + value + ">====================>>>>")
        val array = new ArrayBuffer[String]()
        array.append(value)
        array
    }

    def mergeValue(array:ArrayBuffer[String], str:String) = {
        println("》》》>>========mergeValue:局部聚合结果<" + array + ">,被聚合的值:" + str + "===========>>>>")
        array.append(str)
        array
    }

    def mergeCombiners(array1:ArrayBuffer[String], array2:ArrayBuffer[String]) = {
        println("|-|-|<>|-|>>========mergeCombiners:全局聚合临时结果<" + array1 + ">,局部聚合的值:" + array2 + "===========>>>>")
        array1.++:(array2)
    }
}

2.15.3 模拟reduceByKey

object Demo5_CombineByKey_ReduceByKey extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val context = SparkUtils.getContext("Demo5_CombineByKey_ReduceByKey")

        val wordList = List(
            "hello everyone",
            "hello everybody",
            "hello world",
            "hello lihl",
            "lihl nihao",
            "chengzhiyuan zhen shuai"
        )
        val stuRDD: RDD[String] = context.parallelize(wordList, 3)
        val mapRDD: RDD[(String, Int)] = stuRDD.flatMap(_.split("\\s+")).map((_, 1))
        mapRDD.foreach(println)
        println("reduceByKey --------------------------------------------------->")
        mapRDD.reduceByKey(_+_).foreach(println)

        println("combineByKey --------------------------------------------------->")
        val combineByKey: RDD[(String, Int)] = mapRDD.combineByKey(num => num, (sum, num) => sum + num, (sum1, sum2) => sum1 + sum2)
        combineByKey.foreach(println)
    }

    def createCombiner(num:Int):Int = num
    def mergeValue(sum:Int, num:Int):Int = sum + num
    def mergeCombiners(sum1:Int, sum2:Int):Int = sum1 + sum2
}

2.16 aggregateByKey

aggregateByKey和combineByKey都是一个相对底层的聚合算子,可以完成系统没有提供的其它操作,相当于自定义算子。

aggregateByKey底层还是使用combineByKeyWithClassTag来实现,所以本质上二者没啥区别,区别就在于使用时的选择而已。

2.16.1 groupByKey

object Demo6_AggregateByKey_GroupByKey extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val context = SparkUtils.getContext("Demo4_CombineByKey_GroupByKey")

        val stuList = List(
            "白普州,1904-bd-bj",
            "伍齐城,1904-bd-bj",
            "曹佳,1904-bd-sz",
            "曹莹,1904-bd-sz",
            "刘文浪,1904-bd-wh",
            "姚远,1904-bd-bj",
            "匿名大哥,1904-bd-sz",
            "欧阳龙生,1904-bd-sz"
        )
        val stuRDD: RDD[String] = context.parallelize(stuList, 3)

        val class2InfoRDD: RDD[(String, String)] = stuRDD.mapPartitionsWithIndex {
            case (partition, iterator) => {
                val array = iterator.toArray
                println(s"${partition} : ${array.mkString("[", ",", "]")}")
                array.map(line => {
                    val dotIndex = line.lastIndexOf(",")
                    val className = line.substring(dotIndex + 1)
                    val info = line.substring(0, dotIndex)
                    (className, info)
                }).toIterator
            }
        }

        println("AggregateByKey-----------------------------------")
        class2InfoRDD.aggregateByKey(ArrayBuffer[String]())(seqOp, combOp).foreach(println)
    }

    def seqOp(array:ArrayBuffer[String], str:String) = {
        println("》》》>>========seqOp:局部聚合结果<" + array + ">,被聚合的值:" + str + "===========>>>>")
        array.append(str)
        array
    }

    def combOp(array1:ArrayBuffer[String], array2:ArrayBuffer[String]) = {
        println("|-|-|<>|-|>>========combOp:全局聚合临时结果<" + array1 + ">,局部聚合的值:" + array2 + "===========>>>>")
        array1.++:(array2)
    }
}

2.16.2 reduceByKey

object Demo7_AggregateByKey_ReduceByKey extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val context = SparkUtils.getContext("Demo5_CombineByKey_ReduceByKey")

        val wordList = List(
            "hello everyone",
            "hello everybody",
            "hello world",
            "hello lihl",
            "lihl nihao",
            "chengzhiyuan zhen shuai"
        )
        val stuRDD: RDD[String] = context.parallelize(wordList, 3)
        val mapRDD: RDD[(String, Int)] = stuRDD.flatMap(_.split("\\s+")).map((_, 1))
        mapRDD.foreach(println)
        println("reduceByKey --------------------------------------------------->")
        mapRDD.reduceByKey(_+_).foreach(println)

        println("combineByKey --------------------------------------------------->")
        val aggregateByKey: RDD[(String, Int)] = mapRDD.aggregateByKey(0)((sum, num) => sum + num, (sum1, sum2) => sum1 + sum2)
        aggregateByKey.foreach(println)
    }
}

3 action算子(++++)

所有的这些算子都是在rdd,rdd上的分区partition上面执行的,不是在driver本地执行。

3.1 foreach

3.2 count

统计该rdd中元素的个数

val sc = SparkUtils.getContext("Demo8_action")
val valueRDD: RDD[Int] = sc.parallelize(1 to 100)
val count: Long = valueRDD.count()
println(count) // 统计rdd中的元素个数

3.3 take

取前n个元素

val top10: Array[Int] = valueRDD.take(10)
println(top10.mkString(","))

3.4 first

take(n)中比较特殊的一个take(1)(0)

//3. first
val i: Int = valueRDD.first()
println(i)

3.5 collect

字面意思就是收集,拉取的意思,该算子的含义就是将分布在集群中的各个partition中的数据拉回到driver中,进行统一的处理;但是这个算子有很大的风险存在,第一,driver内存压力很大,第二数据在网络中大规模的传输,效率很低;所以一般不建议使用,如果非要用,请先执行filter。

val context = new SparkContext(new SparkConf().setAppName(this.getClass.getSimpleName).setMaster("local[*]"))
        val listRDD = context.parallelize(1 to 100)
        val arrRDD = listRDD.filter(_ % 2 == 0).collect()
        arrRDD.foreach(println)

3.6 reduce

一定记清楚,reduce是一个action操作,reduceByKey是一个transformation。reduce对一个rdd执行聚合操作,并返回结果,结果是一个值。
使用指定的可交换和关联的二进制运算符减少此RDD的元素。

//4. reduce
val sum: Int = valueRDD.reduce((previous, next) => previous + next)
println(sum)

3.7 countByKey

分组统计key的个数

//5. countByKey:要求你的rdd存放的元素都必须要二维的元组类型
val keyRDD: RDD[(String, String)] = sc.parallelize(List(
    ("bj", "程志远"),
    ("hz", "杨豪"),
    ("bj", "张海英")
))
val map: collection.Map[String, Long] = keyRDD.countByKey()
println(map.mkString(","))

3.8 saveTextFile和saveObjectFile和saveSequenceFile

        //6. saveTextFile
//        keyRDD.saveAsTextFile("file:///d:/1")

        //7. saveSequenceFile
//        keyRDD.saveAsSequenceFile("file:///d:/1")

        //8. saveObjectFile
//        keyRDD.saveAsObjectFile("file:///d:/1")

4 RDD的弹性持久化策略(+++)

4.1 什么是持久化,为什么要持久化

Spark中最重要的功能之一是跨操作在内存中持久化(或缓存)数据集。 当您保留RDD时,每个节点都会将其计算的所有分区存储在内存中,并在该数据集(或从其派生的数据集)上的其他操作中重用它们。 这样可以使以后的操作更快(通常快10倍以上)。 缓存是用于迭代算法和快速交互使用的关键工具。

4.2 如何进行持久化

您可以使用其上的persist()或cache()方法将一个RDD标记为持久。 第一次在操作中对其进行计算时,它将被保存在节点上的内存中。 Spark的缓存是容错的-如果RDD的任何分区丢失,它将使用最初创建它的转换自动重新计算。
持久化的方法就是rdd.persist()或者rdd.cache()

4.3 持久化策略

持久化策略 含义
MEMORY_ONLY(默认) rdd中的数据,以未经序列化的java对象格式,存储在内存中。如果内存不足,剩余的部分不持久化,使用的时候,没有持久化的那一部分数据重新加载。这种效率是最高,但是是对内存要求最高的。
MEMORY_ONLY_SER 就比MEMORY_ONLY多了一个SER序列化,保存在内存中的数据是经过序列化之后的字节数组,同时每一个partition此时就是一个比较大的字节数组。
MEMORY_AND_DISK 和MEMORY_ONLY相比就多了一个,内存存不下的数据存储在磁盘中。
MEMEORY_AND_DISK_SER 比MEMORY_AND_DISK多了个序列化。
DISK_ONLY 就是MEMORY_ONLY对应,都保存在磁盘,效率太差,一般不用。
xxx_2 就是上述多个策略后面加了一个_2,比如MEMORY_ONLY_2,MEMORY_AND_DISK_SER_2等等,就多了一个replicate而已,备份,所以性能会下降,但是容错或者高可用加强了。所以需要在二者直接做权衡。如果说要求数据具备高可用,同时容错的时间花费比从新计算花费时间少,此时便可以使用,否则一般不用。
HEAP_OFF(experimental) 使用非Spark的内存,也即堆外内存,比如Tachyon,HBase、Redis等等内存来补充spark数据的缓存。

4.4 持久化和非持久化性能比较

object Demo1_Persistent {
    def main(args: Array[String]): Unit = {
        val conf = new SparkConf()
            .setAppName(s"${Demo1_Persistent.getClass.getSimpleName}")
            .setMaster("local[*]")

        val context = new SparkContext(conf)
        //读取外部数据
        var start = System.currentTimeMillis()
        val lines = context.textFile("file:///E:/wc.txt")
        var count = lines.count()
        println("没有持久化:#######lines' count: " + count + ", cost time: " + (System.currentTimeMillis() - start) + "ms")
        lines.persist(StorageLevel.MEMORY_AND_DISK) //lines.cache()
        start = System.currentTimeMillis()
        count = lines.count()
        println("持久化之后:#######lines' count: " + count + ", cost time: " + (System.currentTimeMillis() - start) + "ms")
        lines.unpersist()//卸载持久化数据
        context.stop()
    }
}

没有持久化:#######lines' count: 4, cost time: 920ms
持久化之后:#######lines' count: 4, cost time: 23ms

5 共享变量

5.1 广播变量(+++++)

不使用广播变量产生的问题
广播变量解决方式

var num = 1
val bNum: Broadcast[Int] = sc.broadcast(num)

5.2 累加器

accumulator累加器的概念和mr中出现的counter计数器的概念有异曲同工之妙,对默写具备某些特征的数据进行累加。累加器的一个好处是,不需要修改程序的业务逻辑来完成数据累加,同时也不需要额外的触发一个action job来完成累加,反之必须要添加新的业务逻辑,必须要触发一个新的action job来完成,显然这个accumulator的操作性能更佳!

累加的使用:
构建一个累加器:
val accu = sc.longAccumuator()
累加的操作:
accu.add(参数)
获取累加器的结果:
val ret = accu.value

val accumulator: LongAccumulator = sc.longAccumulator // 获取累加器
accumulator.add(1) // 对累加器进行递增
accumulator.value //获取到累加器的值

object Demo2_Accumulator {
    def main(args: Array[String]): Unit = {
        val conf = new SparkConf()
            .setAppName(s"${Demo2_Accumulator.getClass.getSimpleName}")
            .setMaster("local[*]")

        val sc = new SparkContext(conf)

        val list = List(
            "A second is in Spark is shared variables that can be is in parallel is"
        )
        val lines = sc.parallelize(list)
        val words = lines.flatMap(_.split("\\s+"))

        //统计每个单词出现的次数
        val accumulator = sc.longAccumulator

        val rbk = words.map(word => {
            if(word == "is") accumulator.add(1)
            (word, 1)
        }).reduceByKey(_+_)
        rbk.foreach(println)
        println("================使用累加器===================")
        println("is: " + accumulator.value)

        Thread.sleep(10000000)
        sc.stop()
    }
}

6 高级排序

6.1 sortByKey

object Demo1_SortByKey extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val conf = new SparkConf()
            .setAppName(s"Demo1_SortByKey")
            .setMaster("local[*]")

        val sc = new SparkContext(conf)

        //sortByKey 数据类型为k-v,且是按照key进行排序

        val stuRDD:RDD[Student] = sc.parallelize(List(
            Student(1, "吴轩宇", 19, 168),
            Student(2, "彭国宏", 18, 175),
            Student(3, "随国强", 18, 176),
            Student(4, "闫  磊", 20, 180),
            Student(5, "王静轶", 18, 168.5)
        ))

        //按照学生身高进行降序排序
        val height2Stu = stuRDD.map(stu => (stu.height, stu))
        //注意:sortByKey是局部排序,不是全局排序,如果要进行全局排序,
        // 必须将所有的数据都拉取到一台机器上面才可以
        val sorted = height2Stu.sortByKey(ascending = false, 1)
        sorted.foreach { case (height, stu) => println(stu)}
        sc.stop()
    }
}
case class Student(id:Int, name:String, age:Int, height:Double)

6.2 sortBy

这个sortByKey其实使用sortByKey来实现,但是比sortByKey更加灵活,因为sortByKey只能应用在k-v数据格式上,而这个sortBy可以应在非k-v键值对的数据格式上面。

object Demo2_SortBy extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val sc = SparkUtils.getContext("Demo2_SortBy")

        //sortByKey 数据类型为k-v,且是按照key进行排序

        val stuRDD:RDD[Student] = sc.parallelize(List(
            Student(1, "吴轩宇", 19, 168),
            Student(2, "彭国宏", 18, 175),
            Student(3, "随国强", 18, 176),
            Student(4, "闫  磊", 20, 180),
            Student(5, "王静轶", 18, 168.5)
        ))

        val sortByRDD: RDD[Student] = stuRDD.sortBy(stu => stu.height, false, 1)
        sortByRDD.foreach(println)
    }
}

6.3 takeOrder

takeOrdered也是对rdd进行排序,但是和上述的sortByKey和sortBy相比较,takeOrdered是一个action操作,返回值为一个集合,而前两者为transformation,返回值为rdd。如果我们想在driver中获取排序之后的结果,那么建议使用takeOrdered,因为该操作边排序边返回。
其实是take和sortBy的一个结合体。
takeOrdered(n),获取排序之后的n条记录

object Demo3_takeOrder extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val sc = SparkUtils.getContext("Demo2_SortBy")

        //sortByKey 数据类型为k-v,且是按照key进行排序

        val stuRDD:RDD[Student] = sc.parallelize(List(
            Student(1, "吴轩宇", 19, 168),
            Student(2, "彭国宏", 18, 175),
            Student(3, "随国强", 18, 176),
            Student(4, "闫  磊", 20, 180),
            Student(5, "王静轶", 18, 169.5)
        ),1)

        stuRDD.takeOrdered(3)(new Ordering[Student]{
            override def compare(x: Student, y: Student): Int = {
                y.height.compareTo(x.height)
            }
        }).foreach(println)
    }
}

6.4 分组topn

object Demo4_group_topn extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val list = List(
            "chinese ls 91",
            "english ww 56",
            "chinese zs 90",
            "chinese zl 76",
            "english zq 88",
            "english lx 100"
        )

        val context: SparkContext = SparkUtils.getContext("Demo4_group_topn")
        val listRDD: RDD[String] = context.parallelize(list)

        val groupRDD: RDD[(String, Iterable[(String, String)])] = listRDD.map(line => {
            val arr: Array[String] = line.split("\\s+")
            (arr(0), (arr(1), arr(2)))
        }).groupByKey

        val value: RDD[(String, List[(String, String)])] = groupRDD.map(tup => {
            val tuples: List[(String, String)] = tup._2.toList.sortWith((prevous, next) => prevous._2.toInt > next._2.toInt).take(2)
            (tup._1, tuples)
        })
        val takeRDD = value
        takeRDD.foreach(println)
    }

7 分区器

Spark目前支持hash分区和range分区,用户也可以自定义分区。hash分区是默认的分区规则,spark中分区器直接决定了rdd中的分区的个数,rdd中的每条数据经过shuffle过程。

tip:

1 分区类必须要继承Partitioner的父类

2 定义分区规则的时候,分区编号不能超过分区数-1

3 在执行分区自定义类的时候,必须要手动的将数据zipWithIndex()

4 rdd.partitionBy(自定义分区对象)

object Demo5_Partitioner extends LoggerTrait{
    def main(args: Array[String]): Unit = {
        val sc = SparkUtils.getContext("Demo2_SortBy")

//        val stuRDD = sc.parallelize(1 to 100).zipWithIndex()
        val stuRDD = sc.parallelize(List(
            Student(1, "李轩宇", 19, 168),
            Student(2, "王国宏", 18, 175),
            Student(3, "陈国强", 18, 176),
            Student(4, "闫  磊", 20, 180),
            Student(5, "王静轶", 18, 169.5)
        )).zipWithIndex()

        val value: RDD[(Student, Long)] = stuRDD.partitionBy(new MyPartitioner(3))
        value.mapPartitionsWithIndex((partition, iterator) => {
            println(s"$partition, ${iterator.toList.mkString(",")}")
            iterator
        }).foreach(println)
    }
}

class MyPartitioner(partitions:Int) extends Partitioner {

    override def numPartitions: Int = partitions

    override def getPartition(key: Any): Int = {
        println("mypatiton ----------------->")

        if (key.isInstanceOf[Student]) {
            var stu = key.asInstanceOf[Student]
            if(stu.name.contains("李")) return 1
            else if(stu.name.contains("王")) return 2
            else return 4
        }else return 0
    }
}

你可能感兴趣的:(spark)