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中可以封装数据处理逻辑。
- 是一个可实例化对象的类
- 封装了大量方法和属性
- 需要适合做分布式计算(减小数据规模,并行计算)
简单点说,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();
}
}
参考来源: