Spark
Spark
和Hadoop
Hadoop | Spark | |
---|---|---|
起源时间 | 2005 | 2009 |
起源地 | MapReduce | University of California Berkeley |
数据处理引擎 | Batch | Batch |
编程模型 | MapReduce | Resilient distributed Datesets |
内存管理 | Disk Based | JVM Managed |
延迟 | 高 | 中 |
吞吐量 | 中 | 高 |
优化机制 | 手动 | 手动 |
API | Low level | high level |
流处理 | NA | Spark Streaming |
SQL支持 | Hive, Impala | SparkSQL |
Graph支持 | NA | GraphX |
机器学习支持 | NA | SparkML |
Spark
对比Hadoop
特点Spark
优缺点Spark
将运算的中间数据存放在内存, 迭代计算效率更高; 而MapReduce
的中间结果需要保存到磁盘Spark
容错性更高, 通过弹性分布式数据集RDD来实现高容错; 一部分数据丢失或戳错可以通过数据集的计算过程的血缘关系来实现重建; MapReduce
发生错误只能重新计算Spark
相比于Hadoop
提供了transformation
和action
这两大类的多功能api, 以及流式处理Spark Streaming
模块, 图计算GraphX
等等; MapReduce
只提供了map
和reduce
两种操作Spark
框架和生态更加复杂, 首先有RDD
, 血缘lineage
, 执行时的有向无环图DAG
/stage
划分等, 很多时候都需要根据不同场景分别调优以达到性能要求; 而MapReduce
框架及应用较为简单, 但运行较为稳定, 更适合长期稳定运行Hadoop
优缺点优点:
hadoop
可以按位存储和处理数据Hadoop
能够在节点之间动态的移动数据, 并保证各个节点的动态平衡Hadoop
能够保存数据的多个副本, 并且能够自动将失败的任务重新分配缺点:
Application
: 用户编写的Spark
应用程序, 包含了driver
程序以及集群上运行的程序代码, 物理机器上涉及了driver
, master
, worker
三个节点RDD
(Resilient Distributed Dataset): 弹性分布式数据集是Spark
中最基本的数据抽象, 代表了一个不可变, 可分区, 可并行计算的集合. RDD
具有数据流模型的特点: 自动容错/位置感知性调度/和可伸缩性. RDD
允许用户在执行多个查询时显示地将工作集缓存在内存中, 后续的查询能够重用工作机, 这极大地提升了查询速度. RDD
包含:
CPUCore
个数Spark
中RDD的计算是以分片为单位的, 每个RDD都会实现compute函数以达到这个目的. compute函数会对迭代器进行复合, 不需要保存每次计算结果Partitioner
即RDD的分片函数: 当前Spark
中实现了两种类型的分片函数, 一个是基于哈希的HashPartitioner
, 另一个是基于范围的RangePartitioner
. 只有对于key-value
的RDD, 才会有Partitioner
Partition
所在的块的位置, 按住奥"移数据不如移动计算"的理念, Spark在记性任务调度的时候, 会尽可能地讲计算任务分配到其所要处理的块的位置executor
上的工作单元, 每个Task
负责一个分区的数据ShuffleMapTask
: 输出是shuffle所需的数据, stage的划分也以此为依据, shuffle之前的所有变换是一个stage, shuffle之后的操作是另个一个stagetask
的并行计算, 可以理解为SparkRDD
里面的action
, 每个action的出发会生成一个job. 用户提交的job会提交给DAGSCheduler
; job会被分解为Stage
, Stage
会被细化乘Task
, Task
就是每个Partition
上的单个数据处理流程Stage
: 是job
的基本调度单位, 一个Job
会分为多组Task
, 每组Task
被称为一个Stage
就行MapStage
, ReduceStage
,或者也被称为TaskSet
, 代表一组关联的, 相互之间没有Shuffle
依赖关系的组成的任务集Worker
: 集群中任何一个可以运行spark应用代码的节点. Worker
是物理节点, 可以在上面启动Executor
进程 分配节点资源Driver
: Spark
中的Driveer
是运行Application
的main
函数, 并且创建了SparkContext
; 创建SparkContext
的目的是为了准备Spark
应用程序的运行环境. 在Spark
中SparkContext
负责与Cluster Manager
通信, 进行资源申请/任务分配和监控等. 当Excutor
部分运行完毕后, Driver
同时负责将SparkContext
关闭 单个任务的管理Executor
: 在每个Worker
上为某应用启动的一个进程, 该进程负责运行Task
, 并且负责将数据存在内存或磁盘上, 每个任务都有各独立的Executor
. Executor
是一个执行Task
的容器 单个任务的执行Standalone模式Spark自带的一种集群模式, 集群由Master和Spark组成. 除了Master和Worker以外, 还可能由HistoryServer, 该进程会在Spark Application运行完成之后, 保存事件日志到HDFS, 启动HistoryServer可以查看应用相关的信息
Spark
1wget https://dlcdn.apache.org/spark/spark-3.4.1/spark-3.4.1-bin-hadoop3.tgz
tar -xvf spark-3.4.1-bin-hadoop3.tgz
sudo mv spark-3.4.1-bin-hadoop3 /usr/local/spark
vim ~/.bashrc
export SPARK_HOME="/usr/local/spark"
/usr/local/spark/bin/spark-shell
Spark shell - Spark Jobs (passnight.local)包含Spark访问界面
Spark
实现WordCount
package com.passnight.bigdata.spark;
import lombok.Cleanup;
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;
public class WordCount {
public static void main(String[] args) {
SparkConf conf = new SparkConf()
.setAppName("WordCount")
.setMaster("local");
@Cleanup JavaSparkContext context = new JavaSparkContext(conf);
JavaRDD<String> data = context.textFile("hdfs://server.passnight.local/test/word list.txt", 10);
JavaPairRDD<String, Integer> result = data.flatMap(line -> Arrays.stream(line.split(" ")).iterator())
.mapToPair(word -> new Tuple2<>(word, 1)) // 映射成词频
.reduceByKey(Integer::sum) // 聚合词频
// 排序
.mapToPair(Tuple2::swap)
.sortByKey(false)
.mapToPair(Tuple2::swap);
System.out.println("-".repeat(100));
System.out.println(result.collect());
System.out.println("-".repeat(100));
}
}
输出如下(省略了日志)
[(I,4), (like,2), (passnight,2), (love,2), (hadoop,2)]
RDD可以通过读取文件或集合创建rdd
package com.passnight.bigdata.spark;
import lombok.Cleanup;
import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import java.util.Arrays;
public class RDDCreation {
public static void main(String[] args) {
SparkConf conf = new SparkConf()
.setAppName("WordCount")
.setMaster("local[*]");
@Cleanup JavaSparkContext context = new JavaSparkContext(conf);
// 通过并行化的方式创建RDD, 默认分区数为核心数
JavaRDD<Integer> rdd = context.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9), 3);
System.out.println("-".repeat(100));
System.out.println(rdd.collect());
System.out.println("-".repeat(100));
// 也可以通过本地文件创建; 这里的最小分区数是参考值, 而非强制值
JavaRDD<String> rdd1 = context.textFile("bigdata/src/main/resources/word list.txt", 100);
System.out.println("-".repeat(100));
System.out.println(rdd1.getNumPartitions());
System.out.println("-".repeat(100));
System.out.println(rdd1.collect());
System.out.println("-".repeat(100));
// 从hdfs读取文件
JavaRDD<String> rdd2 = context.textFile("hdfs://server.passnight.local/test/word list.txt");
System.out.println("-".repeat(100));
System.out.println(rdd2.getNumPartitions());
System.out.println("-".repeat(100));
System.out.println(rdd2.collect());
System.out.println("-".repeat(100));
}
// 读取多个小文件
JavaPairRDD<String, String> rdd3 = context.wholeTextFiles("bigdata/src/main/resources");
System.out.println("-".repeat(100));
System.out.println(rdd3.getNumPartitions());
System.out.println("-".repeat(100));
System.out.println(rdd3.collect());
System.out.println("-".repeat(100));
}
输出为:
# 这里省略了日志和分隔符
[1, 2, 3, 4, 5, 6, 7, 8, 9]
61
[I love passnight, I like passnight, I love hadoop, I like hadoop]
2
[I love passnight, I like passnight, I love hadoop, I like hadoop]
[(file:/************/bigdata/src/main/resources/word list.txt,I love passnight
I like passnight #......................
flatMap
是一类典型的Transformation算子collect
map
算子功能: map算子, 是将RDD中的数字逐条处理, 返回新的RDD
class Map {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Map")
.setMaster("local[*]"));
List<Integer> rdd = context.parallelize(IntStream.range(0, 10).boxed().collect(Collectors.toList()), 3)
.map(i -> i * 10)
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出为:
计算结果:
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
flatMap
算子功能: 先对rdd进行map操作, 再摊平嵌套
class FlatMap {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("FlatMap")
.setMaster("local[*]"));
List<String> rdd = context.parallelize(Arrays.asList("1 2 3", "4 5 6", "7 8 9"), 3)
.flatMap(line -> Arrays.stream(line.split(" ")).iterator())
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出为:
计算结果:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
可以看到多个数组被摊平为一个数组
reduceByKey
算子功能: 针对KV型RDD, 先对key进行分组, 然后根据提供的聚合逻辑, 完成组内数据的聚合操作
class ReduceByKey {
public static void main(String[] args) {
@Cleanup
JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("ReduceByKey")
.setMaster("local[*]"));
List<Tuple2<String, Integer>> rdd = context.parallelizePairs(Stream.of(1, 1, 1, 2, 2, 2, 3, 4, 4, 3, 10)
.map(i -> new Tuple2<>(String.format("值: %d", i), i))
.collect(Collectors.toList()), 3)
.reduceByKey(Integer::sum)
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
[(值: 4,8), (值: 1,3), (值: 2,6), (值: 10,10), (值: 3,6)]
可以看到不同值被分组, 然后进行求和
mapToValues
算子功能: 针对二元元组RDD, 对其内部的Value进行map操作
class MapToValues {
public static void main(String[] args) {
@Cleanup
JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("MapToValues")
.setMaster("local[*]"));
List<Tuple2<String, Integer>> rdd = context.parallelizePairs(Stream.of(1, 1, 1, 2, 2, 2, 3, 4, 4, 3, 10)
.map(i -> new Tuple2<>(String.format("值: %d", i), i))
.collect(Collectors.toList()), 3)
.mapValues(i -> i * 10)
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
[(值: 1,10), (值: 1,10), (值: 1,10), (值: 2,20), (值: 2,20), (值: 2,20), (值: 3,30), (值: 4,40), (值: 4,40), (值: 3,30), (值: 10,100)]
可以看到只有值发生了变化, 且变为了原来的10倍
groupBy
算子功能: 将RDD的数据进行分组
class GroupBy {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("GroupBy")
.setMaster("local[*]"));
List<Tuple2<String, Iterable<Tuple2<String, Integer>>>> rdd = context.parallelizePairs(Arrays.asList(
Tuple2.apply("a", 1), Tuple2.apply("b", 2), Tuple2.apply("b", 1), Tuple2.apply("a", 3), Tuple2.apply("c", 1)
), 3)
.groupBy(Tuple2::_1)
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
[(c,[(c,1)]), (a,[(a,1), (a,3)]), (b,[(b,2), (b,1)])]
可以看到已经根据key分组了
ffilter
算子功能: 过滤符合条件的数据
class Filter {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Filter")
.setMaster("local[*]"));
List<Integer> rdd = context.parallelize(IntStream.range(0, 10).boxed().collect(Collectors.toList()), 3)
.filter(i -> i % 2 == 0)
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
[0, 2, 4, 6, 8]
可以看到已将偶数都过滤出来了
distinct
算子功能: 将rdd数据去重
class Distinct {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Distinct")
.setMaster("local[*]"));
List<Integer> rdd = context.parallelize(Arrays.asList(1, 1, 1, 2, 2, 2, 3, 3, 3), 3)
.distinct(2)
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
List<Tuple2<String, Integer>> rdd2 = context.parallelizePairs(Arrays.asList(Tuple2.apply("a", 1),
Tuple2.apply("b", 1), Tuple2.apply("b", 1),
Tuple2.apply("a", 3), Tuple2.apply("a", 1)), 3)
.distinct(2)
.collect();
System.out.printf("计算结果:%n %s%n", rdd2);
}
}
输出结果为:
计算结果:
[2, 1, 3]
计算结果:
[(a,1), (a,3), (b,1)]
可以看到无论是KV型数据还是普通的数据, 都已经去重了
union
算子功能: 将两个rdd合并成一个rdd
class Union {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Union")
.setMaster("local[*]"));
JavaRDD<Object> rdd1 = context.parallelize(IntStream.range(0, 4).boxed().collect(Collectors.toList()), 3);
JavaRDD<Object> rdd2 = context.parallelize(IntStream.range(2, 7).boxed().collect(Collectors.toList()), 3);
JavaRDD<Object> rdd3 = context.parallelize(IntStream.range(7, 10).boxed().map(String::valueOf).collect(Collectors.toList()), 3);
List<Object> rdd = rdd1.union(rdd2)
.union(rdd2)
.union(rdd3)
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
[0, 1, 2, 3, 2, 3, 4, 5, 6, 2, 3, 4, 5, 6, 7, 8, 9]
可以看到可以合并数据类型, 合并也不会进行去重操作
join
算子功能: 对两个RDD执行join操作, 可以实现SQL的内连接/外连接
class Join {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Join")
.setMaster("local[*]"));
JavaPairRDD<Integer, String> rdd1 = context.parallelizePairs(Arrays.asList(
Tuple2.apply(1, "张三"), Tuple2.apply(2, "李四"),
Tuple2.apply(3, "王五"), Tuple2.apply(4, "赵六")
), 3);
JavaPairRDD<Integer, String> rdd2 = context.parallelizePairs(Arrays.asList(
Tuple2.apply(1, "生产部"), Tuple2.apply(2, "销售部")
), 3);
// 默认按照两个rdd的key进行关联, 不像sql无需用on添加条件
List<Tuple2<Integer, Tuple2<String, String>>> join = rdd1.join(rdd2)
.collect();
List<Tuple2<Integer, Tuple2<String, Optional<String>>>> leftOuterJoin = rdd1.leftOuterJoin(rdd2)
.collect();
System.out.printf("计算结果(join):%n %s%n", join);
System.out.printf("计算结果(leftOuterJoin):%n %s%n", leftOuterJoin);
}
}
输出结果为:
计算结果(join):
[(1,(张三,生产部)), (2,(李四,销售部))]
计算结果(leftOuterJoin):
[(3,(王五,Optional.empty)), (4,(赵六,Optional.empty)), (1,(张三,Optional[生产部])), (2,(李四,Optional[销售部]))]
可以看到两个元组集合根据key关联在一起了, 左外连接保留了在右侧没有对应key的元组
intersection
算子功能: 求两个rdd的交集, 并返回一个rdd
class Intersection {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Intersection")
.setMaster("local[*]"));
JavaRDD<Integer> rdd1 = context.parallelize(IntStream.range(0, 8).boxed().collect(Collectors.toList()), 3);
JavaRDD<Integer> rdd2 = context.parallelize(IntStream.range(5, 7).boxed().collect(Collectors.toList()), 3);
List<Integer> rdd = rdd1.intersection(rdd2)
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为
计算结果:
[6, 5]
可以看到只有在两个集合中都存在的才被输出
glom
算子功能: 将RDD的数据按照分区加上嵌套
class Glom {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Glom")
.setMaster("local[*]"));
List<List<Integer>> rdd1 = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 3)
.glom()
.collect();
System.out.printf("计算结果:%n %s%n", rdd1);
List<List<Integer>> rdd2 = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 2)
.glom()
.collect();
System.out.printf("计算结果:%n %s%n", rdd2);
}
}
输出结果为:
计算结果:
[[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]]
计算结果:
[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]
可以看到分区数和numSlices
参数相对应
groupByKey
算子功能: 针对KV型RDD, 自动按照Key分组
class GroupByKey {
public static void main(String[] args) {
@Cleanup
JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("GroupByKey")
.setMaster("local[*]"));
List<Tuple2<String, Iterable<Integer>>> rdd = context.parallelizePairs(Stream.of(1, 1, 1, 2, 2, 2, 3, 4, 4, 3, 10)
.map(i -> new Tuple2<>(String.format("值: %d", i), i))
.collect(Collectors.toList()), 3)
.groupByKey()
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为
计算结果:
[(值: 4,[4, 4]), (值: 1,[1, 1, 1]), (值: 2,[2, 2, 2]), (值: 10,[10]), (值: 3,[3, 3])]
可以看到已经根据key进行分组了
sortBy
算子功能: 根据输入的函数, 对RDD进行排序
class SortBy {
public static void main(String[] args) {
@Cleanup
JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("SortBy")
.setMaster("local[*]"));
List<Tuple2<String, Integer>> rdd = context.parallelize(new Random().ints(1, 100)
.boxed()
.map(integer -> Tuple2.apply(String.format("值(%d)",integer), integer))
.limit(10)
.collect(Collectors.toList()), 3)
// 若要全局有序, Partition只能设置为1, 否则只能保证分区内局部有序
.sortBy(Tuple2::_2, true, 1)
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
[(值(13),13), (值(15),15), (值(21),21), (值(46),46), (值(52),52), (值(55),55), (值(55),55), (值(66),66), (值(87),87), (值(90),90)]
可以看到已经根据元组的第二个元素排序了
sortByKey
算子功能: 针对KV型RDD, 按照Key进行排序
class SortByKey {
public static void main(String[] args) {
@Cleanup
JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("SortByKey")
.setMaster("local[*]"));
List<Tuple2<String, Integer>> rdd = context.parallelizePairs(new Random().ints(1, 100)
.boxed()
.map(integer -> Tuple2.apply(String.format("值(%d)", integer), integer))
.limit(10)
.collect(Collectors.toList()), 3)
.sortByKey(true, 1)
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
[(值(11),11), (值(25),25), (值(45),45), (值(63),63), (值(64),64), (值(65),65), (值(71),71), (值(77),77), (值(79),79), (值(98),98)]
可以看到结果已经根据key排序了
countByKey
算子功能: 统计key出现的次数, 这个算子是
class CountByKey {
public static void main(String[] args) {
@Cleanup
JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("CountByKey")
.setMaster("local[*]"));
java.util.Map rdd = context.parallelizePairs(Stream.of(1, 1, 1, 2, 2, 2, 3, 4, 4, 3, 10)
.map(i -> new Tuple2<>(String.format("值: %d", i), i))
.collect(Collectors.toList()))
.countByKey();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
{值: 2=3, 值: 4=2, 值: 3=2, 值: 10=1, 值: 1=3}
可以看到已经根据Key进行计数了
collect
算子功能: 将RDD各个分区内的数据, 统一手机到一个Driver中, 形成一个List对象
class Collect {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Collect")
.setMaster("local[*]"));
List<Integer> rdd = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 3)
.collect(); // 注意使用这个算子, 要确认结果集不会太大, 否则可能会导致Driver OOM
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
reduce
算子:功能: 根据传入的逻辑进行聚合
class Reduce {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Reduce")
.setMaster("local[*]"));
Integer result = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 3)
.reduce(Integer::sum);
System.out.printf("计算结果:%n %s%n", result);
}
}
输出结果值为:
计算结果:
45
可以看到成功实现求和
flod
算子功能: 相当于有初始值的聚合, 每个分区内都会有一个初始值, 且分区间聚合也有该初始值
class Fold {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Collect")
.setMaster("local[*]"));
Integer result = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 3)
.reduce(Integer::sum);
System.out.printf("计算结果:%n %s%n", result);
}
}
输出结果为:
计算结果:
85
三个分区聚合引入三个初始值, 因此三个分区聚合后的结果为[16, 25, 34]
, 它们再聚合, 并添加10作为初始值, 最后的结果为 10 + 16 + 25 + 34 = 85 10 + 16 + 25 + 34 = 85 10+16+25+34=85
first
算子功能: 取出rd的第一个元素
class First {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("First")
.setMaster("local[*]"));
Integer result = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 3)
.first();
System.out.printf("计算结果:%n %s%n", result);
}
}
输出结果为:
计算结果:
0
可以看到去除第一个元素
top
算子功能: 对RDD结果集降序排序, 取前N个
class Top {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Top")
.setMaster("local[*]"));
List<Integer> top3 = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.collectingAndThen(Collectors.toList(), (list) -> {
Collections.shuffle(list);
return list;
})), 3)
.top(3);
System.out.printf("计算结果:%n %s%n", top3);
}
}
输出结果为:
计算结果:
[9, 8, 7]
count
算子功能: 返回RDD的数据数
class Count {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Count")
.setMaster("local[*]"));
long count = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 3)
.count();
System.out.printf("计算结果:%n %s%n", count);
}
}
输出结果为:
计算结果:
10
takeSample
算子功能: 随机抽样RDD的数据
class TakeSample {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("TakeSample")
.setMaster("local[*]"));
List<Integer> sample = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 3)
.takeSample(true, 3);
System.out.printf("计算结果:%n %s%n", sample);
}
}
输出结果为:
计算结果:
[5, 4, 9]
可以看到随机取了三个rd中的元素
takeOrderd
算子功能: 对RDD进行排序后取前N个 相比于top, 可以制定排序方法
class TakeOrdered {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("TakeOrdered")
.setMaster("local[*]"));
List<Integer> sample = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.collectingAndThen(Collectors.toList(), list -> {
Collections.shuffle(list);
return list;
})), 3)
.takeOrdered(3);
System.out.printf("计算结果:%n %s%n", sample);
}
}
输出结果为:
计算结果:
[0, 1, 2]
forEach
算子功能: 对rdd的每个元素执行所提供的操作, 但相比于map, 没有返回值 注意forEach
是直接由executor执行的, 其他的算子是由Driver输出的
class ForEach {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("ForEach")
.setMaster("local[*]"));
context.parallelize(IntStream.range(0, 10).boxed().collect(Collectors.toList()), 3)
.foreach(System.out::println);
}
}
输出结果为:
值: 0|值: 3|值: 6|值: 7|值: 4|值: 5|值: 1|值: 2|值: 8|值: 9|
saveAsTextFile
算子功能: 将数据结果写入到文件当中, 这个任务是由Executor执行的 支持本地文件系统, 也支持hdfs; 因为是由Executor执行的, 所以每个分区都会写一部分
class SaveAsTextFile implements Serializable {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("SaveAsTextFile")
.setMaster("local[*]"));
context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 3)
.saveAsTextFile("result");
}
}
可以看到结果成功写入到文件当中了 , 且结果的文件数量和分区数量相同
passnight@passnight-s600:~/project/note/spring/result$ ll
total 36
drwxr-xr-x 2 passnight passnight 4096 11月 4 14:34 ./
drwxrwxr-x 15 passnight passnight 4096 11月 4 14:34 ../
-rw-r--r-- 1 passnight passnight 6 11月 4 14:34 part-00000
-rw-r--r-- 1 passnight passnight 12 11月 4 14:34 .part-00000.crc
-rw-r--r-- 1 passnight passnight 6 11月 4 14:34 part-00001
-rw-r--r-- 1 passnight passnight 12 11月 4 14:34 .part-00001.crc
-rw-r--r-- 1 passnight passnight 8 11月 4 14:34 part-00002
-rw-r--r-- 1 passnight passnight 12 11月 4 14:34 .part-00002.crc
-rw-r--r-- 1 passnight passnight 0 11月 4 14:34 _SUCCESS
-rw-r--r-- 1 passnight passnight 8 11月 4 14:34 ._SUCCESS.crc
mapPartitions
算子功能: 同map一样, 但一次操作一整个分区的数据 这样可以极大减少网络io次数
class MapPartitions {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("MapPartitions")
.setMaster("local[*]"));
List<Integer> rdd = context.parallelize(IntStream.range(0, 10).boxed().collect(Collectors.toList()), 3)
.mapPartitions(integerIterator -> StreamSupport.stream(Spliterators.spliteratorUnknownSize(integerIterator, 0), false)
.map(integer -> integer * 10)
.iterator())
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
可以看到所有元素的值都变为了原来的10倍
foreachPartitions
算子功能: 同forEach一样, 但一次操作整个分区的数据
class ForeachPartitions {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("ForeachPartitions")
.setMaster("local[*]"));
context.parallelize(IntStream.range(0, 10).boxed().collect(Collectors.toList()), 3)
.mapPartitions(integerIterator -> Stream.generate(integerIterator::next)
.map(integer -> integer * 10)
.iterator())
.foreachPartition(it -> System.out.printf("值: %s|", it));
}
}
输出结果为:
值: java.util.Spliterators$1Adapter@8c66b7c|值: java.util.Spliterators$1Adapter@6e74b18e|值: java.util.Spliterators$1Adapter@6cb5e424|
partitionBy
算子功能: 对RDD进行自定义分区操作
class PartitionBy {
public static void main(String[] args) {
@Cleanup
JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("PartitionBy")
.setMaster("local[*]"));
List<List<Tuple2<Integer, String>>> rdd = context.parallelizePairs(Stream.of(1, 1, 1, 2, 2, 2, 3, 4, 4, 3, 10)
.map(i -> new Tuple2<>(i, String.format("值: %d", i)))
.collect(Collectors.toList()))
.partitionBy(new Partitioner() {
@Override
public int numPartitions() {
return 2;
}
@Override
public int getPartition(Object key) {
assert key instanceof Integer;
Integer k = (Integer) key;
return k > 3 ? 1 : 0;
}
}).glom()
.collect();
System.out.printf("计算结果:%n %s%n", rdd);
}
}
输出结果为:
计算结果:
[[(1,值: 1), (1,值: 1), (1,值: 1), (2,值: 2), (2,值: 2), (2,值: 2), (3,值: 3), (3,值: 3)], [(4,值: 4), (4,值: 4), (10,值: 10)]]
可以看到大于3和小于3的分为了两组
repartition
算子功能: 改变分区的数量 注意添加分区可能会导致shuffle, 进而影响到性能, 因此尽量不要改变分区大小, 更不要增大分区
class Repartition {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Repartition")
.setMaster("local[*]"));
List<List<Integer>> rdd1 = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 3)
.glom()
.collect();
System.out.printf("计算结果:%n %s%n", rdd1);
List<List<Integer>> rdd2 = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 2)
.repartition(5)
.glom()
.collect();
System.out.printf("计算结果:%n %s%n", rdd2);
List<List<Integer>> rdd3 = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 2)
.repartition(1)
.glom()
.collect();
System.out.printf("计算结果:%n %s%n", rdd3);
}
}
输出结果为:
计算结果:
[[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]]
计算结果:
[[1, 6], [2, 7], [3, 8], [4, 9], [0, 5]]
计算结果:
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
colalesce
算子功能: 修改分区大小 同repartition相比, 它有一个安全机制, 需要打开shuffle才能增加分区
class Coalesce {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Coalesce")
.setMaster("local[*]"));
List<List<Integer>> rdd1 = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 3)
.glom()
.collect();
System.out.printf("计算结果:%n %s%n", rdd1);
List<List<Integer>> rdd2 = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 2)
.coalesce(5)
.glom()
.collect();
System.out.printf("计算结果:%n %s%n", rdd2);
List<List<Integer>> rdd3 = context.parallelize(IntStream.range(0, 10)
.boxed()
.collect(Collectors.toList()), 2)
.coalesce(1)
.glom()
.collect();
System.out.printf("计算结果:%n %s%n", rdd3);
}
}
输出结果为:
计算结果:
[[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]]
计算结果:
[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]
计算结果:
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
cache
方法将其缓存到内存中, 和persist
将其持久化到磁盘上 persist也可以只持久化到内存或多个内存副本中unpresisit
来主动清理缓存下面是一个例子, rdd1和rdd2会被使用两次
public class RddCache {
@SneakyThrows
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Distinct")
.setMaster("local[*]"));
JavaRDD<Integer> rdd1 = context.parallelize(IntStream.range(0, 10).boxed().collect(Collectors.toList()));
JavaRDD<Integer> rdd2 = rdd1.map(x -> x * 10);
rdd2.cache(); // 将rdd保存下来
JavaRDD<String> rdd3 = rdd2.map(String::valueOf);
Integer sum = rdd2.reduce(Integer::sum);
String expression = rdd3.reduce(String::concat);
System.out.printf("计算结果:%n %s%n", sum);
System.out.printf("计算结果:%n %s%n", expression);
TimeUnit.DAYS.sleep(1);
}
}
在管理界面, 可以看到DAG图:
由图可知rdd1和rdd2被计算了2次; 在将rdd缓存下来之后, rdd1和rdd2就只被计算了1次
class CheckPoint {
@SneakyThrows
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("CheckPoint")
.setMaster("local[*]"));
context.setCheckpointDir("checkpoint");
JavaRDD<Integer> rdd1 = context.parallelize(IntStream.range(0, 10).boxed().collect(Collectors.toList()));
JavaRDD<Integer> rdd2 = rdd1.map(x -> x * 10);
rdd2.checkpoint();
JavaRDD<String> rdd3 = rdd2.map(String::valueOf);
Integer sum = rdd2.reduce(Integer::sum);
String expression = rdd3.reduce(String::concat);
System.out.printf("计算结果:%n %s%n", sum);
System.out.printf("计算结果:%n %s%n", expression);
TimeUnit.DAYS.sleep(1);
}
}
可以看到rdd2被缓存下来了
并且任务2直接从CheckPoint开始执行
class Broadcast {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Distinct")
.setMaster("local[*]"));
Map<Integer, String> nameMap = Map.of(3, "张三", 4, "李四", 5, "王五", 6, "赵六");
List<Tuple3<Integer, String, Integer>> scoreMap = Arrays.asList(
Tuple3.apply(3, "语文", 100),
Tuple3.apply(3, "数学", 100),
Tuple3.apply(4, "语文", 100),
Tuple3.apply(4, "数学", 100),
Tuple3.apply(5, "语文", 100),
Tuple3.apply(5, "数学", 100),
Tuple3.apply(5, "英语", 100),
Tuple3.apply(4, "英语", 100),
Tuple3.apply(3, "英语", 100)
);
org.apache.spark.broadcast.Broadcast<Map<Integer, String>> broadcastNameMap = context.broadcast(nameMap);
System.out.printf("计算结果:%n %s%n", context.parallelize(scoreMap)
.map(tuple -> Tuple3.apply(nameMap.get(tuple._1()), tuple._2(), tuple._3()))
.collect());
System.out.printf("计算结果:%n %s%n", context.parallelize(scoreMap)
.map(tuple -> Tuple3.apply(broadcastNameMap.getValue().get(tuple._1()), tuple._2(), tuple._3()))
.collect());
}
}
broadcast
可以将变量封装为广播变量; 这样就可以节约部分情况下变量的传播假设要累加分布式对象的数量, 若没有变量共享, 每个分区都会有一个累加器, 进而导致累加的数量少于实际的数量, 下面是一个累加器的例子
class Accumulator {
public static void main(String[] args) {
@Cleanup JavaSparkContext context = new JavaSparkContext(new SparkConf()
.setAppName("Accumulator")
.setMaster("local[*]"));
AtomicLong count = new AtomicLong(0); // 不适用累加器
context.parallelize(IntStream.range(0, 10).boxed()
.collect(Collectors.toList()), 3)
.map(x -> {
count.incrementAndGet();
System.out.println("计算过程: " + count.get());
return x;
}).collect();
System.out.printf("计算结果:%n %s%n", count.get());
LongAccumulator countAccumulator = context.sc().longAccumulator();// 累加器
context.parallelize(IntStream.range(0, 10).boxed()
.collect(Collectors.toList()), 3)
.map(x -> {
countAccumulator.add(1);
System.out.println("累加器计算过程: " + countAccumulator.value());
return x;
}).collect();
System.out.printf("累加器计算结果:%n %s%n", countAccumulator.value());
}
}
输出结果为:
计算过程: 1
计算过程: 1
计算过程: 1
计算过程: 2
计算过程: 2
计算过程: 3
计算过程: 2
计算过程: 3
计算过程: 3
计算过程: 4
计算结果:
0
累加器计算过程: 1
累加器计算过程: 2
累加器计算过程: 3
累加器计算过程: 1
累加器计算过程: 2
累加器计算过程: 3
累加器计算过程: 4
累加器计算过程: 1
累加器计算过程: 2
累加器计算过程: 3
累加器计算结果:
10
可以看到每个分区都有一份累加器的拷贝(Executor的拷贝), 并且结算结果是单独的一份拷贝(Driver的拷贝) 传递是值传递, 而非引用传递, 分布式环境下也无法实现引用传递; 但是如果使用Accumulator
的话, 尽管各个分区都是值传递, 但是最后累加的结果会作用在Drive的父拷贝上 注意, 多一个rdd被创创建多次, 会导致accumulator被执行多次, 可以使用cache解决这个问题
Action: 流水线的开关, 只有执行了Action算子, 前面的Transformation算子才会开始执行
Job: 任务, 一个Action会产生一个job
DAG: 有向无环图, 这里特指RDD间血缘关系形成的有向无环图 在运行时, 会生成带有分区关系的DAG
宽依赖: 父RDD的一个分区, 将数据发给子RDD的多个分区 此过程也被成为shuffle
窄依赖: 父RDD的一份分区, 全部将数据发送给子RDD的一个分区
Stage: stage是通过宽依赖划分的, 一个宽依赖会划分出一个新的Stage, 因此Stage内部一定是窄依赖
spark.default.parallelism
配置 可以在启动参数/配置文件/SparkConf对象中配置对于订单数据
id,user_id,commodity_code,count,money
2,user1,00001,2,200
3,user1,00001,2,200
4,user1,00001,2,200
9,user1,00001,2,200
10,user1,00001,2,200
11,user1,00001,2,200
12,user1,00001,2,200
13,user1,00001,2,200
15,user1,00001,2,200
18,user1,00001,20,200
可以通过spark读取
public class SparkSQLBase {
public static void main(String[] args) throws AnalysisException {
// 创建SparkSession对象
SparkSession spark = SparkSession.builder()
.appName("test")
.master("local[*]")
.getOrCreate();
// 通过SparkSession获取SparkContext
SparkContext context = spark.sparkContext();
Dataset<Row> df = spark.read().csv("bigdata/src/main/resources/order.csv")
.toDF("id", "user_id", "commodity_code", "count", "money");
df.printSchema();
df.show();
// 创建表
df.createTempView("order");
// 写sql
spark.sql("SELECT * FROM order limit 3;").show();
// 使用dsl风格写sql
df.where("count=20").show();
}
}
输出为
root
|-- id: string (nullable = true)
|-- user_id: string (nullable = true)
|-- commodity_code: string (nullable = true)
|-- count: string (nullable = true)
|-- money: string (nullable = true)
+---+-------+--------------+-----+-----+
| id|user_id|commodity_code|count|money|
+---+-------+--------------+-----+-----+
| id|user_id|commodity_code|count|money|
| 2| user1| 00001| 2| 200|
| 3| user1| 00001| 2| 200|
| 4| user1| 00001| 2| 200|
| 9| user1| 00001| 2| 200|
| 10| user1| 00001| 2| 200|
| 11| user1| 00001| 2| 200|
| 12| user1| 00001| 2| 200|
| 13| user1| 00001| 2| 200|
| 15| user1| 00001| 2| 200|
| 18| user1| 00001| 20| 200|
+---+-------+--------------+-----+-----+
+---+-------+--------------+-----+-----+
| id|user_id|commodity_code|count|money|
+---+-------+--------------+-----+-----+
| id|user_id|commodity_code|count|money|
| 2| user1| 00001| 2| 200|
| 3| user1| 00001| 2| 200|
+---+-------+--------------+-----+-----+
+---+-------+--------------+-----+-----+
| id|user_id|commodity_code|count|money|
+---+-------+--------------+-----+-----+
| 18| user1| 00001| 20| 200|
+---+-------+--------------+-----+-----+
@Test
public void buildFromRdd() throws AnalysisException {
@Cleanup JavaSparkContext context = JavaSparkContext.fromSparkContext(spark.sparkContext());
JavaRDD<Row> rdd = context.textFile("src/main/resources/traffic.txt", 10)
.map(line -> line.split("\t"))
.map(words -> RowFactory.create(Long.parseLong(words[0]), words[1]));
Dataset<Row> df = spark.createDataFrame(rdd, DataTypes.createStructType(Arrays.asList(
DataTypes.createStructField("phone_number", DataTypes.LongType, true),
DataTypes.createStructField("ip", DataTypes.StringType, true)
)));
df.printSchema();
// 展示数据
// 展示前20条数据, 并且不截断数据
df.show(20, false);
// 将DataSet注册为临时表; 这样就可以查询了
df.createTempView("traffic");
spark.sql("SELECT * FROM traffic where phone_number < 14589530085").show();
}
@Test
public void buildFromSparkSql() throws AnalysisException {
Dataset<Row> df = spark.read()
.format("csv")
.option("header", false)
.option("sep", "\t")
.option("encoding", StandardCharsets.UTF_8.name())
.schema(DataTypes.createStructType(Arrays.asList(
DataTypes.createStructField("phone_number", DataTypes.LongType, true),
DataTypes.createStructField("ip", DataTypes.StringType, true)
)))
.load("src/main/resources/traffic.txt");
df.printSchema();
df.createTempView("traffic");
spark.sql("SELECT * FROM traffic where phone_number < 14589530085").show();
}
数据准备
private final static SparkSession spark = SparkSession.builder()
.appName("test")
.master("local[*]")
.getOrCreate();
private final static Dataset<Row> df = spark.read()
.format("csv")
.option("header", false)
.option("sep", "\t")
.option("encoding", StandardCharsets.UTF_8.name())
.schema(DataTypes.createStructType(Arrays.asList(
DataTypes.createStructField("phone_number", DataTypes.LongType, true),
DataTypes.createStructField("ip", DataTypes.StringType, true),
DataTypes.createStructField("host", DataTypes.StringType, true),
DataTypes.createStructField("up", DataTypes.LongType, false),
DataTypes.createStructField("down", DataTypes.LongType, false),
DataTypes.createStructField("code", DataTypes.IntegerType, false)
)))
.load("src/main/resources/traffic.txt");
@Test
public void dslStyleQuery() {
df.select("ip", "code")
.filter(df.col("phone_number").lt(14589530085L))
.limit(10)
.show();
}
输出为
+--------------+----+
| ip|code|
+--------------+----+
| 110.11.174.29| 200|
| 21.234.130.14| 200|
| 90.242.200.96| 404|
| 68.99.109.14| 200|
|148.227.226.79| 404|
|153.178.25.132| 200|
| 191.49.192.31| 500|
| 10.60.145.193| 500|
| 52.122.13.63| 500|
| 203.82.225.65| 500|
+--------------+----+
@Test
public void sqlStyleQuery() throws AnalysisException {
// // 创建全局临时试图, 可以跨session共享
// df.createGlobalTempView()
// // 同createTempView, 但是视图存在则替换;
// df.createOrReplaceGlobalTempView();
df.createTempView("traffic");
spark.sql("SELECT code, count(*) FROM traffic group by code").show();
}
输出为
+----+--------+
|code|count(1)|
+----+--------+
| 500| 38|
| 404| 25|
| 200| 37|
+----+--------+
@Test
public void wordCount_buildFromRDD() throws AnalysisException {
@Cleanup JavaSparkContext context = JavaSparkContext.fromSparkContext(spark.sparkContext());
JavaRDD<Row> rdd = context.textFile("hdfs://server.passnight.local/test/word list.txt", 10)
.flatMap(line -> Arrays.stream(line.split(" ")).iterator())
.map(RowFactory::create);
Dataset<Row> df = spark.createDataFrame(rdd, DataTypes.createStructType(List.of(
DataTypes.createStructField("word", DataTypes.StringType, false)
)));
df.createTempView("words");
spark.sql("SELECT word, count(*) AS cnt FROM words GROUP BY word ORDER BY cnt DESC")
.show();
}
输出为
+---------+---+
| word|cnt|
+---------+---+
| I| 4|
| love| 2|
|passnight| 2|
| like| 2|
| hadoop| 2|
+---------+---+
@Test
public void wordCount_buildFromSparkSql() throws AnalysisException {
Dataset<Row> words = spark.read()
.text("hdfs://server.passnight.local/test/word list.txt");
words.printSchema();
Dataset<Row> df2 = words.withColumn("value", functions.explode(functions.split(words.col("value"), " ")));
df2.createTempView("words");
df2.groupBy("value")
.count()
.orderBy("count")
.show();
}
输出为
+---------+-----+
| value|count|
+---------+-----+
| love| 2|
|passnight| 2|
| like| 2|
| hadoop| 2|
| I| 4|
+---------+-----+
@Test
public void writeText() {
// text只能写出一列数据, 因此要将df转化为一列
df.select(functions.concat_ws("---",
functions.col("ip"),
functions.col("up"),
functions.col("down")))
.write()
.mode("overwrite")
.format("text")
.save("data.txt");
}
@Test
public void writeCsv() {
df.select(functions.col("ip"),
functions.col("up"),
functions.col("down"))
.write()
.mode("overwrite")
.option("sep", ",")
.option("header", true)
.format("csv")
.save("data.csv");
}
@Test
public void writeJson() {
df.select(functions.col("ip"),
functions.col("up"),
functions.col("down"))
.write()
.mode("overwrite")
.format("json")
.save("data.json");
}
@Test
public void writeParquet() {
df.select(functions.col("ip"),
functions.col("up"),
functions.col("down"))
.write()
.mode("overwrite")
.format("parquet")
.save("data.parquet");
}
UDF
类, 方法名称为evaluate, 返回值不能为void; 本质上是一个方法groupBy
一起使用flatMap
spark.udf().register()
后, 通过funcions.callUDF
调用funcitons.udf
的返回值调用spark.udf().register()
后, 直接在SQL中调用 @Test
public void basicUdf() throws AnalysisException {
Dataset<Row> df = spark.createDataFrame(IntStream.range(0, 10)
.boxed()
.map(RowFactory::create)
.collect(Collectors.toList()),
DataTypes.createStructType(List.of(DataTypes.createStructField("value", DataTypes.IntegerType, false))));
// 注册一个udf, 名称为`timeTen`
// `timeTen`名称可以可以用于SQL风格调用
// dsl风格通过`functions.callUDF()`调用
// 也可以通过functions.udf注册, 这样可以直接通过返回的方法调用
spark.udf().register("timeTen", (UDF1<Integer, Integer>) x -> 10 * x, DataTypes.IntegerType);
// dsl风格, 使用`functions.callUDF()`调用
UserDefinedFunction timeTen = functions.udf((UDF1<Integer, Integer>) x -> x * 10, DataTypes.IntegerType);
df.withColumn("value", functions.callUDF("timeTen", functions.col("value")))
.show();
// dsl风格, 使用`functions.udf()`返回值
df.withColumn("value", timeTen.apply(functions.col("value")))
.show();
// SQL风格, 直接在SQL中调用
df.createTempView("values");
spark.sql("select timeTen(value) from values").show();
}
输出为
+-----+
|value|
+-----+
| 0|
| 10|
| 20|
| 30|
| 40|
| 50|
| 60|
| 70|
| 80|
| 90|
+-----+
+-----+
|value|
+-----+
| 0|
| 10|
| 20|
| 30|
| 40|
| 50|
| 60|
| 70|
| 80|
| 90|
+-----+
+--------------+
|timeTen(value)|
+--------------+
| 0|
| 10|
| 20|
| 30|
| 40|
| 50|
| 60|
| 70|
| 80|
| 90|
+--------------+
@Test
public void arrayUdf() {
Dataset<Row> df = spark.createDataFrame(IntStream.range(0, 10)
.boxed()
.map(RowFactory::create)
.collect(Collectors.toList()),
DataTypes.createStructType(List.of(DataTypes.createStructField("value", DataTypes.IntegerType, false))));
UserDefinedFunction toArray = functions.udf((UDF1<Integer, List<Integer>>) x -> Arrays.asList(x, x, x, x, x), DataTypes.createArrayType(DataTypes.IntegerType));
df.withColumn("value", toArray.apply(functions.col("value"))).show();
}
输出为
+---------------+
| value|
+---------------+
|[0, 0, 0, 0, 0]|
|[1, 1, 1, 1, 1]|
|[2, 2, 2, 2, 2]|
|[3, 3, 3, 3, 3]|
|[4, 4, 4, 4, 4]|
|[5, 5, 5, 5, 5]|
|[6, 6, 6, 6, 6]|
|[7, 7, 7, 7, 7]|
|[8, 8, 8, 8, 8]|
|[9, 9, 9, 9, 9]|
+---------------+
@Test
public void mapUdf() {
Dataset<Row> df = spark.createDataFrame(IntStream.range(0, 10)
.boxed()
.map(RowFactory::create)
.collect(Collectors.toList()),
DataTypes.createStructType(List.of(DataTypes.createStructField("value", DataTypes.IntegerType, false))));
UserDefinedFunction toArray = functions.udf((UDF1<Integer, Map<Integer, String>>) x -> Map.of(x, String.valueOf(x)), DataTypes.createMapType(DataTypes.IntegerType, DataTypes.StringType));
df.withColumn("value", toArray.apply(functions.col("value"))).show();
}
输出为
+--------+
| value|
+--------+
|{0 -> 0}|
|{1 -> 1}|
|{2 -> 2}|
|{3 -> 3}|
|{4 -> 4}|
|{5 -> 5}|
|{6 -> 6}|
|{7 -> 7}|
|{8 -> 8}|
|{9 -> 9}|
+--------+
public class WindowTest {
private final static SparkSession spark = SparkSession.builder()
.appName("test")
.master("local[*]")
.getOrCreate();
private final static Dataset<Row> df = spark.read()
.format("csv")
.option("header", false)
.option("sep", "\t")
.option("encoding", StandardCharsets.UTF_8.name())
.schema(DataTypes.createStructType(Arrays.asList(
DataTypes.createStructField("phone_number", DataTypes.LongType, true),
DataTypes.createStructField("ip", DataTypes.StringType, true),
DataTypes.createStructField("host", DataTypes.StringType, true),
DataTypes.createStructField("up", DataTypes.LongType, false),
DataTypes.createStructField("down", DataTypes.LongType, false),
DataTypes.createStructField("code", DataTypes.IntegerType, false)
))).load("src/main/resources/traffic.txt");
@BeforeClass
public static void setUpClass() throws AnalysisException {
df.createTempView("traffic");
}
@Test
public void aggregationWindow() {
spark.sql("SELECT *, AVG(down) OVER() AS avg_down FROM traffic").show();
}
@Test
public void orderWindow() {
spark.sql("SELECT *, RANK() OVER(ORDER BY down DESC) AS rank_down," +
"DENSE_RANK() OVER(PARTITION BY code ORDER BY down DESC) AS dense_rank_down," +
"ROW_NUMBER() OVER(ORDER BY down) AS row_number_down FROM traffic").show();
}
}
输出结果为
+------------+---------------+--------------------+----+----+----+--------+
|phone_number| ip| host| up|down|code|avg_down|
+------------+---------------+--------------------+----+----+----+--------+
| 14591430480| 206.175.250.82| web-49.28.cn|6652|4853| 200| 4597.34|
| 14576404331| 110.11.174.29| lt-91.duxu.cn|3691|9180| 200| 4597.34|
| 14582487728| 21.234.130.14| desktop-19.13.cn|2797|1428| 200| 4597.34|
| 14596521336| 149.125.91.187| db-46.guiying.cn|4742|3870| 500| 4597.34|
| 15964887988|201.254.165.183|desktop-12.guiyin...|8266|8951| 500| 4597.34|
| 14582499209| 90.242.200.96| db-36.05.cn|3686|1143| 404| 4597.34|
| 14505200322| 68.99.109.14| web-94.40.org|6978|4684| 200| 4597.34|
| 15057102608| 73.31.103.153|desktop-77.zhongw...|1180| 785| 404| 4597.34|
| 15961211597| 159.244.71.102| web-89.dh.net|8526|4965| 500| 4597.34|
| 15311413947| 60.85.30.231| db-34.shenhuang.cn|1942|9698| 500| 4597.34|
| 13755692548| 148.227.226.79| srv-89.fanggao.cn|3049|2243| 404| 4597.34|
| 15512665231| 172.147.244.20| lt-73.taoguiying.cn|9494| 151| 200| 4597.34|
| 13671972925| 153.178.25.132| srv-73.31.cn|3311|1452| 200| 4597.34|
| 18142899590| 31.150.73.196| web-49.nl.cn|2909| 277| 500| 4597.34|
| 15013760479| 94.26.117.22| email-47.63.cn|5645|4756| 200| 4597.34|
| 15696235979| 80.73.193.75| lt-91.lei.cn|9845|1267| 404| 4597.34|
| 15678423363| 171.44.202.193| db-37.99.cn|7496|7354| 200| 4597.34|
| 13313631905| 191.49.192.31| laptop-78.nd.cn|3037|3070| 500| 4597.34|
| 15911783755| 208.18.32.83| db-48.yao.net|4846|4935| 404| 4597.34|
| 14589530086| 57.193.203.100| srv-25.mingcao.cn|1861|3034| 200| 4597.34|
+------------+---------------+--------------------+----+----+----+--------+
+------------+---------------+--------------------+----+----+----+---------+---------------+---------------+
|phone_number| ip| host| up|down|code|rank_down|dense_rank_down|row_number_down|
+------------+---------------+--------------------+----+----+----+---------+---------------+---------------+
| 14576404331| 110.11.174.29| lt-91.duxu.cn|3691|9180| 200| 5| 1| 96|
| 15848614274|221.193.203.253| web-81.duanxiao.cn|8474|8973| 200| 6| 2| 95|
| 15133313425| 3.41.203.203| laptop-65.fan.cn|1979|8496| 200| 12| 3| 89|
| 14722781518| 41.33.230.230|laptop-66.zhangze...|3840|8161| 200| 15| 4| 86|
| 13985614323| 105.154.67.146| laptop-67.jie.cn|5629|7876| 200| 18| 5| 83|
| 15025055835| 57.67.224.58| web-46.weiliao.cn|1093|7830| 200| 19| 6| 82|
| 15708423956| 52.173.24.63| web-36.yangchao.cn| 201|7774| 200| 20| 7| 81|
| 15550368967| 181.80.90.147| laptop-44.yanli.cn|4669|7770| 200| 21| 8| 80|
| 15281538689| 173.204.178.87| web-83.weimin.cn|7082|7718| 200| 23| 9| 78|
| 13618975336| 178.12.49.98| srv-13.min.net|9290|7560| 200| 24| 10| 77|
| 14765852831| 121.98.240.15| laptop-81.fang.cn|3595|7556| 200| 25| 11| 76|
| 15678423363| 171.44.202.193| db-37.99.cn|7496|7354| 200| 27| 12| 74|
| 13434369051| 220.82.35.57| srv-03.longqian.org|2160|6650| 200| 30| 13| 71|
| 14554599007| 52.35.92.91| web-91.xiulanlai.cn|6098|5124| 200| 38| 14| 63|
| 14591430480| 206.175.250.82| web-49.28.cn|6652|4853| 200| 42| 15| 59|
| 15013760479| 94.26.117.22| email-47.63.cn|5645|4756| 200| 43| 16| 58|
| 14505200322| 68.99.109.14| web-94.40.org|6978|4684| 200| 46| 17| 55|
| 13913021809| 190.111.163.19| srv-83.jn.cn|8296|4349| 200| 48| 18| 53|
| 15108282222| 56.175.78.40| laptop-17.mao.net|8846|3830| 200| 55| 19| 46|
| 18788825153| 61.76.43.152| lt-48.81.net|8101|3812| 200| 56| 20| 45|
+------------+---------------+--------------------+----+----+----+---------+---------------+---------------+
RDD的执行流程RDD->DAGScheduler->TaskSceduler->Worker
与RDD不同的是, SparkSQL会对写完的代码执行自动优化; 以提高代码执行效率
SparkSQL可以自动优化而RD不行的原因
Catalyst执行流程:
2. API层接受SQL语句, Catalyst解析SQL并生对应的RDD执行计划, 并由集群执行
具体流程
在AST中加入元数据信息, 便于后续优化 如score.id -> id#1#L; 表score.id的id为1, 类型为Long
进行优化, 主要的友发方式有谓词下推即列值裁剪
生成执行计划: 根据上述过程生成的优化后的AST, 生成物理计划, 从而生成RDD来执行
^:
Downloads | Apache Spark ↩︎