Spark Core

  1. Spark Core
    1.1 第一天介绍发展、特性。
    1.2 安装了standalone模式:伪分布式
    1.3 部署模式——local、standalone、yarn模式。 mesos、云…
    1.4 编码:java、lambda、scala
    1.5 底层执行流程

  2. Scala
    2.1 集合
    Array : 长度可变。长度不可变
    Set : 有序:TreeSet/无序
    List/ListBuffer: 长度、元素值可变、元素值长度不可变
    Tuple : 都是初始化之后就不可变了。元祖是只读的,作用传值。JavaBean
    Map : key对应的value可变/不可变
    2.2 函数式编程
    2.2.1 将函数赋值给一个变量,也衍生出来一种新的定义函数方式
    val method = (name:String) => println(name)
    def func(name:String) = println(name)

2.2.2 匿名函数
2.2.3 将函数作为一个参数传递给另一个函数
def func(f:(String) => Unit):Unit = {}

2.2.4 类型推断
func(s => println(“”))

2.2.5 高阶函数
2.2.6 闭包和柯力化

2.3 隐式转换
2.3.1 隐式函数
作用:不修改源码就能添加一个新的功能

2.3.2 隐式参数
隐式参数可传可不传,不传使用它默认的参数
sorted(implicit Ordering)




# Spark cored第一天知识点回顾

```sh
##1. Spark整个生态 (记忆)
##1.1 Spark core 、 Spark Sql 、 Spark Streaming 、 Structured Streaming 、 Spark MLlib 、 Spark Graphx

##2. spark发展史(了解)

##3. 比较hadoop(mapreduce)和spark的优缺点 : 最快100倍,即使离线计算也块至少10倍以上
##3.1 spark比mr块的最主要的原因是什么?基于内存计算

##4. spark部署
##4.1 windows端的部署
##4.2 spark的standalone模式(这种模式不太用)
- spark-evn.sh
- 内网ip和公网ip的区别

##4.3 编程初体验 : wordcount
- java : 又臭又长
- lambda :java8之后才出现的新语法
- scala :简洁

##5. 打包,存放的服务器端运行
##5.1 maven中加入打包插件(将第三方的jar包也打包到jar中)
##5.2 使用spark-submit来提交我们的spark程序

一 Spark发展历史

2009年在伯克利大学研究型项目,2010年通过BSD许可协议正式对外发布(开源)。2012年Spark第一篇论文发布,发布了第一个正式版(Spark 0.6.0)。

2013年被纳入到apache的基金会中,随后发布了Spark Streaming、Spark Mllib(机器学习库)、Shark(Spark on Hadoop)。

2014年5月底发布Spark1.0.0发布。发布Spark Graphx(图计算)、Spark SQL代替Shark

2015年,推出DataFrame(spark第二代编程模型),Spark第一代编程模型使用的RDD。国内真正的开始普及Spark的就是这年。

2016年,推出DataSet(spark第三代编程模型)

2017年,推出Structured Streaming(结构流),spark streaming的升级版。

2018年,Spark 2.4.0将spark底层的通信框架akka替换成了netty。

2022年,Spark已经发展3.x.x版本

二 Spark学习什么

Spark Core : Spark核心API,提供DAG分布式内存计算框架

Spark SQL : 提供交互式查询API

Spark Streaming/Structured Streaming :实时流处理框架

Spark ML : 机器学习API(在项目中用到)

Spark Graphx : 图计算

三 Spark的由来

  • Hadoop缺点

  • 不适合低延迟数据访问(Mapreduce的缺点)

  • 无法高效存储大量小文件

  • 不支持多用户写入以及任意修改文件

  • Hadoop与Spark的关系与区别

Hadoop(Mapreduce)Spark
起源20052009
数据处理引擎Batch(批处理)Batch(批处理)
编程模型MapreduceRDD(弹性分布式数据集)
内存管理Disk Based(基于磁盘)JVM Managed
延迟度HighMedium
吞吐量MediumHigh
优化机制ManualManual
APILow-Level(代码提供了底层功能)High-Level(高级功能以及封装好了)
流式处理支持NA(Storm)Spark Streaming/Structured Streaming(spark的流式计算本质上还是批式计算)
SQL支持Hive、ImpalaSpark SQL
Graph支持NA(pregel)Graphx
机器学习支持NASparkMllib
  • 处理流程比较

img

  • 总结
  1. Spark把运算的中间数据存放在内存,迭代计算效率更高;Mapreduce的中间结果需要落地到磁盘,这样必然会有磁盘的io操作,非常的影响性能。
  2. Spark容错性更高,通过弹性式分布式数据集RDD来实现高效容错。所谓弹性:就是空间足够就在内存中计算,空间不够就在磁盘中计算。如果一旦出现数据丢失,她可以利用DAG的血缘来做数据恢复。
  3. Spark的通用性更好,Spark提供了非常多的算子来帮助我们进行数据分析,比如transformation和action算子API功能来完成大量的分析工作。除此之外它还包括流式计算、图计算、机器学习库中的功能,而Mapreduce只提供了map和reduce两种操作,对于流式计算也很匮乏。
  4. Spark框架和生态更为复杂。首先有RDD、血缘lineage、执行时产生DAG(有向无环图)、stage划分等。很多时候spark job都需要根据不同业务场景进行调优从而达到性能要求;Mapreduce框架和生态就相对简单,对性能要求也较弱,但是想的运行还是较为稳定,适合长期后台运行。

1 Spark Core

实现了Spark的基本功能、包含了任务调度、内存管理、错误恢复、与存储系统交互等等模块。Spark Core还包含了RDD(弹性式分布式数据集)的API的定义

2 Spark SQL

是Spark用来操作结构化数据的程序包。通过Spark SQL的SQL功能,换言之就是使用SQL或HQL来进行数据的查询。Spark SQL支持多种数据源:比如hive、parquet、json等等。

3 Spark Streaming

Spark提供对实时数据进行流式计算的组件。提供了操作流式计算的API,并且与Spark Core中RDD API高度对应。

4 Structured Streaming

结构流是构建在Spark SQL引擎上的可伸缩且容错的流式处理引擎(理解未spark streqaming的升级版)。可以实现低至100毫秒的低延迟流式处理。

四 Spark的部署

Spark : 3.1.2
Hadoop : 3.2.1

1 基于windows的spark体验

1.1 解压

img

1.2 配置环境变量并启动

img

img

在配置好了环境变量之后
tip:
要求必须要配置SPARK_HOME这个变量,然后到其bin目录下启动spark-shell.cmd/spark-shell2.cmd

img

1.3 wordcount

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

(lizhi,1)
(wangsan,1)
(hello,5)
(you,1)
(lei,1)
(li,1)
(han,1)
(meimei,1)

img

2 基于Linux的Standalone模式的Spark安装

2.1 standalone模式安装

##1. 创建目录
[root@hadoop ~]# mkdir -p /opt/apps ## 用于存放安装软件的目录
[root@hadoop ~]# mkdir -p /opt/software ## 用于存放安装包的目录


##2. 解压
[root@hadoop software]# tar -zxvf spark-3.1.2-bin-hadoop3.2.tgz  -C /opt/apps/
[root@hadoop software]# tar -zxvf scala-2.12.8.tgz -C /opt/apps/
[root@hadoop software]# tar -zxvf jdk-8u261-linux-x64.tar.gz -C /opt/apps/


##3. 配置环境变量
[root@hadoop scala-2.12.8]# vi /etc/profile

export JAVA_HOME=/opt/apps/jdk1.8.0_261
export SCALA_HOME=/opt/apps/scala-2.12.8
export SPARK_HOME=/opt/apps/spark-3.1.2-bin-hadoop3.2
export PATH=$PATH:$JAVA_HOME/bin:$SCALA_HOME/bin:$SPARK_HOME/bin:$SPARK_HOME/sbin
export CLASS_PATH=.:$JAVA_HOME/bin

[root@hadoop scala-2.12.8]# source /etc/profile

##4. 配置spark
[root@hadoop scala-2.12.8]# cd /opt/apps/spark-3.1.2-bin-hadoop3.2/conf/
##4.1 配置spark-env.sh
[root@hadoop conf]# mv spark-env.sh.template spark-env.sh
[root@hadoop conf]# vim spark-env.sh

export JAVA_HOME=/opt/apps/jdk1.8.0_261

##4.2 配置workers
[root@hadoop conf]# mv workers.template workers
[root@hadoop conf]# vim workers

hadoop

##5. 如果是全分布式,你还需要将conf目录中的所有的文件分发给其他的服务器节点
[root@hadoop conf]# scp -r /opt/apps/spark-3.1.2-bin-hadoop3.2/ root@hadoop01:$PWD

##6. 启动Spark集群
[root@hadoop sbin]# mv start-all.sh start-all-spark.sh
[root@hadoop sbin]# mv stop-all.sh stop-all-spark.sh
[root@hadoop sbin]# start-all-spark.sh ## 启动
[root@hadoop sbin]# stop-all-spark.sh ## 停止

##7. 查看webui
http://hadoop:8080

2.2 关于云服务器公网ip和内网ip的区别

img

2.3 集群结构

img

2.4 spark-env.sh

# 使用spark-submit脚本的时候需要使用到以下的配置:
# ./bin/run-example or ./bin/spark-submit
# - HADOOP_CONF_DIR, to point Spark towards Hadoop configuration files
# - SPARK_LOCAL_IP, to set the IP address Spark binds to on this node
# - SPARK_PUBLIC_DNS, to set the public dns name of the driver program

# 配置执行器和驱动程序在服务器内部运行相关的配置
# - SPARK_LOCAL_IP, to set the IP address Spark binds to on this node
# - SPARK_PUBLIC_DNS, to set the public DNS name of the driver program
# - SPARK_LOCAL_DIRS, storage directories to use on this node for shuffle and RDD data
# - MESOS_NATIVE_JAVA_LIBRARY, to point to your libmesos.so if you use Mesos

# Spark使用Yarn模式的配置就在这个地方配置
# - SPARK_CONF_DIR, Alternate conf dir. (Default: ${SPARK_HOME}/conf)
# - HADOOP_CONF_DIR, to point Spark towards Hadoop configuration files
# - YARN_CONF_DIR, to point Spark towards YARN configuration files when you use YARN
# - SPARK_EXECUTOR_CORES, Number of cores for the executors (Default: 1).
# - SPARK_EXECUTOR_MEMORY, Memory per Executor (e.g. 1000M, 2G) (Default: 1G)
# - SPARK_DRIVER_MEMORY, Memory for Driver (e.g. 1000M, 2G) (Default: 1G)
export HADOOP_CONF_DIR=/opt/apps/hadoop-3.2.1/etc/hadoop
export YARN_CONF_DIR=/opt/apps/hadoop-3.2.1/etc/hadoop

# 配置使用standalone模式的spark集群的配置信息
# - SPARK_MASTER_HOST, to bind the master to a different IP address or hostname
# - SPARK_MASTER_PORT / SPARK_MASTER_WEBUI_PORT, to use non-default ports for the master
# - SPARK_MASTER_OPTS, to set config properties only for the master (e.g. "-Dx=y")
# - SPARK_WORKER_CORES, to set the number of cores to use on this machine
# - SPARK_WORKER_MEMORY, to set how much total memory workers have to give executors (e.g. 1000m, 2g)
# - SPARK_WORKER_PORT / SPARK_WORKER_WEBUI_PORT, to use non-default ports for the worker
# - SPARK_WORKER_DIR, to set the working directory of worker processes
# - SPARK_WORKER_OPTS, to set config properties only for the worker (e.g. "-Dx=y")
# - SPARK_DAEMON_MEMORY, to allocate to the master, worker and history server themselves (default: 1g).
# - SPARK_HISTORY_OPTS, to set config properties only for the history server (e.g. "-Dx=y")
# - SPARK_SHUFFLE_OPTS, to set config properties only for the external shuffle service (e.g. "-Dx=y")
# - SPARK_DAEMON_JAVA_OPTS, to set config properties for all daemons (e.g. "-Dx=y")
# - SPARK_DAEMON_CLASSPATH, to set the classpath for all daemons
# - SPARK_PUBLIC_DNS, to set the public dns name of the master or workers

export SPARK_MASTER_HOST=hadoop
export SPARK_MASTER_PORT=7077
export SPARK_WORKER_CORES=2
export SPARK_WORKER_MEMORY=1g


# Options for launcher
# - SPARK_LAUNCHER_OPTS, to set config properties and Java options for the launcher (e.g. "-Dx=y")

# Generic options for the daemons used in the standalone deploy mode
# - SPARK_CONF_DIR      Alternate conf dir. (Default: ${SPARK_HOME}/conf)
# - SPARK_LOG_DIR       Where log files are stored.  (Default: ${SPARK_HOME}/logs)
# - SPARK_LOG_MAX_FILES Max log files of Spark daemons can rotate to. Default is 5.
# - SPARK_PID_DIR       Where the pid file is stored. (Default: /tmp)
# - SPARK_IDENT_STRING  A string representing this instance of spark. (Default: $USER)
# - SPARK_NICENESS      The scheduling priority for daemons. (Default: 0)
# - SPARK_NO_DAEMONIZE  Run the proposed command in the foreground. It will not output a PID file.
# Options for native BLAS, like Intel MKL, OpenBLAS, and so on.
# You might get better performance to enable these options if using native BLAS (see SPARK-21305).
# - MKL_NUM_THREADS=1        Disable multi-threading of Intel MKL
# - OPENBLAS_NUM_THREADS=1   Disable multi-threading of OpenBLAS

export JAVA_HOME=/opt/apps/jdk1.8.0_261
export SCALA_HOME=/opt/apps/scala-2.12.8

3 Spark编程初体验

3.1 创建spark的maven工程

##1. 使用idea创建一个普通的maven工程,一直下一步即可
##2. 导入spark的依赖:pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qf.bigdata</groupId>
    <artifactId>spark</artifactId>
    <version>1.0</version>

    <properties>
        <spark-version>3.1.2</spark-version>

        <!-- 配置maven的编译器版本 -->
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- spark core -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.12</artifactId>
            <version>${spark-version}</version>
        </dependency>

        <!-- spark sql -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-sql_2.12</artifactId>
            <version>${spark-version}</version>
        </dependency>

        <!-- spark streaming -->
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-streaming_2.12</artifactId>
            <version>${spark-version}</version>
        </dependency>
    </dependencies>
</project>

##3. 创建scala目录并设置scala目录为编码目录
##3.1 CTRL + Shift + Alt + S
##3.2 Modules -> 选中scala目录 -> Sources。保存即可

3.2 Java : WordCount

package com.qf.bigdata.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;

/**
* 第一个Spark Core的WordCount程序
*/
public class Demo1_WordCount {
    public static void main(String[] args) {
        //1. 创建Spark的编程入口
        //1.1 Spark的配置对象,主要作用是用于配置spark相关的配置信息
        SparkConf conf = new SparkConf();
        //1.2 使用本地的硬件配置信息配置spark
        conf.setMaster("local[*]");
        //1.3 配置本次spark的程序的程序名称
        conf.setAppName(Demo1_WordCount.class.getSimpleName());
        //1.4 通过配置对象获取到Spark Core的核心对象
        JavaSparkContext sc = new JavaSparkContext(conf);
        
        //2. 加载外部数据
        JavaRDD<String> linesRDD = sc.textFile("H:/wc.txt");
        //2.1 遍历读取文件的内容
        linesRDD.foreach(new VoidFunction<String>() {
            public void call(String line) throws Exception {
                System.out.println(line);
            }
        });
        
        //3. flatmap
        JavaRDD<String> wordsRDD = linesRDD.flatMap(new FlatMapFunction<String, String>() {
            public Iterator<String> call(String line) throws Exception {
                return Arrays.asList(line.split("\\s+")).iterator();
            }
        });
        //3.1 遍历读取文件的内容
        wordsRDD.foreach(new VoidFunction<String>() {
            public void call(String line) throws Exception {
                System.out.println(line);
            }
        });
        
        //4. map
        JavaPairRDD<String, Integer> pairRDD = wordsRDD.mapToPair(new PairFunction<String, String, Integer>() {
            public Tuple2<String, Integer> call(String word) throws Exception {
                return new Tuple2<String, Integer>(word, 1);
            }
        });
        
        //4.1 遍历读取文件的内容
        pairRDD.foreach(new VoidFunction<Tuple2<String, Integer>>() {
            public void call(Tuple2<String, Integer> t) throws Exception {
                System.out.println(t._1 + "-->" + t._2);
            }
        });
        
        //5. reducebykey
        JavaPairRDD<String, Integer> resRDD = pairRDD.reduceByKey(new Function2<Integer, Integer, Integer>() {
            public Integer call(Integer v1, Integer v2) throws Exception {
                return v1 + v2;
            }
        });
        
        resRDD.foreach(new VoidFunction<Tuple2<String, Integer>>() {
            public void call(Tuple2<String, Integer> t) throws Exception {
                System.out.println(t._1 + "-->" + t._2);
            }
        });
    }
}

3.3 Java Lambda : WordCount

package com.qf.bigdata.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 scala.Tuple2;

import java.util.Arrays;

/**
 * Java Lambda
 */
public class Demo2_Lambda {
    public static void main(String[] args) {
        //1. 创建Spark的编程入口
        //1.1 Spark的配置对象,主要作用是用于配置spark相关的配置信息
        SparkConf conf = new SparkConf();
        //1.2 使用本地的硬件配置信息配置spark
        conf.setMaster("local[*]");
        //1.3 配置本次spark的程序的程序名称
        conf.setAppName(Demo1_WordCount.class.getSimpleName());
        //1.4 通过配置对象获取到Spark Core的核心对象
        JavaSparkContext sc = new JavaSparkContext(conf);

        //2. 加载外部数据
        JavaRDD<String> linesRDD = sc.textFile("H:/wc.txt");
        //2.1 遍历读取文件的内容
        linesRDD.foreach(line -> System.out.println(line));

        //3. flatmap
        JavaRDD<String> wordsRDD = linesRDD.flatMap(line -> Arrays.asList(line.split("\\s+")).iterator());
        wordsRDD.foreach(line -> System.out.println(line));

        //4. map
        JavaPairRDD<String, Integer> pairRDD = wordsRDD.mapToPair(word -> new Tuple2<String, Integer>(word, 1));
        pairRDD.foreach(t -> System.out.println(t._1 + "-->" +t._2));

        //5. reducebykey
        JavaPairRDD<String, Integer> resRDD = pairRDD.reduceByKey((v1, v2) -> v1 + v2);
        resRDD.foreach(t -> System.out.println(t._1 + "-->" +t._2));

        //6. 释放资源
        sc.close();
    }
}

3.4 Scala : WordCount

package com.qf.bigdata.spark.core.day1

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

object Demo1_Scala_Spark_WordCount {
  def main(args: Array[String]):Unit = {
    //1. 创建SparkContext对象
    val conf = new SparkConf().setMaster("local[*]").setAppName("scala_wordcount")
    val sc = new SparkContext(conf)
    
    //2. 读取数据
    val lineRDD:RDD[String] = sc.textFile("H:/wc.txt")
    //3. 切割
    val wordRDD:RDD[String] = lineRDD.flatMap(_.split("\\s+"))
    //4. map将数组中的元素转换成一个一个的元组
    val pairRDD:RDD[(String, Int)] = wordRDD.map((_, 1))
    //5. 汇总
    val res = pairRDD.reduceByKey((v1, v2) => v1 + v2)
    //6. 打印
    res.foreach(println)
    //7. 释放资源
    sc.stop()
  }
}

3.5 进一步简化的scala程序

package com.qf.bigdata.spark.core.day1

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

object Demo1_Scala_Spark_WordCount {
  def main(args: Array[String]):Unit = {
    //1. 创建SparkContext对象
    val sc = new SparkContext(new SparkConf().setMaster("local[*]").setAppName("scala_wordcount"))
    //2. 读取数据
    sc.textFile("H:/wc.txt").flatMap(_.split("\\s+"))
      .map((_, 1)).reduceByKey((v1, v2) => v1 + v2).reduceByKey((v1, v2) => v1 + v2).foreach(println)
    //7. 释放资源
    sc.stop()
  }
}

3.6 将spark程序打包存放到服务器中执行

3.6.1 修改依赖配置文件:pom.xml
<!-- 第三方jar包的打包插件 -->
    <build>
        <sourceDirectory>src/main/scala</sourceDirectory>
        <plugins>
            <!-- 第三方jar包的打包插件:如果你这里爆红,是因为之前没有下载过依赖,请你先下载再使用 -->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <!-- scala打包插件 -->
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <version>2.15.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
3.6.2 操作步骤
##1. 在HDFS中创建目录:/input
##2. 将wc.txt上传到/input目录中
##3. 将我们的程序打包并上传到linux:/data/jar
##tip:
## 修改代码的路径:sc.textFile("/wc.txt")
##4. 使用spark提交spark的程序:local模式
spark-submit \
--class com.qf.bigdata.spark.core.day1.Demo1_Scala_Spark_WordCount \
--master local \
/data/jars/spark-1.0-jar-with-dependencies.jar

3.7 关于Spark的部署模式

3.7.1 部署模式说明
##1. Local : 本地模式,一般用于测试,比如eclipse、idea写程序测试的时候
##2. standalone : 是spark自带的资源调度框架,如果使用此模式,你需要先自行的安装spark的分布式集群
##3. yarn(国内) : hadoop自带的资源调度框架,spark也可以将自己的job提交给yarn进行计算
##4. mesos(欧美) :资源调度框架,类似yarn

## tip:
我们在执行spark程序的时候需要设置master,从而来设置spark的部署模式,
那么设置部署模式一共有3种途径:
1. 在代码中设置(最高的)
2. 在配置文件中设置(其次)
3. 脚本/命令中设置(最低的)
3.7.2 standalone模式
##1. 修改代码,将setMaster的代码注释
##2. 重新打包
##3. 提交脚本
spark-submit \
--class com.qf.bigdata.spark.core.day1.Demo1_Scala_Spark_WordCount \
--master spark://10.206.0.4:7077 \
/data/jars/spark-1.0-jar-with-dependencies.jar

img

##1. 检查结果路径:
$SPARK_HOME/work/job#id/partitionId/结果日志
3.7.3 yarn模式
3.7.3.1 使用步骤
##1. 开启yarn的集群
##2. 提交脚本
spark-submit \
--class com.qf.bigdata.spark.core.day1.Demo1_Scala_Spark_WordCount \
--master yarn \
--deploy-mode cluster \
--driver-memory 600M \
--executor-memory 800M \
/data/jars/spark-1.0-jar-with-dependencies.jar

tip:
client : 用于测试,一般jar包在哪里提交,就在那里运行
cluster : 用于生产,将jar包提交给rm,由rm负责分配给集群中的nm执行

img

##1. 查看结果:在hadoop的日志中查看
$HADOOP_HOME/logs/userlogs/#applicationid/#containerid/日志
3.7.3.2 Yarn Client的工作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uvqu8ou0-1653622391466)(011.png)]

  1. Spark Yarn Client向Yarn的ResourceManager申请启动ApplicationMaster。同时在SparkContext初始化将DAGSecheduler和TaskScheduler等组件,由于我们选择yarn-client模式,程序回选择YarnClientClustserSchelder和YarnClientSchedulerBackend。
  2. ResourceManager收到请求之后,在集群中选择某一个NodeManager作为该应用程序分配第一个Contaier,要求在这个Container启动一个ApplicationMaster。与Yarn-cluser模式区别是在ApplicationMaster中不运行SparkContext,只与SparkContext进行联系进行资源分配。
  3. Client中的SparkContext在初始化完毕之后,与ApplicationMaster只建立通讯。向ResurceManager注册,根据任务信息向ResourceManager申请资源。
  4. 一旦在ApplicationMaster中申请到了资源,便与对应NodeManager通信,要求它在活得的Container中启动CoarseGrainedExecutorBackend,这个组件在启动后向Client中的SparkContext注册并申请Task。
  5. client中的SparkContext分配Task给CoarseGrainedExecutorBackend执行并且向Driver汇报运行的状态和进度。
  6. 当整个应用程序完成之后,client的SparkContext向ResourceManager申请注销并关闭自己
3.7.3.3 Yarn Cluster

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IcR518re-1653622391467)(012.png)]

3.7.3.4 小结:cluster和client模式的区别

​ 首先你要清楚一个概念:ApplicationMaster。在Yarn总,每个Application实例(Flink Application、Spark Application、Mapreduce Application…)都有ApplicationMaster进程,它是Application启动的时候产生的第一个容器。它负责和ResaurceManager打交道并请求资源,获取资源之后告诉NodeManager为其启动Container。从深层次将yarn-cluser和yarn-client模式的区别其实就是ApplicationMaster进程的区别。

​ Yarn-cluster模式下,Driver运行在ApplicationMaster中,它负责向Yarn申请资源,并监督job运行状况。用户提交job之后,client就可以关掉了,但是job会继续在yarn上运行。

​ yarn-client模式,ApplicationMaster仅仅向yarn请求executor,client会和请求的container通信来调度他们的工作,所以说client会不会关掉?答案肯定是不能。

得出一个结论:

​ - driver会和executor进行通信,所以yarn-cluster在提交app之后可以关闭client,但是yarn-client不可以

​ - yarn-cluster适合生产环境中使用,yarn-client适合在测试的交互环境中使用

3.7.4 Spark Job提交原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5uTFbOk3-1653622391467)(013.png)]

Client : 客户端进程,负责提交作业到Master。在这里指的就是spark-submit

Master : Standalone模式中的主节点(在Yarn模式中就是ResourceManager),负责接受Client提交的job,管理Worker并命令Worker启动Driver和Executor。

Worker : Standalone模式中的从节点(在Yarn模式中就是NodeManager),负责管理本节点的资源,定期向Master汇报心跳,接受Master的命令,启动Executor

Driver : 一个Spark Job运行时包含一个Driver的进程,也是job的主进程,负责job的解析、生成Stage并调度Task到Executor上。包含了:DAGScheduler、TaskScheduler。

Executor:真正执行"spark job"的地方(spark job中的task),一个集群中包含了多个Executor,每个executor接受Driver的Launch Task,一个executor可以执行多个task。

stage : 一个spark job一般会包含1到多个stage

task : 一个stage包含了1到多个task,通过多个task实行并行运算的功能

DAGScheduler : 实现将Spark job分解成1到多个Stage的组件,每个Stage根据RDD的Partition个数决定Task的个数,然后生成相应的Taskset,放到TaskScheduler。

TaskScheduler :实现Task分配到Executor执行的组件

五 Spark 算子

1 什么是算子?

​ 算子是数学中的一个概念。表示一个函数空间到另一个函数空间的映射:X -> Y。广义上讲算子可以推广到任何空间,比如内积空间。

对任何函数进行某一操作都可以认为是一个算子。对a函数进行b操作,那么这个b就是算子。

​ 算子 == 函数

在spark中算子分为两类:

  • Transformation : 转换算子
  • Action : 行动算子

2 Transformation : 转换算子

2.1 介绍

​ RDD中所有转换算子都是延迟加载/懒加载的!!!也就是说他并不会直接进行计算。相反的,它值是记录了这些应用的基础数据集上的转换动作。

​ 只有发生一个action的时候,这些转换算子才会真正的运行。

2.2 转换算子操作

2.2.1 map

他的作用几乎跟scala学习到的map的高阶函数一毛一样!!!

作用:就是对RDD中的数据进行一对一的处理。在spark中的所有的转换算子都只会产生一个处理数据之后的副本

def map[U: ClassTag](f: T => U): RDD[U]

package com.qf.bigdata.spark.core.day3

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

object Demo1_Map {
  def main(args: Array[String]): Unit = {
    //1. 获取到Spark的入口,SparkContext
    val sc = new SparkContext("local[*]", "demo_map", new SparkConf())

    //2. 集合
    val list: Range.Inclusive = 1 to 7

    //3. 加载内存中的数据
    val listRDD: RDD[Int] = sc.parallelize(list)

    //4. map
    val mapRDD: RDD[Double] = listRDD.map(num => num * 10.0)

    //5. foreach : action算子
    mapRDD.foreach(println)
  }
}
2.2.2 flatMap
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
package com.qf.bigdata.spark.core.day3

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

object Demo2_FlatMap {
  def main(args: Array[String]): Unit = {
    //1. 获取到Spark的入口,SparkContext
    val sc = new SparkContext("local[*]", "demo_map", new SparkConf())

    //2.
    val lineRDD: RDD[String] = sc.parallelize(List(
      "hello hello me"
    ))

    //3.
    val mapRDD: RDD[String] = lineRDD.flatMap(line => line.split("\\s+"))

    mapRDD.foreach(println)
  }
}

2.2.3 filter
def filter(f: T => Boolean): RDD[T]
package com.qf.bigdata.spark.core.day3

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

object Demo2_FlatMap {
  def main(args: Array[String]): Unit = {
    //1. 获取到Spark的入口,SparkContext
    val sc = new SparkContext("local[*]", "demo_map", new SparkConf())

    //2.
    val lineRDD: RDD[String] = sc.parallelize(List(
      "hello hello me"
    ))

    //3.
    val mapRDD: RDD[String] = lineRDD.flatMap(line => line.split("\\s+"))

    mapRDD.filter(line => line.equals("hello")).foreach(println)
  }
}
2.2.4 sample

类似于Hive中的TableSample。

作用:抽样查询。

def sample(
    withReplacement: Boolean, // 抽样方式,true返回抽样,false不反回抽样
    fraction: Double, // 抽样比例,取值0~1(不准确)
    seed: Long = Utils.random.nextLong) // 抽样的种子
: RDD[T]
package com.qf.bigdata.spark.core.day3

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo3_Sample {
  def main(args: Array[String]): Unit = {
    //1. 获取到本地sparkContext对象
    val sc: SparkContext = SparkUtils.getLocalSparkContext()
    //2. 加载数据
    val listRDD: RDD[Int] = sc.parallelize(1 to 10000)
    //3. 抽样
    var sampleRDD: RDD[Int] = listRDD.sample(true, 0.001)
    Thread.sleep(100)
    println("抽样空间 :" + sampleRDD.count())
    sampleRDD.foreach(println)
    println("-" * 100)
    sampleRDD = listRDD.sample(true, 0.001)
    Thread.sleep(100)
    println("抽样空间 :" + sampleRDD.count())
    sampleRDD.foreach(println)
    // 释放资源
    SparkUtils.close(sc)
  }
}
2.2.5 union
def union(other: RDD[T]): RDD[T] // 将两个RDD进行合并的方法
package com.qf.bigdata.spark.core.day3

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo4_Union {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()
    //1. 加载数据
    val list1RDD: RDD[Int] = sc.parallelize(List(
      1, 2, 3, 4, 5
    ))

    val list2RDD: RDD[Int] = sc.parallelize(List(
      1, 2, 4, 6, 5
    ))

    //2. union : 将连个rdd进行聚合
    val unionRDD: RDD[Int] = list1RDD.union(list2RDD)

    //3. 打印
    unionRDD.foreach(println)


    SparkUtils.close(sc)
  }
}

2.2.6 distinct

去重

def distinct(): RDD[T]
package com.qf.bigdata.spark.core.day3

import org.apache.spark.rdd.RDD

object Demo5_Distinct {
  def main(args: Array[String]): Unit = {
    val sc = SparkUtils.getLocalSparkContext()

    val listRDD: RDD[Int] = sc.parallelize(List(
      1, 2, 2, 3, 3, 4, 5, 6
    ))

    val disRDD: RDD[Int] = listRDD.distinct()

    disRDD.foreach(println)

    SparkUtils.close(sc)
  }
}
2.2.7 join

join算子就是和sql的join查询一样的功能。但是这里如果想要 使用join算子,有一个前提:

你RDD中的元素必须得是一个二维元祖。

一 、回忆sql中的join

  1. 交叉查询

select * from A a accross Join B b; 因为产生笛卡儿积,所以一般都不用

  1. 内连接 : Inner Join

select * from A a [inner] Join B b [where|on a.xx = b.xx]

  1. 外连接

3.1 左外连接 : 以左表为主体,不管where条件是否成立,左边的表的数据都会显示

select * from A a left [outer] Join B b [where|on a.xx = b.xx]

3.2 右外连接 : 以右表为主体,不管where条件是否成立,右边的表的数据都会显示

select * from A a right [outer] Join B b [where|on a.xx = b.xx]

3.3 全外连接 : 以两边表为主体,不管where条件是否成立,两边边的表的数据都会显示

select * from A a full[outer] Join B b [where|on a.xx = b.xx]

3.4 左半连接

select * from A a left semi Join B b [where|on a.xx = b.xx]

二、 Spark中的Join

e.g.RDD[(K,V)] : K表示SQL中的on后面字段名称的值

RDD1[(K,V)], RDD2[(K,W)]

  1. 内链接

val rdd:RDD[(K, (V,W))] = rdd1.join(rdd2)

  1. 左连接

val rdd:RDD[(K, (V,Option[W]))] = rdd1.leftOuterJoin(rdd2)

  1. 右连接

val rdd:RDD[(K, (Option[V],W))] = rdd1.rightOuterJoin(rdd2)

  1. 全外

val rdd:RDD[(K, (Option[V],Option[W]))] = rdd1.fullOuterJoin(rdd2)

package com.qf.bigdata.spark.core.day3

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo6_Join {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()
    //1. 准备数据
    //1.1 学生
    val stuRDD: RDD[String] = sc.parallelize(List(
      "1 刘诗诗 女 18",
      "2 范冰冰 女 34",
      "3 高圆圆 女 30",
      "4 欧阳娜娜 女 19",
      "5 李冰冰 女 40"
    ))

    //1.2 成绩
    val scoreRDD: RDD[String] = sc.parallelize(List(
      "1 语文 59",
      "2 数学 49",
      "3 英语 39",
      "4 体育 100",
      "6 化学 0"
    ))

    //2. 改造数据
    val stuTupleRDD: RDD[(Int, String)] = stuRDD.map(line => {
      val sid = line.substring(0, line.indexOf(" ")).toInt
      val info = line.substring(line.indexOf(" ") + 1)
      (sid, info)
    })

    val scoreTupleRDD: RDD[(Int, String)] = scoreRDD.map(line => {
      val sid = line.substring(0, line.indexOf(" ")).toInt
      val info = line.substring(line.indexOf(" ") + 1)
      (sid, info)
    })

    println(stuTupleRDD.take(10).mkString(","))
    println(scoreTupleRDD.take(10).mkString(","))

    //3. join
    //3.1 查询有成绩的学生
    val stuInnerScoreRDD: RDD[(Int, (String, String))] = stuTupleRDD.join(scoreTupleRDD)
    println(stuInnerScoreRDD.take(10).mkString(","))

    //3.2 left
    val stuLeftScoreRDD: RDD[(Int, (String, Option[String]))] = stuTupleRDD.leftOuterJoin(scoreTupleRDD)
    println(stuLeftScoreRDD.take(10).mkString(","))

    //3.3 right
    val stuRightScoreRDD: RDD[(Int, (Option[String], String))] = stuTupleRDD.rightOuterJoin(scoreTupleRDD)
    println(stuRightScoreRDD.take(10).mkString(","))

    //3.4 full
    val stuFullScoreRDD: RDD[(Int, (Option[String], Option[String]))] = stuTupleRDD.fullOuterJoin(scoreTupleRDD)
    println(stuFullScoreRDD.take(10).mkString(","))

    SparkUtils.close(sc)
  }
}
2.2.8 groupByKey

groupBy —— 分组,由自己去指定字段分组

groupByKey —— 分组,他的分组字段就是这个key。所以要求RDD的元素必须得是一个二维的元组RDD[(K,V)]

这个算子在实际生产中使用不多,他不会进行局部聚合,就会导致分布式环境下这个算子性能不足

def groupByKey(): RDD[(K, Iterable[V])]
package com.qf.bigdata.spark.core.day3

import org.apache.spark.rdd.RDD

object Demo7 {
  def main(args: Array[String]): Unit = {
    val sc = SparkUtils.getLocalSparkContext()

    //1. 加载数据
    val stuRDD: RDD[String] = sc.parallelize(List(
      "1,杨过,1,22,zzbigdata-2201",
      "2,郭靖,1,42,zzbigdata-2202",
      "3,张无忌,1,2,zzbigdata-2203",
      "4,黄蓉,2,452,zzbigdata-2202",
      "5,周芷若,2,12,zzbigdata-2203"
    ))

    //2. 改造数据
    val stuClassRDD: RDD[(String, String)] = stuRDD.map(line => {
      val index: Int = line.lastIndexOf(",")
      val className = line.substring(index + 1)
      val info = line.substring(0, index)
      (className, info)
    })

    //3. 分组:[(zzbigdata-2202, [(2,郭靖,1,42), (4,黄蓉,2,452)]), (...)]
    val gbkRDD: RDD[(String, Iterable[String])] = stuClassRDD.groupByKey()

    //4. 打印结果
    println(gbkRDD.take(10).mkString(","))

    SparkUtils.close(sc)
  }
}
2.2.9 groupBy
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
e.g.
package com.qf.bigdata.spark.core.day3

import org.apache.spark.rdd.RDD

object Demo7 {
  def main(args: Array[String]): Unit = {
    val sc = SparkUtils.getLocalSparkContext()

    //1. 加载数据
    val stuRDD: RDD[String] = sc.parallelize(List(
      "1,杨过,1,22,zzbigdata-2201",
      "2,郭靖,1,42,zzbigdata-2202",
      "3,张无忌,1,2,zzbigdata-2203",
      "4,黄蓉,2,452,zzbigdata-2202",
      "5,周芷若,2,12,zzbigdata-2203"
    ))

    //3. 分组:[(zzbigdata-2202, [(2,郭靖,1,42), (4,黄蓉,2,452)]), (...)]
    val classRDD: RDD[(String, Iterable[String])] = stuRDD.groupBy(line => {
      val index: Int = line.lastIndexOf(",")
      val className = line.substring(index + 1)
      className
    })

    //4. 打印结果
    println(classRDD.take(10).mkString(","))

    SparkUtils.close(sc)
  }
}

2.2.10 reduceByKey

和GroupByKey一样,需要先将这个RDD中的元素造成一个二维元祖。

作用:作用和GroupByKey类似,也是先通过Key来进行分组。不同之处,reduceByKey会再获取到每次的前一个元素和后一个元素。分组聚合求和的场景使用得比较多。

def reduceByKey(func: (V, V) => V): RDD[(K, V)]
e.g.
val sum = 0
for(i <- 1 to 10) {
    sum = sum + i
}

第一个V : sum
第二个V : i
package com.qf.bigdata.spark.core.day3

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo8_ReduceByKey {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    val lineRDD: RDD[String] = sc.textFile("C:\\ftp\\pom.xml")

    //wordcount
    val resRDD: RDD[(String, Int)] = lineRDD.flatMap(_.split(",")).map((_, 1)).reduceByKey(_ + _)

    println(resRDD.take(10).mkString(","))

    SparkUtils.close(sc)
  }
}

Spark中也有reduce算子,但是它不是转换算子,他是action算子

def reduce(f: (T, T) => T): T
2.2.11 sortByKey

RDD中的元素必须是二维元祖。无论是sortBy还是sortByKey也好,他们都只能保证分区内有序

package com.qf.bigdata.spark.core.day3

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo9_SortByKey {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    //1. 准备数据
    //1.1 学生
    val stuRDD: RDD[String] = sc.parallelize(List(
      "1 刘诗诗 女 18",
      "2 范冰冰 女 34",
      "3 高圆圆 女 30",
      "4 欧阳娜娜 女 19",
      "5 李冰冰 女 40"
    ),3)

    //1.2 成绩
    val scoreRDD: RDD[String] = sc.parallelize(List(
      "1 语文 59",
      "2 数学 49",
      "3 英语 39",
      "4 体育 100",
      "6 化学 0"
    ),3)

    //2. 改造数据
    val stuTupleRDD: RDD[(Int, String)] = stuRDD.map(line => {
      val sid = line.substring(0, line.indexOf(" ")).toInt
      val info = line.substring(line.indexOf(" ") + 1)
      (sid, info)
    })

    val scoreTupleRDD: RDD[(Int, String)] = scoreRDD.map(line => {
      val sid = line.substring(0, line.indexOf(" ")).toInt
      val info = line.substring(line.indexOf(" ") + 1)
      (sid, info)
    })

    // 默认升序,设置为降序
//    println(stuTupleRDD.sortByKey(false).take(10).mkString(","))
    stuTupleRDD.sortByKey(false).foreach(println)

    SparkUtils.close(sc)
  }
}
2.2.12 mapPartitions

map算子的升级版。map算子的批处理版。此算子,一个分区作为一个批次来进行数据处理。

def mapPartitions[U: ClassTag](
    f: Iterator[T] => Iterator[U],
    preservesPartitioning: Boolean = false): RDD[U
2.2.13 mapPartitionsWithIndex

是mapPartitions的升级版,他比前者多一个index(分区的索引)

def mapPartitionsWithIndex[U: ClassTag](
    f: (Int, Iterator[T]) => Iterator[U],
    preservesPartitioning: Boolean = false): RDD[U]
package com.qf.bigdata.spark.core.day3

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo10_MapPartitions {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    val listRDD: RDD[Int] = sc.parallelize(1 to 10, 3)

    listRDD.map(num => num * 10).foreach(println)
    println("-" * 10)
    listRDD.mapPartitions(partitions => partitions.map(_ * 10)).foreach(println)
    println("-" * 10)
    listRDD.mapPartitionsWithIndex {
      case (index, partitions) => {
        println(s"partition's id is ${index}, partitions 's data is ${partitions.mkString(",")}")
        partitions.map(_ * 10)
      }
    }.foreach(println)
    SparkUtils.close(sc)
  }
}
2.2.14 repatition和coalesce

​ 重分区。其实repatition是coalesce实现的。coalesce默认使用的是窄依赖,repatition是宽依赖的。一般使用的时候对于分区的情况我们都用coalesce;分区增加使用的是repatition。

  • coalesce&repatition
def coalesce(numPartitions: Int, shuffle: Boolean = false,
             partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
            (implicit ord: Ordering[T] = null)
    : RDD[T]
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
package com.qf.bigdata.spark.core.day3

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo11_Repatition {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    val listRDD: RDD[Int] = sc.parallelize(1 to 10000)
    println(listRDD.getNumPartitions)

    //重分区
    val colRDD: RDD[Int] = listRDD.coalesce(10, true)
    println(colRDD.getNumPartitions)
    val reRDD: RDD[Int] = listRDD.repartition(10)

    val colRDD2: RDD[Int] = reRDD.coalesce(1)
    println(colRDD2.getNumPartitions)
    
    SparkUtils.close(sc)
  }
}

2.2.15 CombineByKey

​ 通过观察底层(ReduceByKey、GroupByKey)源码,发现他们的底层源码都是通过CombineByKeyWithClassTag这个函数来实现的。

​ CombineByKey其实就是CombineByKeyWithClassTag的简化版。

​ CombineByKey是Spark底层的聚合算子之一,按照key进行聚合,spark提供的很多的高阶算子都是基于这个算子实现的。

​ 一般使用spark用不到这个算子,但是一旦你发现spark自带的算子不够用的,你需要自定义聚合算子的时候,就需要利用这个算子来自定义聚合算子。

def combineByKey[C](
    createCombiner: V => C, // 初始化方法,每个key第一次出现的自动的调用这个方法
    mergeValue: (C, V) => C, // 分区内聚合的方法,相同的key,对用了不同的value,在相同的区中的时候会调用此方法
    mergeCombiners: (C, C) => C, // 全局聚合方法,所有的相同key的分区的数据会调用此方法进行聚合
): RDD[(K, C)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1AW3BAMj-1653622391468)(014.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vDhSuGnF-1653622391468)(015.png)]

2.2.15.1 重写GroupByKey
package com.qf.bigdata.spark.core.day3

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

import scala.collection.mutable.ArrayBuffer

object Demo12_CombineByKey_GBK {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    //1. 加载数据
    val stuRDD: RDD[String] = sc.parallelize(List(
      "王东阳,zzbigdata2201",
      "徐明伟,zzbigdata2201",
      "毕帆,zzbigdata2201",
      "徐娟娟,hzbigdata2102",
      "乌力吉,hzbigdata2102"
    ), 3)

    //2. 查看哪些数据在哪些区
    val class2InfoRDD: RDD[(String, String)] = stuRDD.mapPartitionsWithIndex {
      case (partitionId, partitions) => {
//        println(s"${partitionId} : ${partitions.mkString(",")}")
        partitions.map(line => {
          val index = line.lastIndexOf(",")
          val className = line.substring(index + 1)
          val info = line.substring(0, index)
          (className, info)
        }).toIterator
      }
    }

    println("gbk ================================================")
    val gbkRDD: RDD[(String, Iterable[String])] = class2InfoRDD.groupByKey()
    gbkRDD.foreach(println)

    println("combineByKey ---> GBK ================================================")
    val cbk2GBK: RDD[(String, ArrayBuffer[String])] = class2InfoRDD.combineByKey(createCombiner, mergeValue, mergeCombiners)
    cbk2GBK.foreach(println)

    SparkUtils.close(sc)
  }

  /**
   * 在同一个分区中,出现第一次key的时候调用一次
   * 0 : 王东阳,zzbigdata2201
   * 1 : 徐明伟,zzbigdata2201,毕帆,zzbigdata2201
   * 2 : 徐娟娟,hzbigdata2102,乌力吉,hzbigdata2102
   *
   * 调用3次
   */
  def createCombiner(stu:String):ArrayBuffer[String] = {
    println(s"==============createCombiner<${stu}>===============================")
    val ab: ArrayBuffer[String] = ArrayBuffer[String]()
    ab.append(stu)
    ab
  }

  /**
   * 分区内的局部聚合(相同的key会调用一次此函数)
   * 0 : 王东阳,zzbigdata2201
   * 1 : 徐明伟,zzbigdata2201,毕帆,zzbigdata2201
   * 2 : 徐娟娟,hzbigdata2102,乌力吉,hzbigdata2102
   *
   * 调用2次
   */
  def mergeValue(ab:ArrayBuffer[String], stu:String):ArrayBuffer[String] = {
    println(s"==============mergeValue局部聚合<${ab}>, 被聚合:${stu} ===============================")
    ab.append(stu)
    ab
  }

  /**
   * 所有的分区中的相同的key的数据都回调用此方法进行全局聚合
   * 0 : 王东阳,zzbigdata2201
   * 1 : 徐明伟,zzbigdata2201,毕帆,zzbigdata220
   * 2 : 徐娟娟,hzbigdata2102,乌力吉,hzbigdata2102
   *
   * 调用1次
   *
   * sum += i
   */
  def mergeCombiners(sum:ArrayBuffer[String], i:ArrayBuffer[String]):ArrayBuffer[String] = {
    println(s"==============mergeCombiners全局聚合<${sum}>, 被聚合:${i} ===============================")
    sum .++:(i)
  }
}

2.2.15.2 重写ReduceByKey
package com.qf.bigdata.spark.core.day3

import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo13_CombineByKey {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    val lineRDD: RDD[String] = sc.parallelize(List(
      "hello hello hello",
      "me you him"
    ))

    val mapRDD: RDD[(String, Int)] = lineRDD.flatMap(_.split("\\s+")).map((_, 1))

    // reduceByKey
    println("reduceByKey===============================")
    mapRDD.reduceByKey(_+_).foreach(println)

    println("combineByKey ========================= reduceByKey ==================================")
    mapRDD.combineByKey[Int](
      (num:Int) => num,
      (sum:Int, num:Int) => sum + num,
      (total:Int, sum:Int) => total + sum).foreach(println)

    SparkUtils.close(sc)
  }
}
2.2.16 aggregateByKey

他的底层也是通过CombineByKeyWithClassTag实现的。他也是自定义算子,用来模拟其他算子的。

aggregateByKey[U: ClassTag]
	(zeroValue: U) // 初始化值
	(seqOp: (U, V) => U, // 局部聚合
     combOp: (U, U) => U) // 全局聚合
: RDD[(K, U)]
2.2.16.1 重写GroupByKey
package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

import scala.collection.mutable.ArrayBuffer

object Demo1_AggregateByKey_GBK {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    //1. 加载数据
    val stuRDD: RDD[String] = sc.parallelize(List(
      "王东阳,zzbigdata2201",
      "徐明伟,zzbigdata2201",
      "毕帆,zzbigdata2201",
      "徐娟娟,hzbigdata2102",
      "乌力吉,hzbigdata2102"
    ), 3)

    //2. 查看哪些数据在哪些区
    val class2InfoRDD: RDD[(String, String)] = stuRDD.mapPartitionsWithIndex {
      case (partitionId, partitions) => {
//        println(s"${partitionId} : ${partitions.mkString(",")}")
        partitions.map(line => {
          val index = line.lastIndexOf(",")
          val className = line.substring(index + 1)
          val info = line.substring(0, index)
          (className, info)
        }).toIterator
      }
    }

    println("gbk ================================================")
    val gbkRDD: RDD[(String, Iterable[String])] = class2InfoRDD.groupByKey()
    gbkRDD.foreach(println)

    println("aggregateByKey ---> GBK ================================================")
    val abkGBK: RDD[(String, ArrayBuffer[String])] = class2InfoRDD.aggregateByKey(ArrayBuffer[String]())(
      seqOp, combOp
    )
    abkGBK.foreach(println)


    SparkUtils.close(sc)
  }

  /**
   * 局部聚合
   */
  def seqOp(ab:ArrayBuffer[String], stu:String):ArrayBuffer[String] = {
    ab.append(stu)
    ab
  }

  /**
   * 全局聚合
   * @param ab1 : sum
   * @param ab2 : i
   */
  def combOp(ab1:ArrayBuffer[String], ab2:ArrayBuffer[String]):ArrayBuffer[String] = ab1.++:(ab2)
}
2.2.16.2 重写ReduceByKey
package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo2_AggregateByKey_RBK {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    val lineRDD: RDD[String] = sc.parallelize(List(
      "hello hello hello",
      "me you him"
    ))

    val mapRDD: RDD[(String, Int)] = lineRDD.flatMap(_.split("\\s+")).map((_, 1))

    // reduceByKey
    println("reduceByKey===============================")
    mapRDD.reduceByKey(_+_).foreach(println)

    println("aggregateByKey ========================= reduceByKey ==================================")

    mapRDD.aggregateByKey(0)(_+_, _+_).foreach(println)
    
    SparkUtils.close(sc)
  }
}

3 Action算子

3.1 介绍

​ action算子的作用是用来驱动转换算子的,说白了就是,转换算子是延迟加载,没有action算子的驱动,转换算子是不执行的!!!所以一个spark程序中至少得有一个action算子。

​ 如果RDD中有多个分区,所有的算子都是在RDD上的分区中执行的,而不是在本地Driver中执行

3.2 操作
3.2.1 foreach
3.2.2 count

作用:统计RDD中的元素的个数

def count(): Long
package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo3_Count {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()
    val listRDD: RDD[Int] = sc.parallelize(1 to 100)
    println(listRDD.count())
    SparkUtils.close(sc)
  }
}
3.2.3 take(n)

作用:返回RDD中的前n个元素。多用于求去topn的业务

def take(num: Int): Array[T]
package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo3_Take {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()
    val listRDD: RDD[Int] = sc.parallelize(1 to 100)
    println(listRDD.take(3).mkString(","))
    SparkUtils.close(sc)
  }
}
3.2.4 first

取RDD中的第一个元素

package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo3_First {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()
    val listRDD: RDD[Int] = sc.parallelize(1 to 100)
    val first: Int = listRDD.first()
    println(first)
    SparkUtils.close(sc)
  }
}
3.2.5 collect

​ 字面解释,收集,这里表示拉取。这个算子的作用就是将不同分区的数据拉取到本地处理,但是这个算子有很大的风险,第一,他会导致driver所在的服务器的内存压力大;第二,在网络中进行大规模的数据传入本身存在巨大的风险;第三,大规模在网络中的传输数据速度很慢。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pVwq9aWv-1653622391469)(016.png)]

3.2.6 reduce

在spark中reduce是一个action算子。reduce不分组,只是对RDD专用的所有的元素进行聚合

def reduce(f: (T, T) => T): T
package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo4_Reduce {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    val userRDD: RDD[(String, String)] = sc.parallelize(List(
      ("name", "lixi"),
      ("age", "36"),
      ("gender", "male")
    ))

    //1. 匿名函数
    /*
     * ((String, String), (String, String)) => (String, String)
     */
//    val res1: (String, String) = userRDD.reduce(kv:Tuple2[String, String] => kv._1 + "_" + kv._2)
//    println(res1)
    println("----")

    //2. 匹配模式
    val res2: (String, String) = userRDD.reduce {
      case ((k1, v1), (k2, v2)) => (k1 + "_" + k2, v1 + "_" + v2)
    }
    println(res2)

    SparkUtils.close(sc)
  }
}
3.2.7 countByKey

长得比较像转换算子,但是其实他是action算子。

作用:统计key的次数

package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo5_CountByKey {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    val userRDD: RDD[(String, String)] = sc.parallelize(List(
      ("name", "lixi"),
      ("age", "36"),
      ("gender", "male"),
      ("name", "rocklee")
    ))

    val map: collection.Map[String, Long] = userRDD.countByKey()
    println(map)

    SparkUtils.close(sc)
  }
}
3.2.8 saveAsTextFile

作用:以文本文件的形式保存数据(本地文件系统、HDFS)

def saveAsTextFile(path: String): Unit
package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD

object Demo5_CountByKey {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    val userRDD: RDD[(String, String)] = sc.parallelize(List(
      ("name", "lixi"),
      ("age", "36"),
      ("gender", "male"),
      ("name", "rocklee")
    ))

    userRDD.saveAsTextFile("src/main/resources/1")

    SparkUtils.close(sc)
  }
}

六 RDD编程模型介绍

1 什么是RDD

​ 弹性式分布式数据集。Spark中计算的基本单元。分布式存储提升了RDD的读写性能,弹性表示内存充足数据就存储在内存中,内存不足,就存储在磁盘中。

​ 一组RDD可以形成DAG(有向(方向)无环图)

​ RDD是Spark的第一代编程模型。

2 Spark的持久化设置

​ 一般来说将数据从内存中保存到磁盘中就叫做持久化。从某些场景下,将数据从内存中溢出到缓存中也叫做持久化。

​ 在RDD中有一个方法persist或cache,他们的作用就是标记RDD持久化。

​ persist是持久化数据到磁盘中,可以选择不同的持久化策略

持久化策略含义
MEMORY_ONLY(默认)RDD中的数据以未经过序列化的Java对象的形式保存在内存中。如果内存不足,剩余的部分不会持久化,使用的时候, 没有持久化的部分重新加载。这种效率还可以。
MEMORY_ONLY_SER相比较MEMORY_ONLY,多了一个序列化功能。他会对内存中的数据经过序列化为一个字节数组。
MEMORY_AND_DISK弹性表示内存充足数据就存储在内存中(在内存中的时候没有经过序列化),内存不足,就存储在磁盘中
MEMORY_AND_DISK_SER弹性表示内存充足数据就存储在内存中(在内存经过序列化),内存不足,就存储在磁盘中
DISK_ONLY只存储在磁盘中
xxx_2上述的策略后面加上_2。比如:MEMORY_ONLY_2。_2表示的副本。性能肯定下降,但是安全性能提升。一般不用
HEAP_OFF使用堆外内存(非spark内存),可以spark数据存储到:Redis、HBase、ClickHouse。。。
package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel

object Demo6_Persist {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    var start = System.currentTimeMillis()
    val textRDD: RDD[String] = sc.textFile("C:\\ftp\\SZBIGDATA-2103\\day3-flink旅游\\resource\\QParameterTool.java")
    var count: Long = textRDD.count()
    println(s"没有持久化 lines count : ${count}, cost time : ${System.currentTimeMillis() - start} ms")

    textRDD.persist(StorageLevel.MEMORY_AND_DISK) // 开启持久化
      start = System.currentTimeMillis()
      count = textRDD.count()
      println(s"持久化 lines count : ${count}, cost time : ${System.currentTimeMillis() - start} ms")
    textRDD.unpersist() // 卸载持久化

    SparkUtils.close(sc)
  }
}

3 DAG :有向无环图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qPRDEFNg-1653622391470)(017.png)]

4 共享变量

所谓的共享变量就是大家都能使用的变量。那这个“大家”指什么东西?指的是执行器(executor)

4.1 广播变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t2nmvabd-1653622391470)(018.png)]

package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD

object Demo7_Broadcast {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    //1. 加载数据
    val stuRDD = sc.parallelize(List(
      Student("1", "令狐冲", "1", 11),
      Student("2", "任盈盈", "0", 12),
      Student("3", "岳灵珊", "0", 13),
      Student("4", "东方姑娘", "2", 14),
    ))
    val genderMap = Map(
      "0" -> "小姐姐",
      "1" -> "小哥哥"
    )
    //2. 经过分析,我们stuRDD在执行的时候会被分成多个task执行,需要genderMap多次,这样增大了传输,所以我们需要对他进行广播
    val genderBC: Broadcast[Map[String, String]] = sc.broadcast(genderMap)
    //3. 处理数据
    val resRDD: RDD[Student] = stuRDD.map(stu => {
      //3.1 从广播变量中获取到性别的值
      val gender: String = genderBC.value.getOrElse(stu.gender, "人妖")
      Student(stu.id, stu.name, gender, stu.age)
    })
    resRDD.foreach(println)
    SparkUtils.close(sc)
  }
}

case class Student(id:String, name:String, gender:String, age:Int)

4.2 累加器:类似于Mapreduce学习计数器(accumulator)

package com.qf.bigdata.spark.core.day4

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.SparkContext
import org.apache.spark.util.LongAccumulator

object Demo8_Accumulator {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    val stuRDD = sc.parallelize(List(
      "Our materialistic society to led us to believe that to cannot be obtained without having money."
    ))

    // 统计to这个单词出现了几次
    val count: Long = stuRDD.flatMap(_.split("\\s+")).filter(word => word == "to").count()
    println(count)

    // 利用累加器来统计
    // 获取到累加器
    val accumulator: LongAccumulator = sc.longAccumulator
    stuRDD.flatMap(_.split("\\s+")).filter(word => {
      val isok = word == "to"
      if (isok) accumulator.add(1)
      isok
    }).foreach(println)
    println(accumulator.value)

    SparkUtils.close(sc)
  }
}

5 分区

​ 为了保证RDD中的并行读写,从而保证数据处理的速度,我们在RDD中有分区的概念,一个完整的数据RDD中被分为了多个分区存储。

​ 对于数据分拆之后究竟存储于哪一个分区,是由分区器(Partitioner)决定。在Spark中默认提供了集中分区器,我们也可以自定义分区器。

​ Spark中目前支持的分区器:Hash分区和Range分区。

5.1 Partitioner

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJYpB5yi-1653622391471)(019.png)]

5.1.1 HashPartitioner

90%以上RDD的相关分区默认都是使用的这个分区器。

作用:依据RDD中的key值进行hashcode,然后对整个分区数取模,得到paritionId值。同时支持key为null的情况,如果key为null将把这条数据存储到0分区中。

5.1.2 自定义Partitioner

根据自己的需求,量身定制的数据应该存储到哪一个分区中。

5.1.2.1 CustomerPartitioner
package com.qf.bigdata.spark.core.day5

import org.apache.spark.Partitioner

import scala.util.Random

/**
 * 随机分区器
 */
class CustomerPartitioner(partitions: Int) extends Partitioner{

  private val random = new Random()

  /**
   * 表示自定义分区器的分区数总共是多少
   */
  override def numPartitions: Int = partitions

  /**
   * 根据key值分配数据到哪个分区中
   */
  override def getPartition(key: Any): Int = random.nextInt(numPartitions)
}

5.1.2.2 Demo1_Partitioner
package com.qf.bigdata.spark.core.day5

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkContext}

object Demo1_Partitioner {
  def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    //1. 加载数据,并处理数据为一个二维的元祖
    val listRDD: RDD[(Int, Long)] = sc.parallelize(1 to 10, 4)
      .zipWithIndex() // [(1,0),(2,1),(3,2),...,(10,9)]

    //2. 如果我们不设置自定义分区器,默认使用的是hash分区
    println("默认使用hashpartitioner ------------------------------------")
    val func = (partitionId:Int, iterator:Iterator[(Int, Long)]) => iterator.map(t => {
      s"[partition : ${partitionId}, value : ${t}]"
    })

    var arrays: Array[String] = listRDD.mapPartitionsWithIndex(func).collect()
    arrays.foreach(println)

    println("自定义分区器 ------------------------------------")
    //3. 注册自定义分区器
    val rdd2: RDD[(Int, Long)] = listRDD.partitionBy(new CustomerPartitioner(4))
    arrays = rdd2.mapPartitionsWithIndex(func).collect()
    arrays.foreach(println)

    SparkUtils.close(sc)
  }
}

布置作业:2个分区,1to10000,要求奇数分一个区,偶数分一个区

ftp://10.8.157.15/ZZBIGDATA-2201

七 综合练习

0 处理每次运行的时候日志问题

package com.qf.bigdata.spark.core.day5

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.log4j.{Level, Logger}
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkContext}
import org.slf4j.LoggerFactory

object Demo1_Partitioner {

  //1. 获取到一个叫做Demo1_Partitioner的日志处理器
  private val logger = LoggerFactory.getLogger(Demo1_Partitioner.getClass.getSimpleName)

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

    Logger.getLogger("org").setLevel(Level.WARN) // 设置org包下的所有的日志打印都是WARN级别

    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    //1. 加载数据,并处理数据为一个二维的元祖
    val listRDD: RDD[(Int, Long)] = sc.parallelize(1 to 10, 4)
      .zipWithIndex() // [(1,0),(2,1),(3,2),...,(10,9)]

    //2. 如果我们不设置自定义分区器,默认使用的是hash分区
    println("默认使用hashpartitioner ------------------------------------")
    val func = (partitionId:Int, iterator:Iterator[(Int, Long)]) => iterator.map(t => {
      s"[partition : ${partitionId}, value : ${t}]"
    })

    var arrays: Array[String] = listRDD.mapPartitionsWithIndex(func).collect()
    arrays.foreach(println)

    println("自定义分区器 ------------------------------------")
    //3. 注册自定义分区器
    val rdd2: RDD[(Int, Long)] = listRDD.partitionBy(new CustomerPartitioner(4))
    arrays = rdd2.mapPartitionsWithIndex(func).collect()
    arrays.foreach(println)

    SparkUtils.close(sc)
  }
}

1 案例1

有数据格式如下:

timestamp province city userid adid
时间点 省份 城市 用户 广告
用户ID范围:0-99
省份,城市,ID相同:0-9
adid:0-19

需求:

1.统计每一个省份点击TOP3的广告ID
2.统计每一个省份每一个小时的TOP3广告ID

package com.qf.bigdata.spark.core.day5

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.log4j.{Level, Logger}
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.slf4j.LoggerFactory

/**
 * 案例1
 */
object Demo2 {
  private val logger = LoggerFactory.getLogger(Demo2.getClass.getSimpleName)

  def main(args: Array[String]): Unit = {
    Logger.getLogger("org").setLevel(Level.WARN)
    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    //1. 读取文件
    val logRDD: RDD[String] = sc.textFile("C:\\ftp\\ZZBIGDATA-2201\\day008-spark core\\resource\\Advert.txt")

    //2. 切割日志:并统计省份和广告对应的点击次数之和
    val resRDD: RDD[(String, Int)] = logRDD.map(_.split("\\s+"))
      .map(arr => (arr(1) + "_" + arr(4), 1)) // 先统计这个省份的广告被点击了多少次,所以我要先以省份和广告作为key来统计次数
      .reduceByKey(_ + _) // 对省份和广告为key进行求点击次数之和

    //3. 以点击次数降序排序然后取top3
    val res: collection.Map[String, List[(String, Int)]] = resRDD.map {
      case (pa, cnt) => {
        val param: Array[String] = pa.split("_") // 省份和广告的字符串数组
        (param(0), (param(1), cnt)) // (province, adid, cnt)
      }
    }.groupByKey() // [(province, [(adid1, cnt1), (adid2,cnt2), ...])]
      .mapValues(values => values.toList.sortWith((previous, next) => previous._2 > next._2) // 按照点击次数降序排序
        .take(3)).collectAsMap()  // 取前3名

    //4. 打印
    println(res)

    SparkUtils.close(sc)
  }
}

2 案例2

  1. 数据格式
  • 19735E1C66.log 这个文件中存储着日志信息
手机号,时间戳,基站ID,连接状态(1连接 0断开)
  • lac_info.txt 这个文件中存储基站信息
基站ID, 经, 纬度 
  1. 需求

根据用户产生日志的信息,在那个基站停留时间最长
求所用户经过的所有基站的范围内(经纬度)所停留时间最长的Top2

翻译:求每个用户停留时长最长的两个基站的经纬度是多少

2.1 DateUtils

package com.qf.bigdata.spark.core.day5

import java.text.SimpleDateFormat
import java.util.Date

/**
 * 日期工具类
 */
object DateUtils {

  private val DATE_FORMAT:String = "yyyyMMddHHmmss"

  /**
   * 将date的字符串转换为Date类型
   */
  def dateStr2Date(dateStr: String, DATE_FORMAT: String): Date = {
    //1. 获取到日期格式化对象
    val format = new SimpleDateFormat(DATE_FORMAT)
    //2. 转换
    format.parse(dateStr)
  }

  /**
   * 将一个日期格式的字符串,转换为一个Long类型时间戳
   */
  def dateStr2Timestamep(dateStr: String): Long = {
      //1. 将date的字符串转换为Date类型
      val date:Date = dateStr2Date(dateStr, DATE_FORMAT)
      //2. 获取到date对应的时间戳
      date.getTime
  }

}

2.2 Demo3

package com.qf.bigdata.spark.core.day5

import com.qf.bigdata.spark.core.day3.SparkUtils
import org.apache.log4j.{Level, Logger}
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.slf4j.LoggerFactory

/**
 * 案例2
 */
object Demo3 {
  private val logger = LoggerFactory.getLogger(Demo3.getClass.getSimpleName)
  def main(args: Array[String]): Unit = {
    Logger.getLogger("org").setLevel(Level.WARN)

    val sc: SparkContext = SparkUtils.getLocalSparkContext()

    //1. 加载数据
    val logRDD: RDD[String] = sc.textFile("C:\\ftp\\ZZBIGDATA-2201\\day008-spark core\\resource\\lacduration\\19735E1C66.log")
    //2. 处理原始日志数据,将其转换为一个二维的元素((phone,lac), ts_long)
    val userInfoRDD: RDD[((String, String), Long)] = logRDD.map(line => {
      //2.1 切日志
      val fields: Array[String] = line.split(",")
      //2.2 获取到关键的字段
      val phone: String = fields(0) //手机号码
      val ts: Long = DateUtils.dateStr2Timestamep(fields(1)) // 时间戳
      val lac: String = fields(2) // 基站id
      val eventType: Int = fields(3).toInt // 连接或者断开的状态
      //3. 计算停留时间
      val ts_long = if (eventType == 1) -ts else ts
      //4. 封装数据为元祖
      ((phone, lac), ts_long)
    })

    //3. 计算每个用户在每个基站停留的总时长
    val lacAndPTRDD: RDD[(String, (String, Long))] = userInfoRDD.reduceByKey(_ + _)
      .map {
        case ((phone, lac), ts_long) => (lac, (phone, ts_long))
      }

    //4. 加载基站信息数据
    val lacInfoRDD: RDD[String] = sc.textFile("C:\\ftp\\ZZBIGDATA-2201\\day008-spark core\\resource\\lacduration\\lac_info.txt")
    val lacAndXYRDD: RDD[(String, (String, String))] = lacInfoRDD.map(line => {
      val fields: Array[String] = line.split(",")
      val lac: String = fields(0)
      val x: String = fields(1)
      val y: String = fields(2)
      (lac, (x, y))
    })

    //5. join : [(lac, [(phone, ts_long),(x, y)]),...]
    val joinRDD: RDD[(String, ((String, Long), (String, String)))] = lacAndPTRDD join lacAndXYRDD
    val phoneAndTXYRDD: RDD[(String, Long, String, String)] = joinRDD.map {
      case (lac, ((phone, ts), (x, y))) => (phone, ts, x, y)
    }
    //6. 转换 : 按照phone分组
    val groupRDD: RDD[(String, Iterable[(String, Long, String, String)])] = phoneAndTXYRDD.groupBy(_._1)

    //7. 降序排序
    val sortRDD: RDD[(String, List[(String, Long, String, String)])] = groupRDD.mapValues(_.toList.sortBy(_._2).reverse)

    //8. 处理一下,value中的phone数据和时长数据,只保留xy
    val resRDD: RDD[(String, List[(String, String)])] = sortRDD.map(t => {
      val phone = t._1
      val filterList: List[(String, String)] = t._2.map(tp => {
        val x: String = tp._3
        val y: String = tp._4
        (x, y)
      })
      (phone, filterList)
    })

    //9 top2
    val resultRDD: RDD[(String, List[(String, String)])] = resRDD.mapValues(_.take(2))
    println(resultRDD.collect().toList)

    SparkUtils.close(sc)

  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值