Spark
基本概念
- Spark是类MapReduce的通用并行计算框架,Spark拥有MR所具有的有点,但不同于R的是Job中间的输出结果可以保存到内存中,从而不再需要读写HDFS,因此Spark能更好的适用于数据挖掘于机器学习等需要迭代的MR的算法。
- 分布式计算框架
- 基于内存
- 由Scala开发,便于快速开发
- 微批次(Spark Core),准实时,流式处理(Spark Streaming),即席查询(Spark SQL)
运行模式
- Local(多用于测试)
- Standalone
- YARN(Yarn作为资源调度)
- Mesos
SparkCore
RDD
概念
- Resilient Distributed Dataset 弹性分布式数据集,最小单位partition(分区)
- shuffle:父RDD指向多个子RDD partition
- 宽依赖:父RDD与子RDD partition之间的关系是一对多(有shuffle产生)
- 窄依赖:父RDD与子RDD partition之间的关系是一对一/多对一
- DAG:有向无环图,按宽窄依赖切分stage(一组并行的Task组成)
- 推测执行:如果有任务执行特别缓慢,则会重新启动一个相同的task,哪个先执行完,则以执行完的结果为准,如果对于ETL程序,关闭推测执行,会造成数据重复写入。如果数据倾斜并开启了推测执行(spark.speclation,默认关闭false),则程序会一直执行下去。
- 数据倾斜:HDFS中某block数据量大,其他的小,RDD中某个partition数据量大,其余的数据量小。
五大特性
- RDD是由一系列的partition组成
- 算子是作用在Partiitons上的
- RDD之间有依赖关系(宽窄依赖)
- 分区器是作用在K,V(Tuple)格式的RDD上的(分区器:在有shuffle的时候产生)
- partition提供一系列的最佳计算位置,利于task数据处理本地化(计算向数据移动)
Q&A
- 哪里体现了RDD的弹性(容错)?
- RDD之间有依赖关系
- partition的个数可多可少
- 哪里体现了RDD 的分布式?
- partition 是分布在集群节点中的
- Spark中的textFile底层调用的多是MR读取HDFS的方法,当读取文件的时候首先Split(128MB)
- RDD中实际上不存数据的。
Lineage
概念
- 血缘,一系列的RDD依赖关系形成的
1. Master(Standalone): 资源管理主节点(进程)
2. Cluster Manager: 在集群上获取资源的外部服务(例:standalone,Mesos,Yarn)
3. Worker Node: 资源管理的从节点(进程)或者说是管理本机资源的进程
4. Application: 基于Spark的用户程序,包含了driver程序和运行在集群上的Executor程序
5. Driver Program: 用来连接工作进程(Worker)的程序
6. Executor: 是在一个Worker进程所管理的节点上为某个Application启动的一个进程,该进程负责运行任务,并且负责将数据存在内存或磁盘上,每个应用都有各自独立的Executor`
7. Task: 被送到某个Executor上的工作单元
8. Job: 包含很多任务(Task)的并行计算,每个Action算子触发一个Job
9. Stage: 一个Job会被拆分成很多组任务,每组任务被成为Stage(一组并行的task)。
(Yarn相关)
10. Resource Manager:
11. Node Manager:
Spark任务执行流程
解释:
一个Spark程序启动,则对应一个Jvm进程
Drive和Worker之间的通信(发送task,回收结果,Driver和Worker都是节点上的进程)
Master是资源调度的主节点,Worker是资源管理的从节点。
回收的结果放在Jvm内存中,有OOM风险
算子
Transformation算子及代码
Transformation 类算子,延迟执行,要用Action类算子出发执行 RDD => RDD
filter
object TestSpark {
def main(args: Array[String]): Unit = {
/**
* conf 可以配置application运行的参数
* 1.指定运行模式
* 2.指定appName
* 3.指定程序运行时的运行参数
*/
val conf = new SparkConf()
conf.setMaster("local").setAppName("test")
/**
* SparkContext 是通往集群的唯一通道
*/
val sc = new SparkContext(conf)
val lines = sc.textFile("./score")
//filter,true的会留下,false的过滤掉
val result = lines.filter(line =>{
line.contains("liming")
})
result.foreach(print)
sc.stop()
}
}
flatMap
scala
object TestSpark {
def main(args: Array[String]): Unit = {
/**
* conf 可以配置application运行的参数
* 1.指定运行模式
* 2.指定appName
* 3.指定程序运行时的运行参数
*/
val conf = new SparkConf()
conf.setMaster("local").setAppName("test")
/**
* SparkContext 是通往集群的唯一通道
*/
val sc = new SparkContext(conf)
val lines = sc.textFile("./score")
//flatMap 按行读取
val words = lines.flatMap({
_.split(",")
})
words.foreach(println(_))
//println(words.conut())
sc.stop()
}
}
java
import org.apache.spark.SparkConf;
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.VoidFunction;
import java.util.Arrays;
import java.util.Iterator;
public class TestJavaSpark {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setMaster("local[4]").setAppName("test");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> stringJavaRDD = sc.textFile("./score");
JavaRDD<String> words = stringJavaRDD.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String line) throws Exception {
return Arrays.asList(line.split(",")).iterator();
}
});
words.foreach(new VoidFunction<String>() {
@Override
public void call(String s) throws Exception {
System.out.println(s);
}
});
//sc.close();
sc.stop();
}
}
map
scala
object TestSpark {
def main(args: Array[String]): Unit = {
/**
* conf 可以配置application运行的参数
* 1.指定运行模式
* 2.指定appName
* 3.指定程序运行时的运行参数
*/
val conf = new SparkConf()
conf.setMaster("local").setAppName("test")
/**
* SparkContext 是通往集群的唯一通道
*/
val sc = new SparkContext(conf)
val lines = sc.textFile("./score")
val words = lines.flatMap({
_.split(",")
})
//一进一出
val result = words.map(word =>{
new Tuple2(word, 1)
})
result.foreach(println(_))
sc.stop()
}
}
运行结果:(word count)
(1,1)
(liming,1)
(100,1)
(2,1)
(zhangsan,1)
(200,1)
(3,1)
(lisi,1)
(300,1)
java
public class TestJavaSpark {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setMaster("local[4]").setAppName("test");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> stringJavaRDD = sc.textFile("./score");
JavaRDD<String> words = stringJavaRDD.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String line) throws Exception {
return Arrays.asList(line.split(",")).iterator();
}
});
/**
* 参数解析
* 1.第一个String,输入的类型,对应call中的类型
* 2.第二个String,Integer 返回值类型,对应Tuple中的返回值类型
*
*/
JavaPairRDD<String, Integer> word = words.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
word.foreach(new VoidFunction<Tuple2<String, Integer>>() {
@Override
public void call(Tuple2<String, Integer> stringIntegerTuple2) throws Exception {
System.out.println(stringIntegerTuple2);
}
});
//sc.close();
sc.stop();
}
}
运行结果:
(1,1)
(1,1)
(liming,1)
(liming,1)
(100,1)
(100,1)
(2,1)
(2,1)
(zhangsan,1)
(zhangsan,1)
(200,1)
(200,1)
(3,1)
(lisi,1)
(3,1)
(300,1)
(lisi,1)
(300,1)
reduceByKey
Scala WordCount
object TestSpark {
def main(args: Array[String]): Unit = {
/**
* conf 可以配置application运行的参数
* 1.指定运行模式
* 2.指定appName
* 3.指定程序运行时的运行参数
*/
val conf = new SparkConf()
conf.setMaster("local").setAppName("test")
/**
* SparkContext 是通往集群的唯一通道
*/
val sc = new SparkContext(conf)
val lines = sc.textFile("./score")
val words = lines.flatMap({
_.split(",")
})
val result = words.map(word =>{
Tuple2(word, 1)
})
//sortBy(_._2)按出现频率排序
//sortByKey()按key字典序排序 = sortBy(_._1)
//result.map(tuple =>{
// Tuple2(tuple._2, tuple._1)
//}).map(tuple =>{
// Tuple2(tuple._2, tuple._1)
//}) = sortBy(_._2)
result.reduceByKey((a: Int, b: Int) =>{
a + b
}).sortByKey().foreach(x => {
println( s"${x._1} appears ${x._2} times")
})
sc.stop()
}
}
运行结果:
1 appears 1 times
100 appears 1 times
2 appears 1 times
200 appears 1 times
3 appears 1 times
300 appears 1 times
liming appears 1 times
lisi appears 1 times
zhangsan appears 1 times
Java Wordcount
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.*;
import scala.Tuple2;
import java.util.Arrays;
import java.util.Iterator;
public class TestJavaSpark {
public static void main(String[] args) {
SparkConf conf = new SparkConf();
conf.setMaster("local[4]").setAppName("test");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> stringJavaRDD = sc.textFile("./score");
JavaRDD<String> words = stringJavaRDD.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String line) throws Exception {
return Arrays.asList(line.split(",")).iterator();
}
});
/**
* 参数解析
* 1.第一个String,输入的类型,对应call中的类型
* 2.第二个String,Integer 返回值类型,对应Tuple中的返回值类型
*
*/
JavaPairRDD<String, Integer> pairRDD = words.mapToPair(new PairFunction<String, String, Integer>() {
@Override
public Tuple2<String, Integer> call(String s) throws Exception {
return new Tuple2<>(s, 1);
}
});
/**
* 参数解析
*
*
*/
JavaPairRDD<String, Integer> reduceRDD = pairRDD.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1 + v2;
}
});
reduceRDD.sortByKey().foreach(new VoidFunction<Tuple2<String, Integer>>() {
@Override
public void call(Tuple2<String, Integer> stringIntegerTuple2) throws Exception {
System.out.println(stringIntegerTuple2._1 + " appears " + stringIntegerTuple2._2+" times ");
}
});
//sc.close();
sc.stop();
}
}
运行结果:
300 appears 2 times
1 appears 2 times
liming appears 2 times
100 appears 2 times
lisi appears 2 times
2 appears 2 times
zhangsan appears 2 times
200 appears 2 times
3 appears 2 times
sample
抽样
object TestSpark {
def main(args: Array[String]): Unit = {
/**
* conf 可以配置application运行的参数
* 1.指定运行模式
* 2.指定appName
* 3.指定程序运行时的运行参数
*/
val conf = new SparkConf()
conf.setMaster("local").setAppName("test")
/**
* SparkContext 是通往集群的唯一通道
*/
val sc = new SparkContext(conf)
val lines = sc.textFile("./score")
/**
* 参数
* 1.true 有放回的抽取,false 无放回的抽取
* 2.抽样比例
* 3.seed for the random number generator,同一批数据,运行结果一样
*/
lines.sample(true,0.2)
sc.stop()
}
}
Action算子
Action算子出发转换算子执行,有一个Action算子,这个Application就有一个job RDD=>else
take
object TestSpark {
def main(args: Array[String]): Unit = {
/**
* conf 可以配置application运行的参数
* 1.指定运行模式
* 2.指定appName
* 3.指定程序运行时的运行参数
*/
val conf = new SparkConf()
conf.setMaster("local").setAppName("test")
/**
* SparkContext 是通往集群的唯一通道
*/
val sc = new SparkContext(conf)
val lines = sc.textFile("./score")
//返回数组
val strings = lines.take(2)
for(elem <- strings){
println(elem)
}
sc.stop()
}
}
first
object TestSpark {
def main(args: Array[String]): Unit = {
/**
* conf 可以配置application运行的参数
* 1.指定运行模式
* 2.指定appName
* 3.指定程序运行时的运行参数
*/
val conf = new SparkConf()
conf.setMaster("local").setAppName("test")
/**
* SparkContext 是通往集群的唯一通道
*/
val sc = new SparkContext(conf)
val lines = sc.textFile("./score")
val strings = lines.first()
println(strings)
sc.stop()
}
}
collect
数据量大的时候不建议使用。
object TestSpark {
def main(args: Array[String]): Unit = {
/**
* conf 可以配置application运行的参数
* 1.指定运行模式
* 2.指定appName
* 3.指定程序运行时的运行参数
*/
val conf = new SparkConf()
conf.setMaster("local").setAppName("test")
/**
* SparkContext 是通往集群的唯一通道
*/
val sc = new SparkContext(conf)
val lines = sc.textFile("./score")
//将结果拉回到Driver端
val strings = lines.collect()
for(elem <- strings){
println(elem)
}
sc.stop()
}
}
运行结果:
1,liming,100
2,zhangsan,200
3,lisi,300
持久化算子
- cache(懒执行,保存到内存中,持久化的单位是partition)
- persist(懒执行,持久化的单位是partition)
- checkpoint
cache
public class TestCache {
public static void main(String[] args) throws MalformedURLException {
SparkConf conf = new SparkConf();
conf.setMaster("local[4]").setAppName("test");
JavaSparkContext jsc = new JavaSparkContext(conf);
JavaRDD<String> text = jsc.textFile("./data-test-4.txt");
/**
*缓存需要action算子才能触发
*/
JavaRDD<String> cache = text.cache();
long begin = System.currentTimeMillis();
long count = cache.count();
long end = System.currentTimeMillis();
System.out.println( "count is " + count + ",time is :" + (end - begin));
//JavaRDD<String> cache = text.cache();//如果放在这里则两次执行时间相近,则证明是需要触发执行的
long begin1 = System.currentTimeMillis();
long count1 = cache.count();
long end1 = System.currentTimeMillis();
System.out.println("count1 is " + count1 +", time2 is :" + (end1 - begin1));
jsc.stop();
}
}
运行结果:
count is 128000,time is :2576
count1 is 128000, time2 is :74
persist
可以指定持久化的级别
public class TestCache {
public static void main(String[] args) throws MalformedURLException {
SparkConf conf = new SparkConf();
conf.setMaster("local[4]").setAppName("test");
JavaSparkContext jsc = new JavaSparkContext(conf);
JavaRDD<String> text = jsc.textFile("E:\\A-2018-05-04\\streamingwork\\data-test-4.txt");
// JavaRDD<String> cache = text.cache();
/**
* 懒执行
* class StorageLevel private(
* private var _useDisk: Boolean,
* private var _useMemory: Boolean,
* private var _useOffHeap: Boolean,
* private var _deseriaLized: Boolean,//不序列化
* private var _replication: Int = 1,
* )
*
* 常用级别,val MEMORY_ONLY = new StorageLevel(false, true, false, true)
* val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
*/
JavaRDD<String> persist = text.persist(new StorageLevel());
//JavaRDD<String> persist = text.persist(StorageLevel.MEMORY_ONLY());
long begin = System.currentTimeMillis();
long count = persist.count();
long end = System.currentTimeMillis();
System.out.println( "count is " + count + ",time is :" + (end - begin));
// JavaRDD<String> persist = text.persist(new StorageLevel());
long begin1 = System.currentTimeMillis();
long count1 = persist.count();
long end1 = System.currentTimeMillis();
System.out.println("count1 is " + count1 +", time2 is :" + (end1 - begin1));
jsc.stop();
}
}
运行结果:
count is 128000,time is :1943
count1 is 128000, time2 is :178
checkpoint
可以将RDD持久化到磁盘,还可以切断RDD之间的依赖关系
public class TestCache {
public static void main(String[] args) throws MalformedURLException {
SparkConf conf = new SparkConf();
conf.setMaster("local[4]").setAppName("test");
JavaSparkContext jsc = new JavaSparkContext(conf);
JavaRDD<String> text = jsc.textFile("E:\\A-2018-05-04\\streamingwork\\data-test-4.txt");
//设置目录
jsc.setCheckpointDir("./checkpointDir");
//缓存优化,因为checkpoint是在lineage执行完之后重新启动一个job,追溯到相应节点之后持久化
//cache之后会加快执行速度
text.cache();
text.checkpoint();
text.collect();
jsc.stop();
}
}
广播变量
val br = sc.broadcast(value)
使用:val brList = br.value
1.会将变量广播到每个Executor端中,每个Executor中单独有一份(不同的jvm进程)。
2.广播变量不能广播RDD(SparkStreaming中有算子可以),因为RDD中不存储数据。想广播RDD,可以广播rdd.collect()
3.只能在Driver端定义、修改,在Executor端使用,不能再Executor端改变广播变量的值,会造成线程不安全
4.Java中定义广播变量要定义为final(匿名内部类中使用变量)
累加器
在Driver端定义变量,Executor累加计数,最后在Driver端输出的变量还是初始定义的值,所以就引进了累加器,累加器会更新Driver端的变量值。
//伪代码
val rdd = sc.textFile("")
val sum = 0
rdd.map({
sum += 1
}).foreach{...}
println( " sum = " + sum )
运行结果:
sum = 0
--------------------------line one-----------------------------
累加器:
val rdd = sc.mkrdd(Array("", "", ""))
val accumulator = sc.accumulator(0)
rdd.map({
accumulator.add(1)
.....
}).collect()
println ("accumulator = " + accumulator.value )
//只能在Driver端定义,在Executor端使用,不能再Executor端取值(accumulator.value)
//但是可以直接打印 accumulator 对象
-----------------------------line two-------------------------------------
Demo:
import org.apache.spark.{SparkConf, SparkContext}
object ScalaWC {
def main(args: Array[String]): Unit = {
val conf = new SparkConf()
conf.setMaster("local[4]").setAppName("wc")
val sc = new SparkContext(conf)
//累加器
val value = sc.accumulator(0)
val list = List[String]("234")
/** 广播变量
* 不能广播rdd(SparkStreaming可以),因为rdd不存数据,方法:sc.broadcast(rdd.collect())
* 广播变量只能在driver端定义,在Executor端使用。
* Driver端可以修改广播变量的值,Executor端无法修改
*/
val br = sc.broadcast(list)
val rdd = sc.makeRDD(Array("123", "234", "345"))
//获取值 br.value(),不同的Executor间不能共用一个广播变量,都是单独的 n Executor = n broadcast
rdd.filter({
value.add(1)
!br.value.contains(_)}).foreach({
println("value = " + value)
println(_)})
sc.stop()
}
}
Spark shell
只支持Scala,能进行简单的开发。
Spark shuffle
磁盘小文件,OOM,频繁GC(频繁GC引发full GC,整个程序等待GC完成)
shuffle write
上一个stage的每个map task 必须保证自己处理的当期那分区中的数据相同的key写入同一个相同的分区文件中,可能会写入多个不同的分区文件中
shuffle read
reduce task 或从上一个stage的所有task所在的机器上寻找属于自己的分区文件,这样就可以保证每个key所对应的value都会汇聚到同一个节点上去处理和聚合
hash shuffle
默认分区器,HashPartitioner
sort shuffle
默认分区器RangePartitioner