Spark知识点讲解
- Spark
- Spark环境部署
- Spark简介
- 为什么使用Spark
- Spark的优势
- Spark技术栈
- Spark架构设计
- Spark架构核心组件
- Spark交互工具
- Spark API
- SparkContext
- SparkSession
- RDD
- DataSet
- DataFrame
- Spark RDD
- 概念
- RDD与DAG
- RDD的特性
- RDD编程流程
- 创建RDD
- RDD分区
- RDD的操作
- RDD转换算子
- RDD常用算子
- Spark分布式计算原理
- RDD的依赖关系
- DAG的工作原理
- Spark Shuffle过程
- RDD优化
- RDD持久化
- 检查点
- 共享变量
- 分区设计
- 数据倾斜
- 装载数据源
- Spark SQL
- Spark SQL架构
- Spark SQL运行原理
- 优化器
- SparkSQL API
- Dataset
- DataFrame
- SparkSQL操作外部数据源
- Parquet文件
- Hive表
- RDBMS表
- SparkSQL函数
- 内置函数
- 自定义函数
- Spark性能优化
- 序列化
- SparkGraphX图形数据分析
- 图的基本概念
- GraphX核心抽象
- GraphX API
- pergel函数
Spark
Spark环境部署
- 前置条件:完成Scala环境部署
- 配置Scala和Spark
Spark简介
- 诞生于加州大学伯克利分校AMP实验室,是一个基于内存的分布式计算框架
为什么使用Spark
- MapReduce编程模型的局限性,只有Map和Reduce两个操作,复杂的逻辑需要大量的样板代码
- MapReduce处理效率低,Map中间结果写磁盘,Reduce写HDFS,多个Map通过HDFS交换数据,任务调度与启动开销大,不适合迭代处理、交互式处理和流式处理
- Spark是类Hadoop MapReduce的通用并行框架,Job中间数据结果可以保存在内存,不再需要读写HDFS,比MapReduce平均快10倍以上
Spark的优势
- 速度快
基于内存数据处理,比MR快100个数量级以上
基于硬盘数据处理,比MR快10个数量级以上 - 易用性
支持Java、Scala、Python、R语言
交互式shell方便开发测试 - 通用性
一站式解决方案:批处理、交互式查询、实时流处理、图计算及机器学习 - 随处运行
YARN、Mesos、EC2、Kubernetes、Standalone、Local
Spark技术栈
- Spark Core
核心组件,分布式计算引擎 - Spark SQL
高性能的基于Hadoop的SQL解决方案 - Spark Streaming
可以实现高吞吐量、具备容错机制的准实时流处理系统 - Spark GraphX
分布式图处理框架 - Spark MLlib
构建在Spark上的分布式机器学习库
Spark架构设计
spark的运行架构
- 在驱动程序中,通过Spark Context主导应用的执行
- Spark Context可以连接不同类型的Cluster Manager(Standalone、YARN、Mesos),连接后,获得集群节点上的Executor
- 一个Worker节点默认一个Executor,可通过SPARK_WORKER_INSTANCES调整
- 每个应用获取自己的Executor
- 每个Task处理一个RDD分区
Spark架构核心组件
组件 | 说明 |
Application | 建立在Spark上的用户程序,包括Driver代码和运行在集群各节点Executor中的代码 |
Driver Program | 驱动程序,Application中的main函数并创建Spark Context |
Cluster Manager | 在集群(Standalone、Mesos、YARN)上获取资源的外部服务 |
Worker Node | 集群中任何可以运行Application代码的节点 |
Executor | 某个Application运行在workder节点上的一个进程 |
Task | 被送到某个Executor上的工作单元 |
Job | 包含多个Task组成的并行计算,往往由Spark Action触发生成,一个Application中往往产生多个Job |
Stage | 每个job会被拆分成多组Task,作为一个TaskSet,其名称为Stage |
Spark交互工具
- 本机
spark-shell --master local[*] - Standalone
spark-shell --master spark://MASTERHOST:7077 - YARN
spark-shell --master yarn-client
Spark API
SparkContext
- 连接Driver与Spark Cluster(Workers)
- Spark的主入口
- 每个JVM仅能有一个活跃的Spark Context
- SparkContext.getOrCreate
import org.apache.spark.SparkConf;
import org.apache.spark.SparkContext;
public class Main {
public static void main(String... args){
SparkConf sparkConf = new SparkConf().setMaster("local[*]").setAppName("my_spark");
SparkContext sparkContext = SparkContext.getOrCreate(sparkConf);
}
}
SparkSession
- Spark2.0+应用程序的主入口:包含了Spark Context、SQL Context、Hive Context以及Streaming Context
- SparkSession.getOrCreate()
import org.apache.spark.sql.SparkSession;
public class Main {
public static void main(String... args) {
SparkSession sparkSession = SparkSession
.builder()
.master("local[*]")
.appName("my_spark")
.getOrCreate();
}
}
RDD
- 弹性分布式数据集
- Spark核心,主要数据抽象
DataSet
- 从Spark1.6开始引入的新的抽象、特定领域对象中的强类型集合,它可以使用函数或者相关操作并行的进行转换等操作
DataFrame
- DataFrame是特殊的DataSet
Spark RDD
概念
简单的解释:群的工作节点上的内存中,并执行正确的操作。
复杂的解释:assandra、HBase等、或缓存(内存、内存+磁盘、仅磁盘等),或在故障或缓存收回时重新计算其他RDD分区中的数据
RDD是弹性分布式数据集(Resilient Distributed Datasets)
- 分布式数据集
RDD是只读的、分区记录的集合,每个分区分布在集群的不同节点上;
RDD并不存储真正的数据,只是对数据和操作的描述 - 弹性
RDD默认存放在内存中,当内存不足,Spark自动将RDD写入磁盘 - 容错性
根据数据血统,可以自动从节点失败总恢复分区
RDD与DAG
- 两者是Spark提供的核心抽象
- DAG(有向无环图)反映了RDD之间的依赖关系
RDD的特性
- 一系列的分区信息,每个任务处理一个分区
- 每个分区上都有compute函数,计算该分区中的数据
- RDD之间有一系列的依赖
- 分区函数决定数据(key-value)分配到哪个分区
- 最佳位置列表,将计算任务分配到其所处理数据块的存储位置
RDD编程流程
创建RDD
第一种:使用集合创建RDD
import org.apache.spark.SparkContext;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.SparkSession;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String... args) {
SparkSession sparkSession = SparkSession
.builder()
.master("local[*]")
.appName("my_spark")
.getOrCreate();
SparkContext sc = sparkSession.sparkContext();
JavaSparkContext jsc = JavaSparkContext.fromSparkContext(sc);
List<Integer> list = Arrays.asList(1, 2, 3, 4);
JavaRDD<Integer> data = jsc.parallelize(list);
System.out.println(data.count());
}
}
第二种:通过加载文件生成RDD
import org.apache.spark.SparkContext;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.sql.SparkSession;
public class Main {
public static void main(String... args) {
SparkSession sparkSession = SparkSession
.builder()
.master("local[*]")
.appName("my_spark")
.getOrCreate();
SparkContext sc = sparkSession.sparkContext();
JavaSparkContext jsc = JavaSparkContext.fromSparkContext(sc);
// List<Integer> list = Arrays.asList(1, 2, 3, 4);
// JavaRDD<Integer> data = jsc.parallelize(list);
// System.out.println(data.count());
JavaRDD<String> rdd = jsc.textFile("D:\\a.txt");
System.out.println(rdd.count());
JavaRDD<String> hdfsRDD = jsc.textFile("hdfs://hadoop000:8020/hello.txt");
}
}
第三种:其他创建RDD的方法
- SparkContext.wholeTextFiles();可以针对一个目录中的大量小文件返回<filename,fileContent>作为PairRDD
- RDD
- PairRDD
- SparkContext.sequenceFile,Hadoop SequenceFile的读写支持
- SparkContext.hadoopRDD()、newAPIHadoopRDD(),从hadoop接口API创建
-SparkContext.objectFile(),RDD.saveAsObjectFile()的逆操作
RDD分区
分区是RDD被拆分并发送到节点的不同块之一
- 拥有的分区越多,得到得并行性就越强
- 每个分区都是被分发到不同worker node得候选者
- 每个分区对应一个task
RDD的操作
分为lazy和non-lazy两种:
- Transformation(lazy):也称转换操作、转换算子
- Actions(non-lazy):立即执行,也称动作操作、动作算子
RDD转换算子
对于转换操作,RDD的所有转换都不会直接计算结果
仅记录作用于RDD上的操作
只有遇到Action算子才会进行真正的计算
RDD常用算子
- parallelize
JavaRDD<String> javaStringRDD = sc.parallelize(Arrays.asList("abc", "bcd"));
- makeRDD
只有scala有makeRDD
sc.makeRDD(List("abc","bcd"));
- textFile
调用SparkContext.textFile()方法,从外部存储中读取数据来创建RDD
JavaRDD<String> lines = sc.textFile("F:\\dataexample\\wordcount\\input");
注:textFile支持分区,支持模式匹配,例如把F:\dataexample\wordcount\目录下inp开头的给转换成rdd
JavaRDD<String> lines = sc.textFile("F:\\dataexample\\wordcount\\inp*");
多个路径可以使用逗号分隔,例如:
var lines = sc.textFile("dir1, dir2", 3);
- filter
过滤
JavaRDD<String> lines = jsc.textFile("D:\\a.txt");
lines = lines.filter(f -> f.contains("org"));
lines.foreach(f->{
System.out.println(f);
});
- map
map()接收一个函数,把这个函数用于RDD中的每个元素,将函数的返回结果作为结果RDD编程
JavaRDD<Iterable> it = lines.map(f -> Arrays.asList(f.split("\\+")));
System.out.println(it.first());
- flatMap
有时候我们希望对某个元素生成多个元素,实现该功能的操作叫做flatMap(),flatMap的函数应用于每一个元素,对于每一个元素返回的多个元素组成的迭代器
JavaRDD<String> result = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split("\\+")).iterator();
}
});
//lambda
JavaRDD<String> result2 = lines.flatMap(s -> Arrays.asList(s.split("\\+")).iterator());
- distinct
用于去重,我们生成的RDD可能有重复的元素,使用distinct方法可以去掉重复的元素,不过此方法设计到混洗,操作开销很大
JavaRDD<String> rdd = jsc.parallelize(Arrays.asList("aa", "bb", "cc", "dd", "bb"));
rdd = rdd.distinct();
rdd.foreach(f -> System.out.println(f));
- union
两个RDD进行合并
JavaRDD<String> rdd1 = jsc.parallelize(Arrays.asList("aa", "bb", "cc", "dd", "bb"));
JavaRDD<String> rdd2 = jsc.parallelize(Arrays.asList("ee", "ff"));
JavaRDD<String> unionRDD = rdd1.union(rdd2);
unionRDD.foreach(f -> System.out.println(f));
- intersection
返回两个RDD的交集,并且去重,intersection需要混洗数据,比较浪费性能
JavaRDD<String> rdd1 = jsc.parallelize(Arrays.asList("aa", "bb", "cc", "dd", "bb"));
JavaRDD<String> rdd2 = jsc.parallelize(Arrays.asList("aa", "ee", "ff"));
JavaRDD<String> intersectionRDD = rdd1.intersection(rdd2);
intersectionRDD.foreach(f -> System.out.println(f));
- subtract
RDD1.subtract(RDD2),返回在RDD1中出现,但是不在RDD2中出现的元素,不去重
JavaRDD<String> subtractRDD = rdd1.subtract(rdd2);
- cartesian
RDD1.cartesian(RDD2)返回RDD1和RDD2的笛卡尔积,开销非常大
JavaRDD<String> RDD1 = jsc.parallelize(Arrays.asList("1", "2", "3"));
JavaRDD<String> RDD2 = jsc.parallelize(Arrays.asList("a", "b", "c"));
JavaPairRDD<String, String > cartesianRDD = RDD1.cartesian(RDD2);
List<Tuple2<String, String>> list = cartesianRDD.collect();
list.forEach(f->{
System.out.println(f._1 + "<>" + f._2);
});
- mapToPair
输出Map
JavaPairRDD<String, Integer> mapToPairRDD = rdd.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
JavaPairRDD<String, Integer> mapToPairRDD2 = rdd.mapToPair((PairFunction<String, String, Integer>) s -> new Tuple2<>(s, 1));
- flatMapToPair
flatMapToPair可以一个元素返回多个,相当于先flatMap,再mapToPair
JavaRDD<String> lines = jsc.textFile("d:\\sample.txt");
JavaPairRDD<String, Integer> result = lines.flatMapToPair(new PairFlatMapFunction<String, String, Integer>() {
@Override
public Iterator<Tuple2<String, Integer>> call(String s) throws Exception {
List<Tuple2<String, Integer>> tuple2List = new ArrayList<>();
//至少出现一个空格
String[] array = s.split("\\s+");
for (int i = 0; i < array.length; i++) {
tuple2List.add(new Tuple2<>(array[i], 1));
}
return tuple2List.iterator();
}
});
JavaPairRDD<String, Integer> result2 = lines.flatMapToPair((PairFlatMapFunction<String, String, Integer>) s -> {
List<Tuple2<String, Integer>> tuple2List = new ArrayList<>();
//至少出现一个空格
String[] array = s.split("\\s+");
for (int i = 0; i < array.length; i++) {
tuple2List.add(new Tuple2<>(array[i], 1));
}
return tuple2List.iterator();
});
- combineByKey
分布式数据集聚合操作的鼻祖 - reduceByKey
根据Key对vlue做聚合
JavaRDD<String> lines = jsc.textFile("d:\\sample.txt");
JavaPairRDD<String, Integer> result = lines.flatMapToPair(new PairFlatMapFunction<String, String, Integer>() {
@Override
public Iterator<Tuple2<String, Integer>> call(String s) throws Exception {
List<Tuple2<String, Integer>> tuple2List = new ArrayList<>();
//至少出现一个空格
String[] array = s.split("\\s+");
for (int i = 0; i < array.length; i++) {
tuple2List.add(new Tuple2<>(array[i], 1));
}
return tuple2List.iterator();
}
});
JavaPairRDD<String, Integer> result2 = lines.flatMapToPair((PairFlatMapFunction<String, String, Integer>) s -> {
List<Tuple2<String, Integer>> tuple2List = new ArrayList<>();
//至少出现一个空格
String[] array = s.split("\\s+");
for (int i = 0; i < array.length; i++) {
tuple2List.add(new Tuple2<>(array[i], 1));
}
return tuple2List.iterator();
});
JavaPairRDD<String, Integer> result3 = result2.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer i1, Integer i2) throws Exception {
return i1 + i2;
}
});
Map<String, Integer> map = result3.collectAsMap();
map.entrySet().forEach(f -> {
System.out.println(f.getKey() + "<>" + f.getValue());
});
- foldByKey
该函数用于RDD[K,V]根据K将V做折叠、合并处理 - sortByKey
sortByKey函数用于对pairRDD按照Key进行排序,第一个参数可以设置true或者false,默认是true - groupByKey
将RDD[key,value]按照key进行分组,形成RDD[key,Interable]的形式,类似sql中的groupby或group_concat
JavaRDD<Tuple2<String, Double>> score = jsc.parallelize(Arrays.asList(new Tuple2("xm",60), new Tuple2("xm", 70), new Tuple2("zs", 90), new Tuple2("zs", 98)));
JavaPairRDD<String, Double> scoreRDD = JavaPairRDD.fromJavaRDD(score);
JavaPairRDD<String, Iterable<Double>> scorePairRDD = scoreRDD.groupByKey();
Map<String, Iterable<Double>> resultMap = scorePairRDD.collectAsMap();
resultMap.entrySet().forEach(f -> System.out.println("key>>>>" + f.getKey()));
- cogroup
groupByKey是对单个RDD的数据进行分组,cogroup是对多个共享同一个键的RDD进行分组;
例如:
RDD1.cogroup(RDD2)会将RDD1和RDD2按照相同的key进行分组,得到(key,RDD[key,Iterable[value1],Iterable[value2]])
cogroup也可以进行多个分组
JavaRDD<Tuple2<String, Double>> scoreDetail1 = jsc.parallelize(Arrays.asList(new Tuple2("zhangsan", 75), new Tuple2("zhangsan", 90), new Tuple2("lisi", 97), new Tuple2("lisi", 96)));
JavaRDD<Tuple2<String, Double>> scoreDetail2 = jsc.parallelize(Arrays.asList(new Tuple2("zhangsan", 75), new Tuple2("wangwu", 60), new Tuple2("wangwu", 62)));
JavaRDD<Tuple2<String, Double>> scoreDetail3 = jsc.parallelize(Arrays.asList(new Tuple2("zhangsan", 45), new Tuple2("zhangsan", 24), new Tuple2("lisi", 97), new Tuple2("lisi", 35)));
JavaPairRDD<String, Double> scoreMap1 = JavaPairRDD.fromJavaRDD(scoreDetail1);
JavaPairRDD<String, Double> scoreMap2 = JavaPairRDD.fromJavaRDD(scoreDetail2);
JavaPairRDD<String, Double> scoreMap3 = JavaPairRDD.fromJavaRDD(scoreDetail3);
JavaPairRDD<String, Tuple3<Iterable<Double>, Iterable<Double>, Iterable<Double>>> scoreReslt = scoreMap1.cogroup(scoreMap2, scoreMap3);
Map<String, Tuple3<Iterable<Double>, Iterable<Double>, Iterable<Double>>> scoreResultMap = scoreReslt.collectAsMap();
scoreResultMap.entrySet().forEach(f -> System.out.println("cogroup key:" + f.getKey()));
- subtractByKey
删除RDD中键与其他RDD中键相同的元素 - join
RDD1.join(RDD2),可以把RDD1,RDD2中的相同的key给连接起来,类似与sql中的join操作 - fullOuterJoin
和join,不过fullOouterJoin是全连接 - rightOuterJoin
类似右外连接 - leftOuterJoin
类似左外连接 - first【action操作】
返回第一个元素 - take【action操作】
返回第N个元素 - collect【action操作】
返回RDD中的所有元素 - count【action操作】
返回RDD中的元素的个数 - countByValue【action操作】
各元素在RDD中出现的次数 - reduce【action操作】
并行整合RDD中的数据 - aggregate【action操作】
和Reduce类似 - fold【action操作】
一般不使用 - top【action操作】
按照顺序返回前N个元素 - takeOrdered【action操作】
对RDD元素进行升序排序,取出前n个元素并返回,也可以自定义比较器(这里不介绍),类似于top的相反的方法 - foreach
遍历RDD - countByKey【action操作】
按Key进行计数 - collectAsMap【action操作】
RDD转换成Map - saveAsTextFile【action操作】
保存到文件 - saveAsSequenceFile【action操作】
以SequenceFile的文件格式保存到HDFS - saveAsObjectFile【action操作】
- saveAsHadoopFile【action操作】
- mapPartitions
先partition,再把每个partition进行map函数,
比如,将RDD中的所有数据通过JDBC连接写入数据库,如果使用map函数,可能要为每一个元素都创建一个connection,这样开销很大,如果使用mapPartitions,那么只需要针对每一个分区建立一个connection.
JavaRDD<Integer> rdd = jsc.parallelize(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 1));
JavaRDD<Integer> mapPartitionRDD = rdd.mapPartitions(new FlatMapFunction<Iterator<Integer>, Integer>() {
@Override
public Iterator<Integer> call(Iterator<Integer> it) throws Exception {
List<Integer> results = new ArrayList<>();
while (it.hasNext()) {
int i = it.next();
results.add(i * i);
}
return results.iterator();
}
});
mapPartitionRDD.foreach(f -> System.out.println(f));
- mapPartitionsWithIndex
与mapPartitionWithIndex类似,也是按照分区进行的map操作,不过mapPartitionsWithIndex传入的参数多了一个分区的值,下面举个例子,为统计各个分区中的元素 (稍加修改可以做统计各个分区的数量) - HashPartitioner
默认的分区就是HashPartition分区,默认分区不再介绍,下面介绍HashPartition的使用 - RangePartitioner
我理解成范围分区器
使用一个范围,将范围内的键分配给相应的分区。这种方法适用于键中有自然排序,键不为负。本文主要介绍如何使用,原理以后再仔细研究,以下代码片段显示了RangePartitioner的用法
Spark分布式计算原理
RDD的依赖关系
- Lineage:血统、遗传
RDD最重要的特征之一,保存了RDD的依赖关系
RDD实现了基于Lineage的容错机制 - 依赖关系
宽依赖
窄依赖 - 宽依赖对比窄依赖
宽依赖对应shuffle操作,需要在运行时将同一个父RDD的分区传入到不同的子RDD分区中,不同的分区可能位于不同的节点,就可能设计多个节点间的数据传输
当RDD分区丢失时,Spark会对数据进行重新计算,对于窄依赖只需要重新计算一次子RDD的父RDD分区。
结论:相比于宽依赖,窄依赖对优化更有利
DAG的工作原理
- 根据RDD之间的依赖关系,形成一个DAG(有向无环图)
- DAGScheduler将DAG划分为多个Stage
划分依据:是否发生宽依赖(shuffle)
划分规则:从后往前,遇到宽依赖切割为新的Stage
每个Stage由一组并行的Task组成
为什么需要划分Stage?
数据本地化
移动计算,而不是移动数据
保证一个Stage内不会发生数据移动
最佳实践:
尽量避免shuffle
提前部分聚合减少数据移动
Spark Shuffle过程
- 在分区之间重新分配数据
父RDD中同一分区中的数据按照算子要求重新进入子RDD的不同分区中
中间结果写入磁盘
由子RDD拉取数据,而不是由父RDD推送
默认情况下,shuffle不会改变分区数量
RDD优化
- RDD持久化
- RDD共享变量
- RDD分区设计
- 数据倾斜
RDD持久化
- RDD缓存机制:缓存数据至内存/磁盘,可大幅度提升spark应用性能
cache=persisit(MEMORY)
persist - 缓存策略StorageLevel
MEMORY_ONLY(默认)
MEMORY_AND_DISK
DISK_ONLY - 缓存应用场景:
从文件加载数据之后,因为重新获取文件成本较高,经过较多的算子变换之后,重新计算成本较高;
单个非常消耗资源的算子之后; - 使用注意事项:
cache()或persist()后不能再由其他算子
cache()或persisit()遇到action算子完成后才生效
检查点
类似于快照
setCheckpointDir(“hdfs:/checkpoint0918”)
检查点与缓存的区别:
检查点会删除RDD lineage,而缓存不会
Spark Context被销毁后,检查点数据不会被删除
共享变量
广播变量:允许开发者将一个只读变量driver端缓存到每个节点Executor上,而不是每个任务传递一个副本
sc.broadcase();
注意事项:
- Driver端变量在每个Executor每个Task保存一个变量副本
- Driver端广播变量在每个Executor只保存一个变量副本
分区设计
- 分区大小限制为2GB
- 分区太少不利于并发,更容易受数据倾斜影响
- groupby,reduceByKey,sortByKey等内存压力增大
数据倾斜
指分区中的数据分配不均匀,数据集中在少数分区中,严重影响性能通常发生在groupby,join等之后
解决方案:使用新的hash值(如对key加盐)重新分区
装载数据源
Spark SQL
- Spark sql是把spark sql转换成RDD,然后提交到集群执行,执行效率非常快;同时spark sql也支持从hive中读取数据。
Spark SQL架构
- Spark SQL是Spark的核心组件之一
- 能够直接访问现存的Hive数据
- 提供JDBC/ODBC接口供第三方工具借助Spark进行数据处理
- 提供了更高层级的接口方便的处理数据
- 支持多种操作方式:sql,API编程
- 支持多种外部数据源:Rarquet,JSON,RDBMS等
Spark SQL运行原理
Catalyst优化器是Spark SQL的核心
Catalyst Optimizer:Catalyst优化器,将逻辑计划转为物理计划
优化器
- 逻辑计划
- 优化
- 物理计划
SparkSQL API
- SparkContext
- SQLContext:Spark SQL的编程入口
- HiveContext:SQLContext的子集,包含更多功能
- SparkSession,SparkSession合并了SQLContext与HiveContext,提供与Spark功能交互单一入口点,并允许使用DataFrame和Dataset API对Spark进行编程
Dataset
特定域对象中的强类型集合
DataFrame
DataFrame=Dataset[Row]
类似传统数据的二维表格
在RDD基础上加入了Schema(数据结构信息)
DataFrame Schema支持嵌套数据类型:struct、map、array
提供更多类似SQL操作的API
SparkSQL操作外部数据源
SparkSQL支持的外部数据源
Parquet文件
Parquet文件是一种流行的列式存储格式,以二进制存储,文件中包含数据与元数据
Hive表
SparkSQL与Hive的集成
- hive-site.xml拷贝至${SPARK_HOME}/conf下
- 检查hive.metastore.uris是否正确
- 启动元数据服务:$hive service metastore
- 自行创建SparkSession,应用配置仓库地址与启用hive支持
RDBMS表
SparkSQL函数
内置函数
自定义函数
Spark性能优化
序列化
- Java序列化,Spark的默认序列化方式
- Kryo序列化,比Java序列化快约10倍,但不支持所有可序列化类型
- 使用对象数组、原始类型代替Java、Scala集合类
- 避免嵌套结构
- 尽量使用数字作为Key,而非字符串
- 以较大的RDD使用MEMORY_ONLY_SER
- 加载CSV、JSON时,仅加载所需字段
- 仅在需要时持久化中间结果(RDD/DS/DF)
- 避免不必要的中间结果(RDD/DS/DF)的生成
- DF的执行速度比DS快约3倍
- 自定义RDD分区与spark.default.parallelism,该参数用于设置每个stage的默认task数量
- 将大变量广播出去,而不是直接使用
- 尝试处理本地数据并最小化跨工作节点的数据传输
- 表连接(join操作),包含所有的谓词(predicate),最大的表放在第一位,广播最小的表,最小化表join的数量
SparkGraphX图形数据分析
为什么需要图计算:
- 许多大数据以大规模图或网络的形式呈现
- 许多非图结构的大数据,常会被转换为图模型进行分析
- 图数据结构很好的表达了数据之间的关联性
图的基本概念
- 图是由顶点集合vertex及顶点间饿关系集合,边edge组成的一种网状数据结构,通常表示为二元组:Gragh=(V, E),可以对事物之间的关系建模
- 应用场景,在地图应用总找最短路径,社交网络关系,网页间超链接关系
图的术语: - 顶点 Vertex
- 边 Edge
- 有向图:
- 无向图:
- 有环图:包含一系列顶点连接的回路(环路)
- 无环图:DAG即为有向无环图
- 度:一个顶点所有边的数量,
出度:指从当前顶点指向其他顶点的边的数量;
入度:其他顶点指向当前顶点的边的数量; - 图的经典表示方法
邻接矩阵 - 1、对于每条边,矩阵中相应的单元格值为1
2、对于每个循环,矩阵中相应单元格值为2,方便在行或列上求得顶点度数
GraphX核心抽象
弹性分布式属性图(Resilient Distributed Property Graph):
- 顶点和边都带属性的有向多重图
- 一份物理存储,两种视图
GraphX API
- Graph[VD,ED]
- VertexRDD[VD]
- EdgeRDD[ED]
- EdgeTriplet[VD,ED]
- Edge:样例类
- VertexId: Long的别名
pergel函数