scala语言网上自行学习

spark生态圈

spark 计算资源 衡量_spark 计算资源 衡量


其实Hadoop生态圈差不多,只不过Hadoop计算是交给yarn进行。而sql语句是有hive来完成,storm来完成流式计算。而spark把这些东西都集成到一起,用起来很方便。

一、什么是Spark?特点

(1)spark就是为大规模的数据处理过程的一个统一的数据分析引擎。
(2)特点:
1、快:基于内存,同时提供容错机制
2、兼容HDFS、兼容Yarn
3、易用:支持多种编程语言:Scala、Java、Python、R
4、部署在不同平台上:Standalone、Yarn、容器Docker(k8s)
5、通用性:完备的生态圈系统

二、Spark的体系架构:主从架构

spark 计算资源 衡量_spark_02


spark也是主从架构,与其他主从架构一样,也是主节点管理从节点的信息。master节点主要负责接收客户端的请求,协调调度集群的资源,与我们前面所说的yarn差不多。worker则负责执行任务,当然,这些任务是交给executor去工作的。

客户端提交任务主要通过两种方式进行的:

1、spark-submit:提交的是我们写的jar包

2、spark-shell :直接连接master节点,当这个shell不关闭,资源就不释放。

三、搭建Spark的环境

(1)伪分布

1、解压

tar -zxvf spark-3.0.0-bin-hadoop3.2.tgz -C /usr/local/

由于spark的命令与Hadoop命令相似,因此spark与Hadoop的环境变量只能配置一个,我们已经配置过Hadoop环境变量,因此我们不在配置spark环境变量。我们所有的操作都在spark的home目录下执行。
2、核心的配置文件:conf/spark-env.sh
生成spark-env.sh

mv spark-env.sh.template spark-env.sh

进行配置 vi spark-env.sh

export JAVA_HOME=/usr/local/java/jdk1.8.0_251
export SPARK_MASTER_HOST=bigdata111
export SPARK_MASTER_PORT=7077

3、启动sbin/start-all.sh
查看web页面:Web Console:http://192.168.92.111:8080/

(2)全分布

部署在bigdata111(master)、bigdata222(work)、bigdata333(work)主机上
1、配置spark-env.sh

export JAVA_HOME=/usr/local/java/jdk1.8.0_251
export SPARK_MASTER_HOST=bigdata222
export SPARK_MASTER_PORT=7077

2、配置slaves
生成slaves

mv slaves.template  slaves

配置slaves : vi slaves

bigdata444
bigdata333

3、复制到bigdata333、bigdata444

scp -r spark/ root@bigdata333:/usr/local/
scp -r spark/ root@bigdata444:/usr/local/

注意:在部署全分布的时候,进行复制到其他节点的时候,从节点目录必须保持和主节点目录保持一致。
4、在master节点启动sbin/start-all.sh

(3)HA模式

1、基于目录的单点恢复:开发测试、本质依然是一个单点,不是真正的HA。就是把元信息持久化本地磁盘上面,当master宕机时,重启master就可以恢复。这种方式我们不常用。

配置参数

参考值

spark.deploy.recoveryMode

设置为FILESYSTEM开启单点恢复功能,默认值:NONE

spark.deploy.recoveryDirectory

Spark 保存恢复状态的目录

  • 配置spark-env.sh
export JAVA_HOME=/usr/local/java/jdk1.8.0_251
export SPARK_MASTER_HOST=bigdata222
export SPARK_MASTER_PORT=7077
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=FILESYSTEM -Dspark.deploy.recoveryDirectory=/usr/local/spark/recovery"
  • 配置slaves
bigdata444
bigdata333
  • 复制到bigdata333、bigdata444
scp -r spark/ root@bigdata333:/usr/local/
scp -r spark/ root@bigdata444:/usr/local/

2、基于ZooKeeper:生产。这时master节点就交给zookeeper进行管理,因此我们不用去指定哪台主机是master,当master宕机时,zookeeper会自动选举出新的master,在master切换时,只会影响新的job的提交,不会影响正在进行job,zookeeper会去指定,当客户端提交任务时,也是连接zookeeper地址。

配置参数

参考值

spark.deploy.recoveryMode

设置为ZOOKEEPER 开启单点恢复功能,默认值:NONE

spark.deploy.zookeeper.url

Zookeeper集群的地址

spark.deploy.zookeeper.dir

spark信息在ZK中的保存目录,默认:/spark

-配置spark-env.sh
注意:在这里不要指定master地址与端口,只需要配置zookeeper地址就行

export JAVA_HOME=/usr/local/java/jdk1.8.0_251
export SPARK_DAEMON_JAVA_OPTS="-Dspark.deploy.recoveryMode=ZOOKEEPER -Dspark.deploy.zookeeper.url=bigdata222:2181,bigdata333:2181,bigdata444:2181 -Dspark.deploy.zookeeper.dir=/spark"
  • 配置slaves
    配置在哪些节点启动work
bigdata444
bigdata333
  • 复制到其他节点
scp -r spark/ root@bigdata333:/usr/local/
scp -r spark/ root@bigdata444:/usr/local/
  • 启动
    在任意一个节点的spark的home目录下,启动sbin/start-all.sh命令,首先会在这个节点启动一个主节点master,然后按照slaves文件启动从节点。
    当然我们也可以在其他节点的spark的home目录下,启动sbin/start-master.sh命令,再启动一个主节点master,各个节点的元信息都是由zookeeper尽心维护。
    当第一个启动的master宕机,后补master节点补上。

四、执行Spark的任务:客户端工具

(1)spark-submit:提交Spark的jar包

bin/spark-submit --master spark://masterIp:7077 --class main所在类 jar包目录

(2)spark-shell:类似Scala REPL命令行

1、本地模式local:把程序运行在本地的环境(相当于在开发工具中直接运行)
bin/spark-shell

日志:
	Spark context available as 'sc' (master = local[*], app id = local-1603111126829).
	Spark session available as 'spark'.

2、集群模式cluster
bin/spark-shell --master spark://bigdata111:7077

日志:
	Spark context available as 'sc' (master = spark://bigdata111:7077, app id = app-20201019204212-0001).
	Spark session available as 'spark'.

demo:WordCount程序

bin/spark-shell --master spark://bigdata111:7077
sc.textFile("hdfs://bigdata111:9000/input/data.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).collect

五、开发自己的WordCount程序

(1)开发Java版本

1、把SPARK_HOME/jar包全部添加到项目中
2、创建MyWordCountDemo 类

public class MyWordCountDemo {
    public static void main(String[] args) {

        // Javaspark传输数据也是基于元组tuple进行传输数据。而tuple是提前定义好的一个数据类型。

        SparkConf conf = new SparkConf().setAppName("MyWordCountDemo").setMaster("local");
        JavaSparkContext javaspark = new JavaSparkContext(conf);


        JavaRDD<String> data = javaspark.textFile("word.txt");
        // 进行切分单词压平操作
        JavaRDD<String> words = data.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public Iterator<String> call(String s) throws Exception {
                return Arrays.asList(s.split(" ")).iterator();
            }
        });
        // 单词返回(word , 1)
        //                                                                         输入    输出     输出
        JavaPairRDD<String, Integer> wordpair = words.mapToPair(new PairFunction<String, String, Integer>() {
            @Override     // 输出    输出            输入
            public Tuple2<String, Integer> call(String word) throws Exception {
                return new Tuple2<String, Integer>(word , 1);
            }
        });

        // 进行按字段分组,进行计数
        JavaPairRDD<String, Integer> wordcount = wordpair.reduceByKey(new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer integer, Integer integer2) throws Exception {
                return integer + integer2;
            }
        });
        // 进行输出
        List<Tuple2<String, Integer>> collect = wordcount.collect();
        for (Tuple2<String, Integer> tuple2 : collect) {
            System.out.println(tuple2._1 + "      "+tuple2._2);
        }

        //停止SparkContext
        javaspark.stop();
    }
}

(2)开发scala版本

和Java相似,只不过代码量比Java少了很多。

(3)分析WordCount数据处理的过程

sc.textFile(“hdfs://bigdata111:9000/input/data.txt”).flatMap(.split(" ")).map((,1)).reduceByKey(+).collect

spark 计算资源 衡量_SPARK_03


过程和MapReduce相似。

六、RDD:弹性分布式数据集(当成是一个集合,由分区组成)

1、什么是RDD?特性、创建RDD

spark 计算资源 衡量_zookeeper_04


通俗的说,rdd可以就是一个分区,并且生成的分区分布在不同的worker上。

(1)弹性分布式数据集(当成是一个集合,由分区组成);每个分区运行在不同的Worker的从节点上

(2)RDD的特性:查看源码RDD.scala

  • A list of partitions
    一组分区
  • A function for computing each split(分片)
    函数(算子)执行计算
  • A list of dependencies on other RDDs
    RDD彼此具有依赖关系
  • Optionally, a Partitioner for key-value RDDs (e.g. to say that the RDD is hash-partitioned)
    自定义分区
  • Optionally, a list of preferred locations to compute each split on (e.g. block locations for an HDFS file)

2、算子:函数、方法

(1)Transformation:延时计算

转换

含义

map

返回一个新的rdd ,并且rdd的每个元素进过func函数转换后组成

filter

返回新的rdd,每个元素经过func计算后返回true的元素组成新的rdd

flatmap

就是flat与map的组合,将多个集合合并成一个

mappartitions

类似于map,但是,是作用于每个分区,获得的是整个分区的元素,而map是获得的是rdd每个元素

mappartitionswithindex

和mappartitions一样,只不过带有每个分区的索引值

sample(withReplacement,fraction,seed)

根据fraction指定的比例对数据进行采用,可以女选择是否使用随机数进行替换,seed是指定随机数的种子

union

两个rdd的并集,并返回新的rdd

intersection

两个rdd的交集,并返回新的rdd

distinct

去重复,并返回的新的rdd

groupbykey

在一个key-value的rdd上调用,返回一个(k,iterarot[v] )rdd

reducebykey

类似 groupbykey,只不过value值直接进行计算

sortbykey

进行排序

sortby

也是进行排序

jion

有两个rdd,返回这两个rdd的相同的key对应的所有元素,并对value尽心聚合

Cartesian

笛卡尔积

注意:在执行上面的算子的时候,并不会执行,只有执行下面的action算子的时候才会触发执行。

(2)Action:触发计算

动作

含义

reduce

聚集所有rdd中所有的元素 ,将rdd中所有元素进行累加

collect

以数组的形式返回数据集的所有元素

count

返回rdd中的元素个数

first

返回rdd中的第一个元素个数

take(n)

返回前n个元素组成的数组

takesample

随机采集几个元素进行返回

saveastextfile(path)

将数据集的元素以textfile的形式保存到hdfs文件系统或其他支持的文件系统,对于每个元素,spark将会调用tostring方法,将它转换为文件中的文本

saveassequencefile(path)

将数据集以Hadoop sequencefile的格式保存到指定的目录,可以使hdfs或其他Hadoop的支持文件系统

saveasobjectfile

以对象的形式进行保存

foreach

在数据集的每个元素上,运行函数进行更新

3、RDD的功能

(1)RDD依赖关系:根据依赖关系,可以划分任务的阶段Stage -----> 划分任务阶段的标准:宽依赖

  • 窄依赖:父RDD的每一分区,最多只被一个子RDD的分区使用
  • 宽依赖:父RDD的每一分区,被多个子RDD的分区使用

(2)RDD的缓存机制:提高效率

默认缓存在内存中
参数设置(源码):object StorageLevel

val NONE = new StorageLevel(false, false, false, false)
val DISK_ONLY = new StorageLevel(true, false, false, false)
val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2)
val MEMORY_ONLY = new StorageLevel(false, true, false, true)
val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)
val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)
val OFF_HEAP = new StorageLevel(true, true, true, false, 1)

查看RDD的源码

/**
 * Persist this RDD with the default storage level (`MEMORY_ONLY`).
 */
 def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)
 /**
 * Persist this RDD with the default storage level (`MEMORY_ONLY`).
 */
def cache(): this.type = persist()

Demo例子:使用RDD读取大的文本

val rdd1 = sc.textFile("hdfs://bigdata111:9000/sh/sales")
				
rdd1.count   ----> 第一次执行,没有缓存
rdd1.cache   ---->标识该RDD能够被缓存
rdd1.count   ----> 第二次执行,(1)执行计算  (2)将结果缓存
rdd1.count   ----> 第三次执行,不执行计算,直接从缓存中取出结果

== Java代码同上 ==

(3)RDD的容错机制:检查点checkpoint

复习:关于检查点

  • HDFS的检查点:触发日志合并
  • Oracle的检查点:会内存中的脏数据写到数据文件上
  • Spark和Flink中的检查点:都是容错机制、本质就是一个目录(本地、HDFS)
    设置Spark的检查点,检查点与linesage(血统)有关:就是任务执行的过程(生命周期)

spark 计算资源 衡量_spark 计算资源 衡量_05

  • 本地目录:用于测试,用于开发和测试,如果使用spark shell测试,需要运行在本地模式上
  • hdfs目录:用于生产
scala> sc.setCheckpointDir("hdfs://bigdata111:9000/spark/ckpt/1021")

scala> val myrdd = sc.textFile("hdfs://bigdata111:9000/input/data.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
myrdd: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[23] at reduceByKey at <console>:24

#表明myrdd是可以进行检查点容错的
scala> myrdd.checkpoint

scala> myrdd.collect

== Java代码同上 ==

4、RDD的高级算子

(1)都是针对每一个分区进行操作

  • mapPartitions(func) map是有返回值
  • foreachPartition foreach没有返回值
  • mapPartitionsWithIndex(func) 重点
API的文档
mapPartitionsWithIndex[U: ClassTag](f: (Int, Iterator[T]) => Iterator[U],
			                        preservesPartitioning: Boolean,
									isOrderSensitive: Boolean)
												
f的参数类型:匿名函数
(Int, Iterator[T]) => Iterator[U]	
Int:表示分区号,从0开始
Iterator[T]:分区中的元素
Iterator[U]:操作完成后的结果

例子:操作分区中的元素,返回:分区号+元素

创建一个RDD,有两个分区
val rdd1 = sc.parallelize(Array(1,2,3,4,5,6),2)
				
创建一个函数,作为f的参数值
def func1(index:Int, iter:Iterator[Int]):Iterator[String] ={
	iter.toList.map( x => "[PartID:" + index + ", Value:" + x + "]" ).iterator
}
				
调用
rdd1.mapPartitionsWithIndex(func1).collect
		
结果
分区0:[PartID:0, Value:1], [PartID:0, Value:2], [PartID:0, Value:3], 
分区1:[PartID:1, Value:4], [PartID:1, Value:5], [PartID:1, Value:6]
			
[PartID:0, Value:1], [PartID:0, Value:2], [PartID:0, Value:3], 
[PartID:1, Value:4], [PartID:1, Value:5], [PartID:1, Value:6], [PartID:1, Value:7]

(2)aggregate、aggregateByKey 聚合

含义:先局部(分区)进行聚合,再全局(RDD)进行聚合
Demo:

  • aggregate的处理过程如下:

    Java代码实现:
public static void main(String[] args) {
        Logger.getLogger("org.apache.spark").setLevel(Level.ERROR);
        Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF);

        SparkConf conf = new SparkConf().setAppName("MyWordCountDemo").setMaster("local");
        JavaSparkContext sc = new JavaSparkContext(conf);

        Integer[] a = new Integer[]{8,2,9,1,10};
        Integer[] b = new Integer[]{5,6,4,2,3};
        List<Integer> list1 = Arrays.asList(a);
        List<Integer> list2 = Arrays.asList(b);
        JavaRDD<Integer> rdd1 = sc.parallelize(list1);
        JavaRDD<Integer> rdd2 = sc.parallelize(list2);

        JavaRDD<Integer> rdd3 = rdd1.union(rdd2);


        Integer aggregate = rdd3.aggregate(0, new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer integer, Integer integer2) throws Exception {
                return integer >= integer2 ? integer:integer2;
            }
        }, new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer integer, Integer integer2) throws Exception {
                return integer + integer2;
            }
        });

        System.out.println(aggregate);

		sc.stop();

    }
  • aggregateByKey 的处理过程如下:

    必须讲JavaADD转化为JavaPairADD

(3)coalesce与repartition

区别:
coalesce ----> 执行shuffle(默认:false) 不会真正的进行分区
repartition—> 执行shuffle(默认:true)

val rdd1 = sc.parallelize(Array(1,2,3,4,5,6),2)
val rdd2 = rdd1.coalesce(3)   -----> 还是2个分区
val rdd3 = rdd1.repartition(3) -----> 是3个分区
val rdd4 = rdd1.coalesce(3,true) -----> 和repartition一样

七、编程案例:Spark Core离线计算

1、求网站的PV(Page View):通过分析访问日志

2、创建自定义分区

主函数

public class WordCount {
    public static void main(String[] args) {
        Logger.getLogger("org.apache.spark").setLevel(Level.ERROR);
        Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF);

        SparkConf conf = new SparkConf().setAppName("MyWordCountDemo").setMaster("local");
        JavaSparkContext sc = new JavaSparkContext(conf);
        // "192.168.88.1 - - [30/Jul/2017:12:54:40 +0800] \"GET /cle.jsp MyDemoWeb/oracle.jsp HTTP/cle.jsp1.1\" 200 242" ;

        JavaRDD<Tuple2<String, String>> jspList  = sc.textFile("src/resources/localhost_access_log.2017-07-30.txt").map(line -> {


            String stName;
            Pattern pattern = Pattern.compile("\\w*.jsp");
            Matcher matcher = pattern.matcher(line);
            if (matcher.find()) {
                stName = matcher.group();
            } else {
                stName = null;
            }
            return new Tuple2<String, String>(stName, line);
        });

        //得到所有的JSP的名字(去重)
        List<String> jspname = jspList.map(x -> x._1).distinct().collect();

        //创建分区规则
        MyPartitioner myPartitioner = new MyPartitioner(jspname);

        //创建分区------报错,Java好像没有partitionBy这个函数
        JavaRDD<Tuple2<String, String>> partitionList = jspList.partitionBy(myPartitioner);

        //输出
        partitionList.saveAsTextFile("d:\\download\\partitionout");
        sc.stop();




    }
}

创建MyPartitioner类

import org.apache.spark.Partitioner;

import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class MyPartitioner extends Partitioner {

    Map<String , Integer> partitionMap = new HashMap<String, Integer>();

    public MyPartitioner(List<String> jspname) {
        int partitionID =0;
        for (String s : jspname) {
            partitionMap.put(s,partitionID++);
        }
    }

    @Override
    public int getPartition(Object key) {
        return partitionMap.getOrDefault(key,0);
    }

    //分区数量
    @Override
    public int numPartitions() {
        return partitionMap.size();
    }
}

3、访问数据库Oracle:一定要针对分区进行操作

基于JDBC,略