Spark大数据技术详解

Spark是一个分布式计算引擎(框架)。

分布式:多进程,多节点。

分布式计算的核心是:切分数据,减小数据规模

3种部署模式

local模式

将Spark部署在本地PC端,常用于开发、测试环境。

on yarn模式

Spark部署在YARN环境,此部署方式可以与Hadoop框架兼容,因为YARN就是Hadoop框架中三大模块(HDFS、YARN、MapReduce)的资源调度模块。

客户端向RM提交任务申请。RM在所有NM中分配第一个Container,运行ApplicationMaster进程,这个AM进程中运行了Spark的Driver,然后负责解析作业的还是Driver。然后Driver向AM向RM申请资源,RM向AM向Driver分配Container。然后Driver把task分发到这些Executor执行。

standalone模式

Spark运行在Spark独立集群里,因为Spark本身也有资源调度模块。

Spark主节点叫Master,类比于YARN中的RM。

Spark从节点叫Worker,类比于YARN中的NM。

客户端向Master提交任务,然后Master在所有Worker中给你分配资源, 第一个管理资源是作业自己的管理进程,叫Driver。Driver解析任务,发现需要若干个task的资源,然后Driver再向Master申请资源,然后Master再在所有Worker中给其分配资源,封装成Executor,Executor内涵多个task。运行过程中,task向Driver进行汇报。

RDD弹性分布式数据集

一个弹性分布式数据集,是spark中的一种数据抽象。

RDD中可以封装数据处理逻辑。

  1. 是一个可实例化对象的类
  2. 封装了大量方法和属性
  3. 需要适合做分布式计算(减小数据规模,并行计算)

简单点说,RDD是一个类,就像String是Java中的一个类一样,这个类中封装了大量的属性和方法,以便对数据进行操作。

我们对数据的操作,也就是一系列的业务逻辑,都可以通过大量的RDD对象组合在一起来实现。

 从集合中创建RDD

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.Arrays;
import java.util.List;
public class Test01_List {
     public static void main(String[] args) {
         // 1.创建配置对象
         SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
         // 2. 创建 sparkContext
         JavaSparkContext sc = new JavaSparkContext(conf);
         // 3. 编写代码
         JavaRDD<String> stringRDD = sc.parallelize(Arrays.asList("hello", "spark"));
         List<String> result = stringRDD.collect();
         for (String s : result) {
         System.out.println(s);
         }
         // 4. 关闭 sc
         sc.stop();
 }
}

从文件中创建RDD 

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.List;
public class Test02_File {
     public static void main(String[] args) {
         // 1.创建配置对象
         SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
         // 2. 创建 sparkContext
         JavaSparkContext sc = new JavaSparkContext(conf);
         // 3. 编写代码
         JavaRDD<String> lineRDD = sc.textFile("input");
         List<String> result = lineRDD.collect();
         for (String s : result) {
         System.out.println(s);
         }
         // 4. 关闭 sc
         sc.stop();
         }
}
    

分区

spark读取数据之后,由于要做分布式计算,所以必然要对数据进行分区。

3种分区策略:

1. 优先使用方法参数
final JavaRDD<String> rdd = jsc.parallelize(names, 2);
2. 使用配置参数:spark.default.parallelism
conf.set("spark.default.parallelism", "4");
3. 采用环境默认总核值

spark读取文件的操作是通过Hadoop的库来实现的,因此分区的数量取决于Hadoop对文件切片的规则:

Hadoop切片规则:
totalsize : 7 byte
goalsize  : totalsize / min-part-num => 7 / 3 = 2 byte
part-num  : totalsize / goalsize => 7 / 2 => 3...1 => 10% => 3 + 1

但是spark在读取文件时,会以行为单位。

也就是说,如果数据只有1行,但是设定了5个分区,那么最终这1行数据会独占1个分区,剩余4个分区是空的,这就是spark以行为单位保证数据的完整性。

因此,我们在保存数据时不能把所有数据保存在同一行,否则对于分布式计算没有意义。正确的做法是,以独立的业务为单位,其数据存在同一行。

RDD的2大类方法(算子)

RDD采用的处理顺序:前一个数据点走完所有的RDD, 才进行下一份数据的处理.

RDD方法处理的数据的2大类

单值类

把每个element视为一个整体。

键值类

所谓Key-Value,就是把分区内的每一个element视为Key和Value的组合。而之前的单值类型,则视为一个整体。

要想使用 Key-Value 类型的算子首先需要使用特定的方法转换为 PairRDD 。

public class Test01_pairRDD{
     public static void main(String[] args) {
         // 1.创建配置对象
         SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
         // 2. 创建 sparkContext
         JavaSparkContext sc = new JavaSparkContext(conf);
         
         // 3. 编写代码
         JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);

         // 4. 把JavaRDD转换为JavaPairRDD
         JavaPairRDD<Integer, Integer> pairRDD = integerJavaRDD.mapToPair(new 
             PairFunction<Integer, Integer, Integer>() {
                 @Override
                 public Tuple2<Integer, Integer> call(Integer integer) throws Exception {
                     return new Tuple2<>(integer, integer);
                     }
                 });
         pairRDD. collect().forEach(System.out::println);
         // 4. 关闭 sc
         sc.stop();
     }
}

转换算子

转换算子就是将旧的RDD通过我们的方法,转换成新的RDD。

之所以这么做,就是要将多个RDD的功能组合在一起

Map

对单值类数据进行处理。

就是对一个rdd中的元素进行逐个处理,在rdd.map()中实现一个函数式接口,接口内编写业务逻辑。

public class Test01_Map {
     public static void main(String[] args) {
     // 1.创建配置对象
     SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
     // 2. 创建 sparkContext
     JavaSparkContext sc = new JavaSparkContext(conf);
     // 3. 编写代码
     JavaRDD<String> lineRDD = sc.textFile("input/1.txt");
    
     // 需求:每行结尾拼接||
     // 两种写法 lambda 表达式写法(匿名函数) 
     JavaRDD<String> mapRDD = lineRDD.map(s -> s + "||");
    
         
     // 匿名函数写法
     JavaRDD<String> mapRDD1 = lineRDD.map(new Function<String, String>() {
     @Override
     public String call(String v1) throws Exception {
     return v1 + "||";
     }
     });
         
     for (String s : mapRDD.collect()) {
     System.out.println(s);
     }
     // 输出数据的函数写法
     mapRDD1.collect().forEach(a -> System.out.println(a));
     mapRDD1.collect().forEach(System.out::println);
     // 4. 关闭 sc
     sc.stop();
     }
}

Filter

1)功能说明 接收一个返回值为布尔类型的函数作为参数。当某个 RDD 调用 filter 方法时,会对该 RDD 中每一个元素应用 f 函数,如果返回值类型为 true,则该元素会被添加到新的 RDD 中。

2)需求说明:创建一个 RDD,过滤出对 2 取余等于 0 的数据

// 实现Function这个函数式接口
JavaRDD<Integer> filterRDD = integerJavaRDD.filter(new Function<Integer, Boolean>() {
    // 重写call方法
    // call方法返回True就保留此数据,否则直接丢弃。方能实现过滤效果
     @Override
     public Boolean call(Integer v1) throws Exception {
         return v1 % 2 == 0;
         }
     });
 filterRDD. collect().forEach(System.out::println);
FlatMap

扁平化:把整体数据拆分成个体来使用

例子:[[1,2],[3,4]]这样的一个ArrayList,传入数据管道时首先传入[1,2],此时使用flatmap就可以把[1,2]拆成1和2,实现整体数据拆分成个体的扁平化效果。同时可以叠加map操作。

代码中需要实现FlatMapFunction这个函数式接口,重写FlatMap的业务逻辑。其返回值是一个iterator,通过迭代器的形式实现扁平化处理。

如图可知,RDD1分区0中的2个元素,经过FlatMap之后就变成了4个元素。

而在Map方法中,处理前后的元素个数是不变的。

JavaRDD<List<String>> listJavaRDD = sc.parallelize(arrayLists,2);
 // 对于集合嵌套的 RDD 可以将元素打散
 // 泛型为打散之后的元素类型
JavaRDD<String> stringJavaRDD = listJavaRDD.flatMap(new 
FlatMapFunction<List<String>, String>() {
     @Override
     public Iterator<String> call(List<String> strings) throws Exception {
         return strings.iterator();
         }
     });
stringJavaRDD. collect().forEach(System.out::println);

GroupBy

实现Function接口,返回值是组名称。因此实现对输入元素与输出去向组之间的映射关系。

groupby之后返回的是键值对类型,即JavaPairRDD

// 泛型为分组标记的类型
JavaPairRDD<Integer, Iterable<Integer>> groupByRDD = integerJavaRDD.groupBy(new 
Function<Integer, Integer>() {
     @Override
     public Integer call(Integer v1) throws Exception {
         return v1 % 2;
     }
 });

groupByRDD.collect().forEach(System.out::println);

分组的过程就是Shuffle

Shuffle之后的1个分区可以存放原先的多个组,例如多个组分配到奇偶数两个分区。

并且Spark要求,所有数据必须完成分组Shuffle之后,才能进行下一步操作。

RDD对象不能保存数据,因此Shuffle操作必然会落盘。

Shuffle有可能导致资源浪费,即Shuffle之后产生多少个分区的数量并不确定,因此只能按照事先的分区数量去创建Executor。即便Shuffle之后产生了空分区,也一样要执行流程。为了解决这个问题,Shuffle操作是有能力去改变Shuffle之后的分区数量的。

SortBy

重写Function接口,在其内部实现对每个element打上标记,然后Spark就会按照这个标记来排序。

public class Test6_SortBy {
     public static void main(String[] args) {
     // 1.创建配置对象
     SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
     // 2. 创建 sparkContext
     JavaSparkContext sc = new JavaSparkContext(conf);
     // 3. 编写代码
     JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(5, 8, 1, 11, 20), 2);
         
     // (1)泛型为以谁作为标准排序 (2) true 为正序 (3) 排序之后的分区个数
     JavaRDD<Integer> sortByRDD = integerJavaRDD.sortBy(new Function<Integer, Integer>() 
        {
         @Override
         public Integer call(Integer v1) throws Exception {
             return v1;
             }
         }, true, 2);
    
     sortByRDD. collect().forEach(System.out::println);
     // 4. 关闭 sc
     sc.stop();
     }
}
Distinct

对内部的元素去重,并将去重后的元素放到新的 RDD 中。

distinct 会存在 shuffle过程。

public class Test05_Distinct {
     public static void main(String[] args) {
     // 1.创建配置对象
         SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
         // 2. 创建 sparkContext
         JavaSparkContext sc = new JavaSparkContext(conf);
         // 3. 编写代码
         JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6), 2);
         
         // 底层使用分布式分组去重 所有速度比较慢,但是不会 OOM
         JavaRDD<Integer> distinct = integerJavaRDD.distinct();
         distinct. collect().forEach(System.out::println);
         // 4. 关闭 sc
         sc.stop();
     }
}

MapValue

功能说明:针对于(K,V)形式的类型只对 V 进行操作

// 需求说明:创建一个 pairRDD,并将 value 添加字符串"|||"

public class Test02_MapValues {
     public static void main(String[] args) {
         // 1.创建配置对象
         SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
         // 2. 创建 sparkContext
         JavaSparkContext sc = new JavaSparkContext(conf);
         // 3. 编写代码
         JavaPairRDD<String, String> javaPairRDD = sc.parallelizePairs(Arrays.asList(
             new Tuple2<>("k", "v"), 
             new Tuple2<>("k1", "v1"), 
             new Tuple2<>("k2", "v2")
         ));
         
         // 只修改 value 不修改 key
         JavaPairRDD<String, String> mapValuesRDD = javaPairRDD.mapValues(new 
            Function<String, String>() {
             @Override
             public String call(String v1) throws Exception {
                 return v1 + "|||";
             }
         });
         
         mapValuesRDD. collect().forEach(System.out::println);
         // 4. 关闭 sc
         sc.stop();
     }
}
GroupByKey

按照 K 重新分组

groupByKey 对每个 key 进行操作,但只生成一个 seq,并不进行聚合。 该操作可以指定分区器或者分区数(默认使用 HashPartitioner)

public class Test03_GroupByKey {
     public static void main(String[] args) {
         // 1.创建配置对象
         SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
         // 2. 创建 sparkContext
         JavaSparkContext sc = new JavaSparkContext(conf);
         // 3. 编写代码
         JavaRDD<String> integerJavaRDD = 
        sc.parallelize(Arrays.asList("hi","hi","hello","spark" ),2);
         // 统计单词出现次数
         JavaPairRDD<String, Integer> pairRDD = integerJavaRDD.mapToPair(new 
        PairFunction<String, String, Integer>() {
             @Override
             public Tuple2<String, Integer> call(String s) throws Exception {
                 return new Tuple2<>(s, 1);
             }
         });
         // 聚合相同的 key,之后value为一个可迭代的迭代器
         JavaPairRDD<String, Iterable<Integer>> groupByKeyRDD = pairRDD.groupByKey();
         
         // 合并值,对迭代器进行迭代求和
         JavaPairRDD<String, Integer> result = groupByKeyRDD.mapValues(new 
            Function<Iterable<Integer>, Integer>() {
                 @Override
                 public Integer call(Iterable<Integer> v1) throws Exception {
                     Integer sum = 0;
                     for (Integer integer : v1) {
                         sum += integer;
                 }
             return sum;
             }
         });
         
         result. collect().forEach(System.out::println);
         // 4. 关闭 sc
         sc.stop();
     }
}}
SortByKey

按照key进行排序。

对于自定义对象,需要实现comparable接口,重写比较逻辑。

ReduceByKey

该操作可以将 RDD[K,V]中的元素按照相同的 K 对 V 进行聚合。其存在多 种重载形式,还可以设置新 RDD 的分区数。

public class Test04_ReduceByKey {
     public static void main(String[] args) {
     // 1.创建配置对象
         SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
         // 2. 创建 sparkContext
         JavaSparkContext sc = new JavaSparkContext(conf);
         // 3. 编写代码
         JavaRDD<String> integerJavaRDD = 
        sc.parallelize(Arrays.asList("hi","hi","hello","spark" ),2);
         // 统计单词出现次数
         JavaPairRDD<String, Integer> pairRDD = integerJavaRDD.mapToPair(new 
            PairFunction<String, String, Integer>() {
                 @Override
                 public Tuple2<String, Integer> call(String s) throws Exception {
                     return new Tuple2<>(s, 1);
                 }
             });
         
         // 聚合相同的 key
         JavaPairRDD<String, Integer> result = pairRDD.reduceByKey(new Function2<Integer, 
            Integer, Integer>() {
                 // 重写对value部分的聚合逻辑:sum(), max(), min()之类的
                 // 计算逻辑是两两比较,迭代进行
                 @Override
                 public Integer call(Integer v1, Integer v2) throws Exception {
                     return v1 + v2;
                 }
                 });
         result. collect().forEach(System.out::println);
         // 4. 关闭 sc
         sc.stop();
     }
}

reduceByKey 和 groupByKey 区别

1)reduceByKey:按照 key 进行聚合,在 shuffle 之前有 combine(预聚合)操作,返回结果是 RDD[K,V]。

2)groupByKey:按照 key 进行分组,直接进行 shuffle。

行动算子

作用:触发转换算子作业的进行。换言之,如果没有行动算子,那么转换算子的逻辑并不会执行。

如何区分行动算子与转换算子:看这个算子的返回值是否是RDD,如果是就是转换算子,否则是行动算子。

collect

作用:将executor端执行的结果按照分区的顺序,拉取回Driver端,并将结果组合成集合对象

弊端:容易将多个executor的结果拉取到driver端,导致内存溢出,因此生产环境慎用

从内存中读取数据:

从HDFS读取数据:分区信息只保存HDFS文件的偏移量,真正的拉取等到executor执行

count

作用:把执行结果的数量获取到

long count = integerJavaRDD.count();
first

作用:从执行结果中获取到第一个

Integer first = integerJavaRDD.first();
take

作用:从执行结果中获取到n个

List<Integer> list = integerJavaRDD.take(3);

CountByKey

作用: 统计每种 key 的个数

public class Test05_CountByKey {
     public static void main(String[] args) {
     // 1.创建配置对象
         SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
         // 2. 创建 sparkContext
         JavaSparkContext sc = new JavaSparkContext(conf);
         // 3. 编写代码
         JavaPairRDD<String, Integer> pairRDD = sc.parallelizePairs(Arrays.asList(
             new Tuple2<>("a", 8), 
             new Tuple2<>("b", 8), 
             new Tuple2<>("a", 8), 
             new Tuple2<>("d", 8))
                                                                   );
         
         Map<String, Long> map = pairRDD.countByKey();
         
         System.out.println(map);
         // 4. 关闭 sc
         sc.stop();
     }
}

save 相关

1)saveAsTextFile(path)保存成 Text 文件 : 将数据集的元素以 textfile 的形式保存到 HDFS 文件系统或者其他支持的文 件系统,对于每个元素,Spark 将会调用 toString 方法,将它装换为文件中的文本

2)saveAsObjectFile(path) 序列化成对象保存到文件 : 用于将 RDD 中的元素序列化成对象,存储到文件中。

public class Test06_Save {
     public static void main(String[] args) {
         // 1.创建配置对象
         SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("sparkCore");
         // 2. 创建 sparkContext
         JavaSparkContext sc = new JavaSparkContext(conf);
         // 3. 编写代码
         
         JavaRDD<Integer> integerJavaRDD = sc.parallelize(Arrays.asList(1, 2, 3, 4),2);
         
         integerJavaRDD.saveAsTextFile("output");
         integerJavaRDD.saveAsObjectFile("output1");
         // 4. 关闭 sc
         sc.stop();
     }
}

参考来源:

尚硅谷2024新版本Spark教程,一套搞定大数据Spark3.x!_哔哩哔哩_bilibili

大数据技术入门精讲(Hadoop+Spark)_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值