前言
1.什么是Spark
Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的类Hadoop MapReduce的通用并行计算框架,Spark拥有Hadoop MapReduce所具有的优点;但不同于MapReduce的是Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法。
Spark是Scala编写,方便快速编程。
处理场景:批处理(spark core)、流处理(spark steaming)、即席查询(spark SQL),后期会往深度学习发展。
2.总体技术栈讲解
底层向上:资源调度框架(YARN)->分布式文件系统(HDFS,基于磁盘存储数据,单位是Block,3个副本备份策略:先本机架,再出机架)->基于内存的文件系统(Tochyon)->Hadoop MR(数据节点之间传输,map到reduce端的过程就是shuffle) ->hive(元数据存储在MYSQL,Derby不支持多用户存储连接;数据仓库)->storm(流式处理框架)->spark steaming ->spark -> BlinkDB(分布式,精确度查询)
3.Spark演变历史
- 发展尤为迅速:2012年开始
- 官网:http://spark.apache.org/
4.Spark与MapReduce的区别
都是分布式计算框架,Spark基于内存(从内存到磁盘),MR基于HDFS(从磁盘到磁盘的过程)。Spark处理数据的能力一般是MR的十倍以上,Spark中除了基于内存计算外,还有DAG有向无环图来切分任务的执行先后顺序。替换不了Hadoop,没有HDFS存储。
5.Spark运行模式
- Local
多用于本地测试,如在eclipse,idea中写程序测试等。 - Standalone
Standalone是Spark自带的一个资源调度框架,它支持完全分布式。 - Yarn
Hadoop生态圈里面的一个资源调度框架,Spark也是可以基于Yarn来计算的。 - Mesos:资源调度框架。
要基于Yarn来进行资源调度,必须实现AppalicationMaster接口,Spark实现了这个接口,所以可以基于Yarn。
6.SparkCore
(1)RDD
- 概念: RDD(Resilient Distributed Dateset),弹性分布式数据集。
- RDD的五大特性:
1.RDD是由一系列的partition组成的(partition的个数跟block块个数一致)。
2.算子(函数)是作用在每一个partition(split)上的。
3.RDD之间有一系列的依赖关系(RDD2丢失了,RDD2依赖RDD1可找回)。
4.分区器(决定数据去往哪个分区)是作用在K,V格式的RDD上。
5.RDD中partition提供一系列最佳的计算位置,体现数据处理的本地化。体现了大数据中“计算移动数据不移动”的理念。 - RDD理解图:
- 注意:
1.sc.textFile方法底层封装的是读取MR读取HDFS文件的方式,读取文件之前先split,默认split大小是一个block大小相同,这个split对应RDD的一个partition。
2.RDD实际上不存储数据,只是存储逻辑,这里方便理解,暂时理解为存储数据。
3.什么是K,V格式的RDD?
如果RDD里面存储的数据都是二元组对象(Tuple2),那么这个RDD我们就叫做K,V格式的RDD。
4.哪里体现RDD的弹性(容错)?
1)partition数量,大小没有限制,可多可少,体现了RDD的弹性。
2)RDD之间依赖关系,可以基于上一个RDD重新计算出RDD。
5.哪里体现RDD的分布式?
RDD是由Partition组成,partition是分布在不同(多个)节点上的。
(2)Spark任务执行原理
以上图中有四个机器节点,Driver和Worker是启动在节点上的进程,运行在JVM中的进程。
- Driver进程向Worker节点发送tasks,RAM是内存,也就是spark基于内存计算数据,并返回结果给Driver。
- Driver负责任务(tasks)的分发和结果的回收。任务的调度。如果task的计算结果非常大就不要回收了。会造成oom(内存溢出的风险)。
- Worker是Standalone资源调度框架里面资源管理的从节点。也是JVM进程。
- Master是Standalone资源调度框架里面资源管理的主节点。也是JVM进程。
(3)Spark代码流程
1.创建SparkConf对象
- 可以设置Application name。
- 可以设置运行模式及资源需求。
2.创建SparkContext对象
3.基于Spark的上下文创建一个RDD,对RDD进行处理。
4.应用程序中要有Action类算子来触发Transformation类算子执行。(只有执行这一步,才会返回结果)
5.关闭Spark上下文对象SparkContext。
(4)Transformations转换算子
- 概念:
Transformations类算子是一类算子(函数)叫做转换算子,如map,flatMap,reduceByKey等。Transformations算子是延迟执行,也叫懒加载执行。
Transformation类算子: - filter
过滤符合条件的记录数,true保留,false过滤掉。 - map
将一个RDD中的每个数据项,通过map中的函数映射变为一个新的元素。
特点:输入一条,输出一条数据。 - flatMap
先map后flat。与map类似,每个输入项可以映射为0到多个输出项。 - sample
随机抽样算子,根据传进去的小数按比例进行又放回或者无放回的抽样。
JavaRDD lines.sample(true, 0.1, 100) //有放回,十条数据左右,100条种子 - reduceByKey
将相同的Key根据相应的逻辑进行处理。 - sortByKey/sortBy
作用在K,V格式的RDD上,对key进行升序或者降序排序。
val conf = new SparkConf().setMaster("test").setAppName("local")
val sc = new SparkContext(conf)
val lines = sc.textFile("./words")
val reduceResult = lines.flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_)
reduceResult.map(_.swap).sortByKey(false).map(_.swap).foreach(println)
(5)Action行动算子
- 概念:
Action类算子也是一类算子(函数)叫做行动算子,如foreach,collect,count等。Transformations类算子是延迟执行,Action类算子是触发执行。一个application应用程序中有几个Action类算子执行,就有几个job运行。
Action类算子: - count
返回数据集中的元素数。会在结果计算完成后回收到Driver端。 - take(n)
返回一个包含数据集前n个元素的集合。 - first
first=take(1),返回数据集中的第一个元素。 - foreach
循环遍历数据集中的每个元素,运行相应的逻辑。 - collect
将计算结果回收到Driver端。
实现一千万条数据量的文件,过滤掉出现次数多的记录,并且其余记录按照出现次数降序排序。
import java.util.Arrays;
import java.util.List;
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 org.apache.spark.api.java.function.FlatMapFunction;
import org.apache.spark.api.java.function.Function;
import org.apache.spark.api.java.function.Function2;
import org.apache.spark.api.java.function.PairFunction;
import org.apache.spark.api.java.function.VoidFunction;
import scala.Tuple2;
/**
* 动态统计出现次数最多的单词个数,过滤掉。
*/
public class Demo1 {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setMaster("local").setAppName("demo1");
JavaSparkContext jsc = new JavaSparkContext(conf);
JavaRDD<String> lines = jsc.textFile("./records.txt");
JavaRDD<String> flatMap = lines.flatMap(new FlatMapFunction<String, String>() {
private static final long serialVersionUID = 1L;
@Override
public Iterable<String> call(String t) throws Exception {
return Arrays.asList(t.split(" "));
}
});
JavaPairRDD<String, Integer> mapToPair = flatMap.mapToPair(new PairFunction<String,String, Integer>() {
private static final long serialVersionUID = 1L;
@Override
public Tuple2<String, Integer> call(String t) throws Exception {
return new Tuple2<String, Integer>(t, 1);
}
});
JavaPairRDD<String, Integer> sample = mapToPair.sample(true, 0.5);
final List<Tuple2<String, Integer>> take = sample.reduceByKey(new Function2<Integer,Integer,Integer>(){
private static final long serialVersionUID = 1L;
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1+v2;
}
}).mapToPair(new PairFunction<Tuple2<String,Integer>, Integer, String>() {
private static final long serialVersionUID = 1L;
@Override
public Tuple2<Integer, String> call(Tuple2<String, Integer> t)
throws Exception {
return new Tuple2<Integer, String>(t._2, t._1);
}
}).sortByKey(false).mapToPair(new PairFunction<Tuple2<Integer,String>, String, Integer>() {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public Tuple2<String, Integer> call(Tuple2<Integer, String> t)
throws Exception {
return new Tuple2<String, Integer>(t._2, t._1);
}
}).take(1);
System.out.println("take--------"+take);
JavaPairRDD<String, Integer> result = mapToPair.filter(new Function<Tuple2<String,Integer>, Boolean>() {
private static final long serialVersionUID = 1L;
@Override
public Boolean call(Tuple2<String, Integer> v1) throws Exception {
return !v1._1.equals(take.get(0)._1);
}
}).reduceByKey(new Function2<Integer,Integer,Integer>(){
private static final long serialVersionUID = 1L;
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1+v2;
}
}).mapToPair(new PairFunction<Tuple2<String,Integer>, Integer, String>() {
private static final long serialVersionUID = 1L;
@Override
public Tuple2<Integer, String> call(Tuple2<String, Integer> t)
throws Exception {
return new Tuple2<Integer, String>(t._2, t._1);
}
}).sortByKey(false).mapToPair(new PairFunction<Tuple2<Integer,String>, String, Integer>() {
private static final long serialVersionUID = 1L;
@Override
public Tuple2<String, Integer> call(Tuple2<Integer, String> t)
throws Exception {
return new Tuple2<String, Integer>(t._2, t._1);
}
});
result.foreach(new VoidFunction<Tuple2<String,Integer>>() {
private static final long serialVersionUID = 1L;
@Override
public void call(Tuple2<String, Integer> t) throws Exception {
System.out.println(t);
}
});
jsc.stop();
}
}
(6)控制算子
- 概念:
控制算子有三种,cache,persist,checkpoint,以上算子都可以将RDD持久化,持久化的单位是partition。cache和persist都是懒执行的。必须有一个action类算子触发执行。checkpoint算子不仅能将RDD持久化到磁盘,还能切断RDD之间的依赖关系。 - cache
默认将RDD的数据持久化到内存中。cache是懒执行。
注意:cache () = persist()=persist(StorageLevel.Memory_Only) - 测试cache文件:
文件:见“NASA_access_log_Aug95”文件。
测试代码:
SparkConf conf = new SparkConf();
conf.setMaster("local").setAppName("CacheTest");
JavaSparkContext jsc = new JavaSparkContext(conf);
JavaRDD<String> lines = jsc.textFile("./NASA_access_log_Aug95");
lines = lines.cache();
long startTime = System.currentTimeMillis();
long count = lines.count();
long endTime = System.currentTimeMillis();
System.out.println("共"+count+ "条数据,"+"初始化时间+cache时间+计算时间="+(endTime-startTime));
long countStartTime = System.currentTimeMillis();
long countrResult = lines.count();
long countEndTime = System.currentTimeMillis();
System.out.println("共"+countrResult+ "条数据,"+"计算时间="+ (countEndTime-countStartTime));
jsc.stop();
//第二次时间减少
- persist:
可以指定持久化的级别。最常用的是MEMORY_ONLY(只放在内存中)和MEMORY_AND_DISK。”_2”表示有副本数。
持久化级别如下: - cache和persist的注意事项:
1.cache和persist都是懒执行,必须有一个action类算子触发执行。
2.cache和persist算子的返回值可以赋值给一个变量,在其他job中直接使用这个变量就是使用持久化的数据了。持久化的单位是partition。
3.cache和persist算子后不能立即紧跟action算子。
错误:rdd.cache().count() 返回的不是持久化的RDD,而是一个数值了。
checkpoint
checkpoint将RDD持久化到磁盘,还可以切断RDD之间的依赖关系。
- checkpoint 的执行原理:
1.当RDD的job执行完毕后,会从finalRDD从后往前回溯。
2.当回溯到某一个RDD调用了checkpoint方法,会对当前的RDD做一个标记。
3.Spark框架会自动启动一个新的job,重新计算这个RDD的数据,将数据持久化到HDFS上。 - 优化:对RDD执行checkpoint之前,最好对这个RDD先执行cache,这样新启动的job只需要将内存中的数据拷贝到HDFS上就可以,省去了重新计算这一步。
- 使用:
checkpoint需要设置checkpoint目录,sc.setCheckpointDir(…);rdd.checkpoint();
SparkConf conf = new SparkConf();
conf.setMaster("local").setAppName("checkpoint");
JavaSparkContext sc = new JavaSparkContext(conf);
sc.setCheckpointDir("./checkpoint");
JavaRDD<Integer> parallelize = sc.parallelize(Arrays.asList(1,2,3));
parallelize.checkpoint();
parallelize.count();
sc.stop();
3.集群搭建以及测试
1.搭建
- Standalone
1).下载安装包:Spark Download,解压 :
2).改名
3).进入安装包的conf目录下,修改slaves.template文件,添加从节点。保存。
4).修改spark-env.sh
SPARK_MASTER_IP:master的ip
SPARK_MASTER_PORT:提交任务的端口,默认是7077
SPARK_WORKER_CORES:每个worker从节点能够支配的core的个数
SPARK_WORKER_MEMORY:每个worker从节点能够支配的内存数
5).同步到其他节点上
6).启动集群
进入sbin目录下,执行当前目录下的./start-all.sh
7).搭建客户端
将spark安装包原封不动的拷贝到一个新的节点上,然后,在新的节点上提交任务即可。
注意:
- 8080是Spark WEBUI界面的端口,7077是Spark任务提交的端口。
- 修改master的WEBUI端口:
1.修改start-master.sh即可。
2.也可以在Master节点上导入临时环境变量,只是作用于之后的程序,重启就无效了。
删除临时环境变量: - yarn
1). 1,2,3,4,5,7步同standalone。
2).在客户端中配置:
案例: - Standalone提交命令:
./spark-submit
--master spark://node1:7077
--class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 10000
- YARN提交命令:
./spark-submit
--master yarn
--class org.apache.spark.examples.SparkPi ../lib/spark-examples-1.6.0-hadoop2.6.0.jar 10000