0802-SparkCore
- 手写WordCount
- 第一章 RDD概述
- 第二章 RDD编程
- 2.0 编程模型
- 2.1 获取SparkContext
- 2.2 RDD的创建
- 2.3 RDD算子之转换算子
- 2.3.1 value 类型
- 2.3.1.1 map(func)
- 2.3.1.2 mapPartition(func)
- 2.3.1.3 mapPartitionWithIndex(func)
- 2.3.1.4 flatMap(func)
- 2.3.1.5 glom()
- 2.3.1.6 groupBy(func)
- 2.3.1.7 filter(func)
- 2.3.1.8 sample(withReplacement, fraction, seed)
- 2.3.1.9 distinct([numTasks])
- 2.3.1.10 coalesce(numPartitions)
- 2.3.1.11 repartition(numPartitions)
- 2.3.1.12 sortBy(func,[ascending], [numTasks])
- 2.3.1.13 pipe(command, [envVars])
- 2.3.2 双value类型
- 2.3.3 Key-value类型
- 2.3.3.1 partitionBy(partitioner: Partitioner)
- 2.3.3.2 groupByKey()
- 2.3.3.3 reduceByKey(func, [numTasks])
- 2.3.3.4 aggregateByKey(zeroValue,[partitioner]) (seqOp,combOp)
- 2.3.3.5 foldByKey(zeroValue)(func)
- 2.3.3.6 combineByKey(createCombiner, mergeValue, mergeCombiners)
- 2.3.3.7 sortByKey([ascending], [numTasks])
- 2.3.3.8 mapValues(func)
- 2.3.3.9 join(otherDataset, [numTasks])
- 2.3.3.10 cogroup(otherDataset, [numTasks])
- 2.3.4 案例实操
- 2.4 RDD算子之行动算子
- 2.5 RDD中的函数传递(Serializable)
- 2.6 RDD依赖关系
- 2.7 RDD缓存
- 2.8 RDD CheckPoint
- 第三章 键值对RDD数据分区器
- 第四章 数据读取与保存
- 第五章 RDD编程进阶
- 第六章 RDD相关概率关系
手写WordCount
0.1 Java
SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("wordcount");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> lines = sc.textFile("pom.xml");
JavaRDD<String> words = lines.flatMap(new FlatMapFunction<String, String>() {
@Override
public Iterator<String> call(String s) throws Exception {
return Arrays.asList(s.split("\t")).iterator();
}
});
JavaPairRDD<String, Integer> wordAndOne = 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> reduced = wordAndOne.reduceByKey(new Function2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer v1, Integer v2) throws Exception {
return v1 + v2;
}
});
JavaPairRDD<Integer, String> swapped = reduced.mapToPair(new PairFunction<Tuple2<String, Integer>, Integer, String>() {
@Override
public Tuple2<Integer, String> call(Tuple2<String, Integer> tuple) throws Exception {
return tuple.swap();
}
});
JavaPairRDD<Integer, String> sorted = swapped.sortByKey(false);
JavaPairRDD<String, Integer> result = sorted.mapToPair(new PairFunction<Tuple2<Integer, String>, String, Integer>() {
@Override
public Tuple2<String, Integer> call(Tuple2<Integer, String> tuple) throws Exception {
return tuple.swap();
}
});
result.saveAsTextFile("2.txt");
sc.stop();
0.2 Java By Lambda
SparkConf conf = new SparkConf().setMaster("local[*]").setAppName("wordcount");
JavaSparkContext sc = new JavaSparkContext(conf);
JavaRDD<String> lines = sc.textFile("pom.xml");
JavaRDD<String> words = lines.flatMap(x -> Arrays.asList(x.split("\t")).iterator());
JavaPairRDD<String, Integer> wordAndOne = words.mapToPair(x -> new Tuple2<>(x, 1));
JavaPairRDD<String, Integer> reduced = wordAndOne.reduceByKey((x, y) -> x + y);
JavaPairRDD<Integer, String> swapped = reduced.mapToPair(tuple -> tuple.swap());
JavaPairRDD<Integer, String> sorted = swapped.sortByKey(false);
JavaPairRDD<String, Integer> result = sorted.mapToPair(tuple -> tuple.swap());
result.saveAsTextFile("result");
0.3 Scala
val conf: SparkConf = new SparkConf().setAppName("DemoWordCount").setMaster("local[*]")
val sc: SparkContext = new SparkContext(conf)
val lines: RDD[String] = sc.textFile("E:\\words.txt")
val words: RDD[String] = lines.flatMap(x => x.split(" "))
val wordAndOne: RDD[(String, Int)] = words.map(x => (x, 1))
val reduced: RDD[(String, Int)] = wordAndOne.reduceByKey((x, y) => x + y)
val result: RDD[(String, Int)] = reduced.sortBy(x => x._2)
result.saveAsTextFile("E:\\output")
sc.stop()
sc.textFile("words.txt").flatMap(_.split(" ")).map((_,1)).reduceByKey(_+_).sortBy(_.2).saveAsTextFile("out")
第一章 RDD概述
1.0 比较Java IO和RDD
1.0.1 Java IO中的装饰者设计模式
1.0.2 RDD
1.1 什么是RDD
- 官方定义:
RDD(Resilient Distributed Dataset)叫做分布式数据集,是Spark中最基本的数据(逻辑
)抽象。代码中是一个抽象类,它代表
一个不可变、可分区
、里面的元素可并行
计算的集合
。- 通俗理解:
(1) RDD是Spark中的一个最基本的抽象
(2) RDD可以认为是一个代理
(3) 有了RDD,就可以像操作本地的集合一样, 来操作分布式数据集.
(4) RDD本身不存储数据集合
1.2 RDD的属性
- 一组
分区
(Partition),即数据集的基本组成单位;- 一个计算每个分区的
函数
;- RDD之间的
依赖
关系;- 一个Partitioner,即RDD的
分片
函数;- 一个列表,存储存取每个Partition的优先
位置
(preferred location)。
1.3 RDD 特点
RDD表示
只读
的分区的数据集,对RDD进行改动,只能通过RDD的转换操作,由一个RDD得到一个新的RDD,新的RDD包含了从其他RDD衍生所必需的信息。RDDs之间存在依赖,RDD的执行是按照血缘关系延时计算的。如果血缘关系较长,可以通过持久化RDD来切断血缘关系。
1.3.1 分区
1.3.2 只读
1.3.3 依赖
1.3.4 缓存
1.3.5 CheckPoint
第二章 RDD编程
2.0 编程模型
在Spark中,RDD被表示为对象,通过对象上的方法调用来对RDD进行转换。经过一系列的transformations定义RDD之后,就可以调用actions触发RDD的计算,action可以是向应用程序返回结果(count, collect等),或者是向存储系统保存数据(saveAsTextFile等)。在Spark中,只有遇到action,才会执行RDD的计算(即延迟计算),这样在运行时可以通过管道的方式传输多个转换。
要使用Spark,开发者需要编写一个Driver程序(main),它被提交到集群以调度运行Worker,如下图所示。Driver中定义了一个或多个RDD,并调用RDD上的action,Worker则执行RDD分区计算任务。
2.1 获取SparkContext
- scala
val conf: SparkConf = new SparkConf().setAppName("Demo2").setMaster("local[*]")
val sc: SparkContext = new SparkContext(conf)
- java
SparkConf conf = new SparkConf().setAppName("Demo01").setMaster("local[*]");
JavaSparkContext jsc = new JavaSparkContext(conf);
2.2 RDD的创建
在Spark中创建RDD的方式有三种:
- 从集合中创建RDD
- 从外部存储创建RDD
- 从其他RDD创建
2.2.1 从集合中创建
parallelize
val rdd: RDD[Int] = sc.parallelize(1 to 10)
makeRDD
val rdd: RDD[Int] = sc.makeRDD(1 to 10)
2.2.2 由外部存储系统的数据集创建
val lines: RDD[String] = sc.textFile("E:\\agent.log")
2.2.3 从其他RDD创建
2.3 RDD算子之转换算子
2.3.1 value 类型
算子 | 作用 |
---|---|
map | 将func作用于每个元素之后得到一个新的RDD |
mapPartition | 类似于map,但独立地在RDD的每一个分片上运行 |
mapPartitionWithIndex | 类似于mapPartitions,但func带有一个整数参数表示分片的索引值 |
flatMap | 先map 后 flat |
glom | 将每一个分区形成一个数组,形成新的RDD类型是RDD[Array[T]] |
groupBy | 分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器。 |
filter | 过滤。返回一个新的RDD,该RDD由经过func函数计算后返回值为true的输入元素组成。 |
sample | 以指定的随机种子随机抽样出数量为fraction的数据 |
distinct | :对源RDD进行去重后返回一个新的RDD |
coalesce | 缩减分区数,用于大数据集过滤后,提高小数据集的执行效率。 |
repartition | 根据分区数,重新通过网络随机洗牌所有数据。 |
sortBy | 使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序。 |
pipe | 管道,针对每个分区,都执行一个shell脚本,返回输出的RDD。 |
2.3.1.1 map(func)
- 作用:
- func
f: T => U
/**
* @MethodName: map(func)
* @作用: 返回一个新的RDD,该RDD由每一个元素经过func函数转换后组成
* @需求: 创建一个1-10数组的RDD,将所有元素*2形成新的RDD
**/
val rdd: RDD[Int] = sc.makeRDD(1 to 10)
// f: T => U
val mapRdd : RDD[Int] = rdd.map(x => x * 10)
2.3.1.2 mapPartition(func)
- 作用:
类似于map, 但独立地在RDD的每一个分片(分区)上运行, 因此在类型为T的RDD上运行时, func的函数类型必须是>Iterator[T] => Iterator[U]
假设有N个元素, M个分区, 那么map的函数将被调用N次, 而mapPartition被调用M次, 一个函数一次处理所有分区.效率高,但可能会内存溢出
- func:
f:Iterator[T] => Iterator[U]
/**
* @MethodName: mapPartitions(func)
* @作用: 遍历每个分区
* @需求: 创建一个RDD,使每个元素*2组成新的RDD
**/
val rdd2 = sc.makeRDD(Array(1, 2, 3, 4))
// f: Iterator[T] => Iterator[U]
val mapPartitionRdd : RDD[Int] = rdd2.mapPartitions(x => x.map(_ * 2)) // x 是一个分区的数据
// 这里的map是scala的map不是spark的map,
// executor 上执行的是x.map(_*2)
2.3.1.3 mapPartitionWithIndex(func)
- 作用:
类似于mapPartitions,但func带有一个整数参数表示分片的索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int, Interator[T]) => Iterator[U];- func
f:(Int, Iterator[T]) => Iterator[U],
/**
* @MethodName: mapPartiotionsWithIndex(func)
* @作用: 类似于mapPartitions,但func带有一个整数参数表示分片索引值,因此在类型为T的RDD上运行时,func的函数类型必须是(Int,Interator[T] => Iterator[U])
* @需求: 创建一个RDD,使每个元素所在分区形成一个元祖组成一个新的RDD
**/
val rdd3 = sc.makeRDD(Array(1, 2, 3, 4))
// f: (Int, Iterator[T]) => Iterator[U],
val mapPartitionsWithIndexRdd: RDD[(Int, Int)] = rdd3.mapPartitionsWithIndex((index, items) => (items.map((index, _))))
// 对于多个参数,采用模式匹配写法
val mapPartitionsWithIndexRdd2: RDD[(Int, String)] = rdd3.mapPartitionsWithIndex {
case (index, items) => {
items.map((_, "分区号:" + index))
}
}
- map()和mapPartition()的区别:
- map():每次处理一条数据。
- mapPartition():每次处理一个分区的数据,这个分区的数据处理完后,原RDD中分区的数据才能释放,可能导致OOM。
- 开发指导:当内存空间较大的时候建议使用mapPartition(),以提高处理效率。
2.3.1.4 flatMap(func)
- 作用:
类似于map,但是每一个输入元素可以被映射为0或多个输出元素,所以func应该返回一个序列
,而不是单一元素- func:
f:T => TraversableOnce[U]
/**
* @MethodName: flatMap(func)
* @作用: 类似于map,但是每一个输入元素可以被映射为0或多个输出元素,所以func应该返回一个序列,而不是单一元素
* @需求: 创建一个1-5的RDD,运用flatMap创建一个新的RDD,新的RDD为原来的2倍
**/
val rdd4 = sc.makeRDD(Array(1, 2, 3, 4, 5))
// f: T => TraversableOnce[U]
val flatMapRdd: RDD[Int] = rdd4.flatMap(x => Array(x * 2))
2.3.1.5 glom()
- 作用: 以分区为单位做操作, 将每个分区形成一个数组, 形成新的RDD[Array[T]]
- func:
空
/**
* @MethodName: glom
* @作用: 将每一个分区形成一个数组,形成新的RDD类型是RDD[Array[T]]
* @需求: 创建一个4个分区的RDD,并将每个分区的数据放到一个数组
**/
val rdd5 = sc.makeRDD(Array(1, 2, 3, 4, 5, 6, 7, 8), 4)
val value: RDD[Array[Int]] = rdd5.glom()
//Array[Array[Int]] = Array(Array(1, 2), Array(3, 4), Array(5, 6), Array(7, 8))
2.3.1.6 groupBy(func)
- 作用:
按照传入函数的返回值
进行分组, 将相同key对应的值放到一个迭代器中- func:
f : T => K
/**
* @MethodName: groupBy(func)
* @作用: 分组,按照传入函数的返回值进行分组。将相同的key对应的值放入一个迭代器
* @需求: 创建一个RDD,按照元素模以2的值进行分组
**/
val rdd6 = sc.makeRDD(Array(1, 2, 3, 4, 5, 6, 7, 8), 4)
// f: T => K
// 分组后的数据形成了元祖(k-v), k表示组的key, v表示分组的数据集合
val value1: RDD[(Int, Iterable[Int])] = rdd6.groupBy(x => x % 2)
//Array[(Int, Iterable[Int])] = Array((0,CompactBuffer(2, 4, 6, 8)), (1,CompactBuffer(1, 3, 5, 7)))
2.3.1.7 filter(func)
- 作用:
过滤, 返回一个新的RDD, 该RDD由经过func函数计算后返回值为true
的输入元素组成- func:
f : T => Boolean
/**
* @MethodName: filter(func)
* @作用: 过滤,返回一个新的RDD, 该RDD由经过func函数计算后返回值为true的输入元素组成
* @需求: 创建一个RDD(由字符串组成) 过滤出一个新的RDD(包含xiao子串)
**/
val rdd7 = sc.makeRDD(Array("xiaohahg", "123", "xiaoxiao", "xhxhiaoxiao", "jdj"))
// f: T => Boolean
val filterRDD : RDD[String] = rdd7.filter(x => x.contains("xiao"))
2.3.1.8 sample(withReplacement, fraction, seed)
- 作用:
以指定的随机种子随机抽样出数据,withReplacement表示是抽出的数据是否放回,true为有放回的抽样,false为无放回的抽样,seed用于指定随机数生成器种子。- func :
/**
* @MethodName: sample
* @作用: 以指定的随机种子随机抽样出数据,withReplacement表示是抽出的数据是否放回true为有放回,seed用于指定随机种子
* @需求: 创建一个RDD(1-10) 从中选择放回和不放回choy
**/
val rdd8 = sc.makeRDD(1 to 10)
val sample1: RDD[Int] = rdd8.sample(true, 0.4, 2)
val sample2: RDD[Int] = rdd8.sample(false, 0.4, 8)
2.3.1.9 distinct([numTasks])
- 作用:
对源RDD进行去重后返回一个新的RDD,默认情况下,只有8个并行任务来操作,但是可以传入一个可选的numTasks参数改变它
一个分区的数据被打乱, 重组. 有shuffle的过程
- shuffle:
将人多的中一个分区的数据打乱.重组
到其他不同的分区的操作, 称之为shuffle操作
/**
* @MethodName: distinct([numTasks])
* @作用: 对源RDD进行去重后返回一个新的RDD,默认情况下,只有8个并任务来操作,但是可以传入一个可选的numTasks参数改变它
* @需求: 创建一个RDD,去重
**/
val rdd9 = sc.makeRDD(Array(1, 2, 3, 4, 3, 2, 3, 32, 3, 2, 5))
val value2: RDD[Int] = rdd9.distinct()
2.3.1.10 coalesce(numPartitions)
- 作用:
缩减分区数, 用于大数据集过滤后, 提高小数据集的执行效率- 也可以理解为合并分区
一个分区的数据没有被打乱重组,没有shuffle
过程
/**
* @MethodName: coalesce(numPartitions)
* @作用: 缩减分区数,用于大数据集过滤后, 提高小数据集的执行xiaolv
* @需求: 创建一个4个分区的RDD,对其缩减分区
**/
val rdd10 = sc.makeRDD(1 to 16, 4)
val value3: RDD[Int] = rdd10.coalesce(3)
2.3.1.11 repartition(numPartitions)
- 作用
根据分区数, 重新通过网络随机洗牌
所有数据’’\- 有shuffle过程
/**
* @MethodName: repartition(numPartitions)
* @作用: 根据分区数,重新通过网络随机洗牌所有数据
* @需求: 创建一个4个分区的RDD,对其重新分区
**/
val rdd11 = sc.makeRDD(1 to 20, 4)
val value4: RDD[Int] = rdd11.repartition(4)
// 相当于coalesce(numPartitions = 4,shuffle = true)
2.3.1.12 sortBy(func,[ascending], [numTasks])
- 作用:
使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序
/**
* @MethodName: sortBy(func,[ascending],[numTasks])
* @作用: 使用func先对数据进行处理,按照处理后的数据比较结果排序,默认为正序
* @需求: 创建一个RDD,按照不同的规则进行排序
**/
val rdd12 = sc.makeRDD(List(2, 1, 3, 4, 6, 3, 5, 2))
val sorted1: RDD[Int] = rdd12.sortBy(x => x)
val sorted2: RDD[Int] = rdd12.sortBy(x => x % 2)
2.3.1.13 pipe(command, [envVars])
2.3.2 双value类型
算子 | 作用 |
---|---|
union | 对源RDD和参数RDD求并集后返回一个新的RDD |
subtract | 计算差的一种函数,去除两个RDD中相同的元素,不同的RDD将保留下来 |
intersection | 对源RDD和参数RDD求交集后返回一个新的RDD |
cartesian | 笛卡尔积(尽量避免使用) |
zip | 将两个RDD组合成Key/Value形式的RDD,这里默认两个RDD的partition数量以及元素数量都相同,否则会抛出异常。 |
2.3.2.1 union(otherDataset)
2.3.2.2 subtract (otherDataset)
2.3.2.3 intersection(otherDataset)
2.3.2.4 cartesian(otherDataset)
2.3.2.5 zip(otherDataset)
2.3.3 Key-value类型
算子 | 作用 |
---|---|
partitionBy | 对pairRDD进行分区操作,如果原有的partionRDD和现有的partionRDD是一致的话就不进行分区, 否则会生成ShuffleRDD,即会产生shuffle过程。 |
groupByKey | groupByKey也是对每个key进行操作,但只生成一个sequence。 |
reduceByKey | 使用指定的reduce函数,将相同key的值聚合到一起,reduce任务的个数可以通过第二个可选的参数来设置。 |
aggregateByKey | 按key将value进行分组合并 ,合并时… |
foldByKey | aggregateByKey的简化操作,seqop和combop相同 |
combineByKey | 对相同K,把V合并成一个集合。 |
sortByKey | 在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD |
mapValues | 针对于(K,V)形式的类型只对V进行操作 |
join | 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD |
cogroup | 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable,Iterable))类型的RDD |
2.3.3.1 partitionBy(partitioner: Partitioner)
- 作用:
对pairRDD进行分区操作, 如果原有partionRDD和现有partionRDD是一致的话就不进行分区, 否则会生成shuffleRDD, 即产生shuffle过程
/**
* @MethodName: partitionBy(Partitioner)
* @作用: 对pairRDD进行分区操作,如果原有的partitionRdd和现有 的partitionRDD是一致的话就不进行分区,否则会生成ShuffleRDD,即会产生shuffle过程
* @需求: 创建一个4个分区的RDD,对其重新分区
**/
val rdd: RDD[(Int, String)] = sc.parallelize(Array((1, "aaa"), (2, "bbb"), (3, "ccc"), (4, "ddd")), 4)
val value: RDD[(Int, String)] = rdd.partitionBy(new HashPartitioner(2))
2.3.3.2 groupByKey()
- 作用:
groupByKey也是对每个key进行操作, 但是只生成一个sequence. (key, sequence[T])
/**
* @MethodName: groupByKey
* @作用: 对每个key进行操作,但只生成一个sequence
* @需求: 创建一个pairRdd,将相同key对应值聚合到一个sequence中,并计算相同key对应值的相加结果
**/
val words: Array[String] = Array("one", "two", "two", "three", "three", "three")
val rdd2: RDD[(String, Int)] = sc.parallelize(words).map(word => (word, 1))
val grouped: RDD[(String, Iterable[Int])] = rdd2.groupByKey()
val value1: RDD[(String, Int)] = grouped.map(t => (t._1, t._2.sum))
2.3.3.3 reduceByKey(func, [numTasks])
- 作用:
返回一个KV的RDD, 使用指定的reduce函数, 将相同key的值聚合到一起, reduce任务的个数可以通过第二个可选参数来设置- func:
f : (V, V) => V
/**
* @MethodName: reduceByKey(func,[numTasks])
* @作用: 在一个(k,v)的RDD上调用,返回一个(k,v)的rdd,使用指定的reduce函数,将相同key聚合到一起,reduce任务的个数可以
* 通过第二个可选参数来设置
* @需求: 创建一个pairRDD,计算相同key对应值的相加结果
**/
val rdd3 = sc.parallelize(words).map(word => (word, 1))
// func: (V, V) => V
val reduceByKeyRdd = rdd3.reduceByKey(_ + _)
- reduceByKey() 和 groupByKey的区别:
- reduceByKey:按照key进行聚合,在shuffle之前有combine(
预聚合
)操作,返回结果是RDD[k,v].- groupByKey: 按照key进行分组,直接进行shuffle。
- 开发指导:reduceByKey比groupByKey,建议使用。但是需要注意是否会影响业务逻辑。
2.3.3.4 aggregateByKey(zeroValue,[partitioner]) (seqOp,combOp)
- 作用:
对分区内和分区间的的数据进行不同的操作- 参数:
(zeroValue:U,[partitioner: Partitioner]) (seqOp: (U, V) => U,combOp: (U, U) => U)
(1)zeroValue:给每一个分区中的每一个key一个初始值;U
(2)seqOp:函数用于在每一个分区中用初始值逐步迭代value;(U, V) => U
(3)combOp:函数用于合并每个分区中的结果。(U, U) => U
/**
* @MethodName: aggregateByKey(zeroValue)(func1,func2)
* @作用: 在kv对的RDD中,按key将value进行分组合并,合并时,将每个value和初始值作为seq函数的参数,进行计算,返回的结果作为一个新的kv对,然后再将结果按照key进行合并,最后将每个分组的value传递给combine函数进行计算
* @需求: 创建一个RDD,取出每个分区相同key对应值的最大值,然后相加
**/
val rdd4 = sc.parallelize(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2)
// (zeroValue: U)(seqOp: (U, V) => U, combOp: (U, U) => U)
val value2: RDD[(String, Int)] = rdd4.aggregateByKey(0)(math.max(_, _), _ + _)
// 分区1(a,3)(c,4) 分区2(b,3) (c,8) 求和(a,3)(b,3)(c,12)
2.3.3.5 foldByKey(zeroValue)(func)
- 作用,
aggregateByKey的简化操作,seqop和combop相同
/**
* @MethodName: foldByKey(zeroValue)(func)
* @作用: aggregateByKey 的简化操作,func1和func2 相同
* @需求:
**/
val rdd5 = sc.parallelize(List(("a", 3), ("a", 2), ("c", 4), ("b", 3), ("c", 6), ("c", 8)), 2)
rdd5.foldByKey(0)(_ + _);
2.3.3.6 combineByKey(createCombiner, mergeValue, mergeCombiners)
- 作用:
对每个key的第一个value进行格式转换- 参数:
(createCombiner: V => C
, mergeValue: (C, V) => C
, mergeCombiners: (C, C) => C)- 参数描述:
(1)createCombiner: combineByKey() 会遍历分区中的所有元素,因此每个元素的键要么还没有遇到过,要么就和之前的某个元素的键相同。如果这是一个新的元素,combineByKey()会使用一个叫作createCombiner()的函数来创建那个键对应的累加器的初始值.将createCombiner函数作用于第一次遇到的key的value
(2)mergeValue: 如果这是一个在处理当前分区之前已经遇到的键,它会使用mergeValue()方法将该键的累加器对应的当前值与这个新的值进行合并
(3)mergeCombiners: 由于每个分区都是独立处理的, 因此对于同一个键可以有多个累加器。如果有两个或者更多的分区都有对应同一个键的累加器, 就需要使用用户提供的 mergeCombiners() 方法将各个分区的结果进行合并。
/**
* @MethodName: combineByKey[C]
* @作用: 对每个key的第一个value进行格式转换
* @需求: 创建一个pairRDD,根据key计算每种key的均值。(先计算每个key出现的次数以及可以对应值的总和,再相除得到结果)
**/
val rdd6 = sc.parallelize(Array(("a", 88), ("b", 95), ("a", 91), ("b", 93), ("a", 95), ("b", 98)), 2)
val sumAndCount: RDD[(String, (Int, Int))] = rdd6.combineByKey(v => (v, 1)
, (acc: (Int, Int), v) => (acc._1 + v, acc._2 + 1)
, (acc1: (Int, Int), acc2: (Int, Int)) => (acc1._1 + acc2._1, acc1._2 + acc2._2))
val result: RDD[(String, Int)] = sumAndCount.map(tuple => (tuple._1, tuple._2._1 / tuple._2._2))
val result1: RDD[(String, Int)] = sumAndCount.map {
case (key, value) => (key, value._1 / value._2)
}
2.3.3.7 sortByKey([ascending], [numTasks])
- 作用:
在一个(K,V)的RDD上调用,K必须实现Ordered接口,返回一个按照key进行排序的(K,V)的RDD
/**
* @MethodName: sortByKey([ascending],[numTasks])
* @作用: 作用在一个KV对的RDD上,K必须实现Ordered接口,返回一个按照key进行排序的(k,v)的RDD
* @需求:
**/
val rdd7 = sc.parallelize(Array((3, "aa"), (6, "cc"), (2, "bb"), (1, "dd")))
val value3: RDD[(Int, String)] = rdd7.sortByKey()
2.3.3.8 mapValues(func)
- 作用: 针对于(K,V)形式的类型只对V进行操作
/**
* @MethodName: mapValues
* @作用: (k,v)只针对v进行操作
* @需求: 创建一个pairRDD,并将value添加字符串“\\”
**/
val rdd8 = sc.parallelize(Array((1, "a"), (1, "d"), (2, "b"), (3, "c")))
// f: V => U
val mapValuesRDD: RDD[(Int, String)] = rdd8.mapValues(x => x + "||")
2.3.3.9 join(otherDataset, [numTasks])
- 作用:
在类型为(K,V)
和(K,W)
的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))
的RDD
/**
* @MethodName: join(otherDataset,[numTasks])
* @作用: 在类型为(K,V)和(K,W)的RDD上调用,返回一个相同key对应的所有元素对在一起的(K,(V,W))的RDD
* @需求: 创建两个pairRDD,并将key相同的数据聚合到一个元组
**/
val rdd91 = sc.parallelize(Array((1, "a"), (2, "b"), (3, "c")))
val rdd92 = sc.parallelize(Array((1, 4), (2, 5), (3, 6)))
val value4: RDD[(Int, (String, Int))] = rdd91.join(rdd92)
//Array[(Int, (String, Int))] = Array((1,(a,4)), (3,(c,6)), (2,(b,5)))
2.3.3.10 cogroup(otherDataset, [numTasks])
- 作用:
在类型为(K,V)
和(K,W)
的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))
类型的RDD
/**
* @MethodName: cogroup(otherDataset,[numTasks])
* @作用: 在类型为(K,V)和(K,W)的RDD上调用,返回一个(K,(Iterable<V>,Iterable<W>))类型的RDD
* @需求: 创建两个pairRDD,并将key相同的数据聚合到一个迭代器
**/
val rdd101: RDD[(Int, String)] = sc.parallelize(Array((1, "a"), (2, "b"), (3, "c")))
val rdd102: RDD[(Int, Int)] = sc.parallelize(Array((1, 4), (2, 5), (3, 6)))
val cogroupRDD: RDD[(Int, (Iterable[String], Iterable[Int]))] = rdd101.cogroup(rdd102)
//Array((1,(CompactBuffer(a),CompactBuffer(4))), (3,(CompactBuffer(c),CompactBuffer(6))), (2,(CompactBuffer(b),CompactBuffer(5))))
2.3.4 案例实操
- 数据结构: 时间戳,省份,城市,用户,广告,中间字段使用空格分割。
1516609143867 6 7 64 16
1516609143869 9 4 75 18
1516609143869 1 7 87 12
- 需求:统计出每一个省份广告被点击次数的TOP3
- 代码实现:
2.4 RDD算子之行动算子
所有的行动算子最后都会触发
sc.runJob
2.4.1 reduce(func)
- 作用:
通过func函数聚合RDD中的所有元素, 先聚合分区内的数据, 再聚合分区间的数据- func:
f: (T, T) => T
//需求:创建一个RDD,将所有元素聚合得到结果。
val rdd1: RDD[Int] = sc.makeRDD(1 to 10, 2)
val reduced: Int = rdd1.reduce(_ + _)
val rdd2: RDD[(String, Int)] = sc.makeRDD(Array(("a", 1), ("a", 3), ("c", 3), ("d", 5)))
val tuple: (String, Int) = rdd2.reduce((x, y) => (x._1, x._2 + y._2))
2.4.2 collect()
- 作用:
在驱动程序中,以数组的形式返回数据集的所有元素。
2.4.3 count()
- 作用:返回RDD中元素的个数
2.4.4 first()
2.4.5 take(n)
- 作用:返回一个由RDD的前n个元素组成的数组
2.4.6 takeOrdered(n)
- 作用:返回该RDD排序后的前n个元素组成的数组
2.4.7 aggregate(zeroValue)(seqOp, combOp)
- 作用:
aggregate函数将每个分区里面的元素通过seqOp和初始值进行聚合,然后用combine函数将每个分区的结果和初始值(zeroValue)进行combine操作。这个函数最终返回的类型不需要和RDD中元素类型一致。- 参数:
(zeroValue: U) (seqOp: (U, T) ⇒ U, combOp: (U, U) ⇒ U)
2.4.8 fold(num)(func)
- 作用:折叠操作,aggregate的简化操作,seqop和combop一样。
2.4.9 saveAsTextFile(path)
- 作用:将数据集的元素以textfile的形式保存到HDFS文件系统或者其他支持的文件系统,对于每个元素,Spark将会调用toString方法,将它装换为文件中的文本
2.4.10 saveAsSequenceFile(path)
- 作用:将数据集中的元素以Hadoop sequencefile的格式保存到指定的目录下,可以使HDFS或者其他Hadoop支持的文件系统。
2.4.11 saveAsObjectFile(path)
- 作用:用于将RDD中的元素序列化成对象,存储到文件中。
2.4.12 countByKey()
- 作用:针对
(K,V)类型
的RDD,返回一个(K,Int)的map
,表示每一个key对应的元素个数。
2.4.13 foreach(func)
- 作用:在数据集的每一个元素上,运行函数func进行更新
2.5 RDD中的函数传递(Serializable)
在实际开发中, 往往需要自己定义一些对于RDD的操作, 那么此时最主要的是, 初始化工作是在Diver端进行的, 而实际的计算, 是在Executor端进行的. 这就涉及到了跨进程通信, 是需要序列化的.
2.5.1 传递一个方法
- 创建一个类
class Search(query:String){
//过滤出包含字符串的数据
def isMatch(query: String): Boolean = {
s.contains(query)
}
//过滤出包含字符串的RDD
def getMatch1 (rdd: RDD[String]): RDD[String] = {
rdd.filter(isMatch)
}
//过滤出包含字符串的RDD
def getMatche2(rdd: RDD[String]): RDD[String] = {
rdd.filter(x => x.contains(query))
}
}
- 创建Spark主程序
object SeriTest {
def main(args: Array[String]): Unit = {
//1.初始化配置信息及SparkContext
val sparkConf: SparkConf = new SparkConf().setAppName("WordCount").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.创建一个RDD
val rdd: RDD[String] = sc.parallelize(Array("hadoop", "spark", "hive", "atguigu"))
//3.创建一个Search对象
val search = new Search()
//4.运用第一个过滤函数并打印结果
val match1: RDD[String] = search.getMatche1(rdd)
match1.collect().foreach(println)
}
}
- 运行程序
Exception in thread "main" org.apache.spark.SparkException: Task not serializable
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:298)
at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:288)
at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:108)
at org.apache.spark.SparkContext.clean(SparkContext.scala:2101)
at org.apache.spark.rdd.RDD$$anonfun$filter$1.apply(RDD.scala:387)
at org.apache.spark.rdd.RDD$$anonfun$filter$1.apply(RDD.scala:386)
at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)
at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:112)
at org.apache.spark.rdd.RDD.withScope(RDD.scala:362)
at org.apache.spark.rdd.RDD.filter(RDD.scala:386)
at com.atguigu.Search.getMatche1(SeriTest.scala:39)
at com.atguigu.SeriTest$.main(SeriTest.scala:18)
at com.atguigu.SeriTest.main(SeriTest.scala)
Caused by: java.io.NotSerializableException: com.atguigu.Search
- 问题说明
//过滤出包含字符串的RDD
def getMatch1 (rdd: RDD[String]): RDD[String] = {
rdd.filter(isMatch)
}
在这个方法中, 所调用的方法isMatch()是定义在Search这个类中的. 实际上调用的是
this.isMatch()
, this就是 new Search() 这个对象, 程序在运行过程中需要将Search对象序列化之后再传递到Executor端.
- 解决方案
使类继承scala.Serializable即可。
class Search() extends Serializable{...}
2.5.2 传递一个属性
- 创建Spark主程序
object TransmitTest {
def main(args: Array[String]): Unit = {
//1.初始化配置信息及SparkContext
val sparkConf: SparkConf = new SparkConf().setAppName("WordCount").setMaster("local[*]")
val sc = new SparkContext(sparkConf)
//2.创建一个RDD
val rdd: RDD[String] = sc.parallelize(Array("hadoop", "spark", "hive", "atguigu"))
//3.创建一个Search对象
val search = new Search()
//4.运用第一个过滤函数并打印结果
val match1: RDD[String] = search.getMatche2(rdd)
match1.collect().foreach(println)
}
}
- 运行程序
Exception in thread "main" org.apache.spark.SparkException: Task not serializable
at org.apache.spark.util.ClosureCleaner$.ensureSerializable(ClosureCleaner.scala:298)
at org.apache.spark.util.ClosureCleaner$.org$apache$spark$util$ClosureCleaner$$clean(ClosureCleaner.scala:288)
at org.apache.spark.util.ClosureCleaner$.clean(ClosureCleaner.scala:108)
at org.apache.spark.SparkContext.clean(SparkContext.scala:2101)
at org.apache.spark.rdd.RDD$$anonfun$filter$1.apply(RDD.scala:387)
at org.apache.spark.rdd.RDD$$anonfun$filter$1.apply(RDD.scala:386)
at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:151)
at org.apache.spark.rdd.RDDOperationScope$.withScope(RDDOperationScope.scala:112)
at org.apache.spark.rdd.RDD.withScope(RDD.scala:362)
at org.apache.spark.rdd.RDD.filter(RDD.scala:386)
at com.atguigu.Search.getMatche1(SeriTest.scala:39)
at com.atguigu.SeriTest$.main(SeriTest.scala:18)
at com.atguigu.SeriTest.main(SeriTest.scala)
Caused by: java.io.NotSerializableException: com.atguigu.Search
- 问题说明
//过滤出包含字符串的RDD
def getMatche2(rdd: RDD[String]): RDD[String] = {
rdd.filter(x => x.contains(query))
}
在这个方法中所调用的属性query是定义在Search这个类中的字段,实际上调用的是
this. query
,this表示Search这个类的对象,程序在运行过程中需要将Search对象序列化以后传递到Executor端。
- 解决方案
1)使类继承scala.Serializable即可。
class Search() extends Serializable{...}
2)将类变量query赋值给局部变量
修改getMatche2为
//过滤出包含字符串的RDD
def getMatche2(rdd: RDD[String]): RDD[String] = {
val query_ : String = this.query//将类变量赋值给局部变量
rdd.filter(x => x.contains(query_))
//字符串是可以直接传递的
}
2.6 RDD依赖关系
每个
RDD中都存储着依赖关系
2.6.1 Lineage(血统)
RDD只支持粗粒度转换,即在大量记录上执行的单个操作。将创建RDD的一系列Lineage(血统)记录下来,以便恢复丢失的分区。RDD的Lineage会记录RDD的
元数据信息
和转换行为
,当该RDD的部分分区数据丢失时,它可以根据这些信息来重新运算和恢复丢失的数据分区。
2.6.2 窄依赖(独生子女)
窄依赖指的是每一个父RDD的Partition最多被子RDD的一个Partition使用,窄依赖我们形象的比喻为
独生子女
一对一, 多对一
2.6.3 宽依赖(超生)
宽依赖指的是多个子RDD的Partition会依赖同一个父RDD的Partition,会引起shuffle,总结:宽依赖我们形象的比喻为
超生
一对多
, 一个分区的数据被分配到多个分区中, 打乱重组, shuffle
2.6.4 DAG
DAG(Directed Acyclic Graph)叫做有向无环图,原始的RDD通过一系列的转换就就形成了DAG,根据RDD之间的依赖关系的不同将DAG划分成不同的
Stage
,
对于窄依赖,partition的转换处理在Stage中完成计算。
对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,
因此宽依赖是划分Stage的依据。
DAG ===> 依赖关系 ===> Stage
2.6.5 任务划分
RDD任务切分中间分为:Application、Job、Stage和Task
Application
:
初始化一个SparkContext即生成一个ApplicationJob
:
一个Action算子
就会生成一个JobStage
:
根据RDD之间的依赖关系的不同将Job划分成不同的Stage,遇到一个宽依赖
则划分一个Stage。
只有第一个Stage结束, 才能开始第二个Stage, 第三个Stage…
stage = shuffle + 1 = wide dependency + 1Task
:
Stage是一个TaskSet
,将Stage划分的结果发送到不同的Executor执行
即为一个Task。
一个stage最后一个RDD分区个数
注意:Application->Job->Stage-> Task每一层都是
1对n
的关系。
2.7 RDD缓存
RDD通过
persist
方法或cache
方法可以将前面的计算结果缓存,默认情况下 persist() 会把数据以序列化的形式缓存在 JVM 的堆空间中。
但是并不是这两个方法被调用时立即缓存
,而是触发后面的action
时,该RDD将会被缓存在计算节点的内存中,并供后面重用。
作用: 将运行时间上 比较重要的数据的计算结果进行缓存, 出错时 不必再从头计算
缓存有可能丢失,或者存储存储于内存的数据由于内存不足而被删除,RDD的缓存容错机制保证了即使缓存丢失也能保证计算的正确执行。通过基于RDD的一系列转换,丢失的数据会被重算,由于RDD的各个Partition是相对独立的,因此只需要计算丢失的部分即可,并不需要重算全部Partition。
(1)创建一个RDD
scala> val rdd = sc.makeRDD(Array("atguigu"))
rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[19] at makeRDD at <console>:25
(2)将RDD转换为携带当前时间戳不做缓存
scala> val nocache = rdd.map(_.toString+System.currentTimeMillis)
nocache: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[20] at map at <console>:27
(3)多次打印结果
scala> nocache.collect
res0: Array[String] = Array(atguigu1538978275359)
scala> nocache.collect
res1: Array[String] = Array(atguigu1538978282416)
scala> nocache.collect
res2: Array[String] = Array(atguigu1538978283199)
(4)将RDD转换为携带当前时间戳并做缓存
scala> val cache = rdd.map(_.toString+System.currentTimeMillis).cache
cache: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[21] at map at <console>:27
(5)多次打印做了缓存的结果
scala> cache.collect
res3: Array[String] = Array(atguigu1538978435705)
scala> cache.collect
res4: Array[String] = Array(atguigu1538978435705)
scala> cache.collect
res5: Array[String] = Array(atguigu1538978435705)
2.8 RDD CheckPoint
Spark中对于数据的保存除了持久化操作之外,还提供了一种检查点的机制,检查点(本质是通过将RDD写入Disk做检查点)是为了通过lineage做容错的辅助,lineage过长会造成容错成本过高,这样就不如在中间阶段做检查点容错,如果之后有节点出现问题而丢失分区,从做检查点的RDD开始重做Lineage,就会减少开销。
检查点通过将数据写入到HDFS文件系统实现了RDD的检查点功能
。
为当前RDD设置检查点。该函数将会创建一个二进制的文件,并存储到checkpoint目录中,该目录是用SparkContext.setCheckpointDir()设置
的。在checkpoint的过程中,该RDD的所有依赖于父RDD中的信息将全部被移除
。对RDD进行checkpoint操作并不会马上被执行,必须执行Action操作才能触发
。
(1)设置检查点
scala> sc.setCheckpointDir("hdfs://node11:9000/checkpoint")
(2)创建一个RDD
scala> val rdd = sc.parallelize(Array("yanzlh"))
rdd: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[14] at parallelize at <console>:24
(3)将RDD转换为携带当前时间戳并做checkpoint
scala> val ch = rdd.map(_+System.currentTimeMillis)
ch: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[16] at map at <console>:26
scala> ch.checkpoint
(4)多次打印结果
scala> ch.collect
res55: Array[String] = Array(yanzlh1538981860336)
scala> ch.collect
res56: Array[String] = Array(yanzlh1538981860504)
scala> ch.collect
res57: Array[String] = Array(yanzlh1538981860504)
scala> ch.collect
res58: Array[String] = Array(yanzlh1538981860504)
第三章 键值对RDD数据分区器
Spark目前支持
Hash
分区和Range
分区,用户也可以自定义
分区,Hash分区为当前的默认分区,Spark中分区器
直接决定了RDD中分区的个数
、RDD中每条数据经过Shuffle过程属于哪个分区和Reduce的个数.
注意:
(1)只有Key-Value
类型的RDD才有分区器
的,非Key-Value类型的RDD分区器的值是None
(2)每个RDD的分区ID范围:0~numPartitions-1,决定这个值是属于那个分区的。
3.0 获取RDD分区
可以通过使用RDD的
partitioner 属性
来获取 RDD 的分区方式。它会返回一个 scala.Option 对象, 通过get
方法获取其中的值
3.1 Hash 分区
3.2 Ranger 分区
3.3 自定义分区
要实现自定义的分区器,你需要继承 org.apache.spark.
Partitioner
类并实现下面三个方法。
(1)numPartitions: Int:返回创建出来的分区数
。
(2)getPartition(key: Any): Int:返回给定键的分区编号
(0 到 numPartitions-1)。
(3)equals():Java 判断相等性的标准方法。这个方法的实现非常重要,Spark 需要用这个方法来检查你的分区器对象是否和其他分区器实例相同,这样 Spark 才可以判断两个 RDD 的分区方式是否相同。
第四章 数据读取与保存
4.1 Text文件
- 数据读取: textFile(String)
- 数据保存: saveAsTextFile(String)
4.2 JSON文件
使用RDD读取JSON文件处理很复杂,同时SparkSQL集成了很好的处理JSON文件的方式,所以应用中多是采用
SparkSQL
处理JSON文件。
4.3 Sequence文件
SequenceFile文件是Hadoop用来存储二进制形式的
key-value
对而设计的一种平面文件(Flat File)。Spark 有专门用来读取 SequenceFile 的接口。在 SparkContext 中,可以调用 sequenceFile[keyClass, valueClass ](path)。
注意:SequenceFile文件只针对PairRDD
- 数据读取: sc.sequenceFile(String)
- 数据保存: rdd.saveAsSequenceFile(String)
4.4 对象文件
对象文件是将对象序列化后保存的文件,采用Java的序列化机制。可以通过objectFilek,v 函数接收一个路径,读取对象文件,返回对应的 RDD,也可以通过调用saveAsObjectFile() 实现对对象文件的输出。因为是序列化所以要指定类型。
- 数据读取: sc.objectFile(String)
- 数据保存: rdd.saveAsObjectFile(String)
4.5 HDFS
4.6 MySQL
支持通过Java JDBC访问关系型数据库。需要通过JdbcRDD进行,示例如下:
(1)添加依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.27</version>
</dependency>
(2)Mysql读取:
def main(args: Array[String]): Unit = {
//1.创建spark配置信息
val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("JdbcRDD")
//2.创建SparkContext
val sc = new SparkContext(sparkConf)
//3.定义连接mysql的参数
val driver = "com.mysql.jdbc.Driver"
val url = "jdbc:mysql://node11:3306/rdd"
val userName = "root"
val passWd = "1229"
//创建JdbcRDD
val rdd: JdbcRDD[(Int, String)] = new JdbcRDD(sc, () => {
Class.forName(driver)
DriverManager.getConnection(url, userName, passWd)
},
"select * from `rddtable` where `id`>=?;",
1,
10,
1,
r => (r.getInt(1), r.getString(2))
)
//打印最后结果
println(rdd.count())
rdd.foreach(println)
rdd.foreachPartition(insertData)
sc.stop()
}
(3)Mysql写入:
def insertData(iterator: Iterator[String]) : Unit = {
Class.forName ("com.mysql.jdbc.Driver").newInstance()
val conn = java.sql.DriverManager.getConnection("jdbc:mysql://hadoop102:3306/rdd", "root", "000000")
iterator.foreach(data => {
val ps = conn.prepareStatement("insert into rddtable(name) values (?)")
ps.setString(1, data)
ps.executeUpdate()
})
}
// 调用
val data: RDD[String] = sc.parallelize(List("Female", "Male", "Female"))
data.foreachPartition(insertData)
4.7 HBase
(1)添加依赖
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.3.1</version>
</dependency>
(2)从HBase读取数据
package com.atguigu
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.hbase.client.Result
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableInputFormat
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.hadoop.hbase.util.Bytes
object HBaseSpark {
def main(args: Array[String]): Unit = {
//创建spark配置信息
val sparkConf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("JdbcRDD")
//创建SparkContext
val sc = new SparkContext(sparkConf)
//构建HBase配置信息
val conf: Configuration = HBaseConfiguration.create()
conf.set("hbase.zookeeper.quorum", "hadoop102,hadoop103,hadoop104")
conf.set(TableInputFormat.INPUT_TABLE, "rddtable")
//从HBase读取数据形成RDD
val hbaseRDD: RDD[(ImmutableBytesWritable, Result)] = sc.newAPIHadoopRDD(
conf,
classOf[TableInputFormat],
classOf[ImmutableBytesWritable],
classOf[Result])
val count: Long = hbaseRDD.count()
println(count)
//对hbaseRDD进行处理
hbaseRDD.foreach {
case (_, result) =>
val key: String = Bytes.toString(result.getRow)
val name: String = Bytes.toString(result.getValue(Bytes.toBytes("info"), Bytes.toBytes("name")))
val color: String = Bytes.toString(result.getValue(Bytes.toBytes("info"), Bytes.toBytes("color")))
println("RowKey:" + key + ",Name:" + name + ",Color:" + color)
}
//关闭连接
sc.stop()
}
}
3)往HBase写入
def main(args: Array[String]) {
//获取Spark配置信息并创建与spark的连接
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("HBaseApp")
val sc = new SparkContext(sparkConf)
//创建HBaseConf
val conf = HBaseConfiguration.create()
val jobConf = new JobConf(conf)
jobConf.setOutputFormat(classOf[TableOutputFormat])
jobConf.set(TableOutputFormat.OUTPUT_TABLE, "fruit_spark")
//构建Hbase表描述器
val fruitTable = TableName.valueOf("fruit_spark")
val tableDescr = new HTableDescriptor(fruitTable)
tableDescr.addFamily(new HColumnDescriptor("info".getBytes))
//创建Hbase表
val admin = new HBaseAdmin(conf)
if (admin.tableExists(fruitTable)) {
admin.disableTable(fruitTable)
admin.deleteTable(fruitTable)
}
admin.createTable(tableDescr)
//定义往Hbase插入数据的方法
def convert(triple: (Int, String, Int)) = {
val put = new Put(Bytes.toBytes(triple._1))
put.addImmutable(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes(triple._2))
put.addImmutable(Bytes.toBytes("info"), Bytes.toBytes("price"), Bytes.toBytes(triple._3))
(new ImmutableBytesWritable, put)
}
//创建一个RDD
val initialRDD = sc.parallelize(List((1,"apple",11), (2,"banana",12), (3,"pear",13)))
//将RDD内容写到HBase
val localData = initialRDD.map(convert)
localData.saveAsHadoopDataset(jobConf)
}
第五章 RDD编程进阶
5.0 Spark中的数据结构
5.0.1 RDD: 分布式数据集
5.0.2 累加器: 分布式只写共享变量
5.0.3 广播变量: 分布式只读共享变量
5.1 累加器
累加器用来对信息进行聚合,通常在向 Spark传递函数时,比如使用 map() 函数或者用 filter() 传条件时,可以使用
驱动器Driver程序中定义的变量
,但是集群中运行的每个任务都会得到这些变量的一份新的副本,更新这些副本的值也不会影响驱动器中的对应变量。如果我们想实现所有分片处理时更新共享变量的功能
,那么累加器可以实现我们想要的效果。
5.1.1 系统累加器
针对一个输入的日志文件,如果我们想计算文件中所有空行的数量,我们可以编写以下程序:
val notice = sc.textFile("./NOTICE")
// 定义累加器变量
val blanklines = sc.accumulator(0)
val tmp = notice.flatMap(line => {
if (line == "") {
blanklines += 1
}
line.split(" ")
})
tmp.count()
// 获取累加器变量的值
blanklines.value
5.1.2 自定义累加器
package com.lz.spark.day02
import org.apache.spark.{SparkConf, SparkContext}
/**
* @ClassName LogAccumulator
* @Description: TODO
* @Author MAlone
* @Date 2019/12/16
* @Version V1.0
**/
class LogAccumulator extends org.apache.spark.util.AccumulatorV2[String, java.util.Set[String]] {
private val _logArray: java.util.Set[String] = new java.util.HashSet[String]()
override def isZero: Boolean = {
_logArray.isEmpty
}
override def reset(): Unit = {
_logArray.clear()
}
override def add(v: String): Unit = {
_logArray.add(v)
}
override def merge(other: org.apache.spark.util.AccumulatorV2[String, java.util.Set[String]]): Unit = {
other match {
case o: LogAccumulator => _logArray.addAll(o.value)
}
}
override def value: java.util.Set[String] = {
java.util.Collections.unmodifiableSet(_logArray)
}
override def copy():org.apache.spark.util.AccumulatorV2[String, java.util.Set[String]] = {
val newAcc = new LogAccumulator()
_logArray.synchronized{
newAcc._logArray.addAll(_logArray)
}
newAcc
}
}
// 过滤掉带字母的
object LogAccumulator {
def main(args: Array[String]) {
val conf=new SparkConf().setAppName("LogAccumulator")
val sc=new SparkContext(conf)
val accum = new LogAccumulator
sc.register(accum, "logAccum")
val sum = sc.parallelize(Array("1", "2a", "3", "4b", "5", "6", "7cd", "8", "9"), 2).filter(line => {
val pattern = """^-?(\d+)"""
val flag = line.matches(pattern)
if (!flag) {
accum.add(line)
}
flag
}).map(_.toInt).reduce(_ + _)
println("sum: " + sum)
for (v <- accum.value) print(v + "")
println()
sc.stop()
}
}
5.2 广播变量(调优策略)
广播变量用来高效分发较大的对象。向所有工作节点发送一个较大的
只读值
,以供一个或多个Spark操作使用。比如,如果你的应用需要向所有节点发送一个较大的只读查询表,甚至是机器学习算法中的一个很大的特征向量,广播变量用起来都很顺手。 在多个并行操作中使用同一个变量,但是 Spark会为每个任务分别发送。
第六章 RDD相关概率关系
输入可能以多个文件的形式存储在HDFS上,每个File都包含了很多块,称为Block。当Spark读取这些文件作为输入时,会根据具体数据格式对应的InputFormat进行解析,
一般是将若干个Block合并成一个输入分片,称为InputSplit,
注意InputSplit不能跨越文件。随后将为这些输入分片生成具体的Task。InputSplit与Task是一一对应的关系
。随后这些具体的Task每个都会被分配到集群上的某个节点的某个Executor去执行。
- 每个节点可以起一个或多个Executor。
- 每个Executor由若干core组成,每个Executor的每个core一次只能执行一个Task。
- 每个Task执行的结果就是生成了目标RDD的一个partiton。 注意:
这里的core是虚拟的core而不是机器的物理CPU核,可以理解为就是Executor的一个工作线程。而 Task被执行的并发度 =Executor数目 * 每个Executor核数。
至于partition的数目
:
- 对于数据读入阶段,例如sc.textFile,输入文件被划分为
多少InputSplit就会需要多少初始Task
。- 在Map阶段partition数目保持不变。
- 在Reduce阶段,RDD的聚合会触发shuffle操作,聚合后的RDD的partition数目跟具体操作有关,例如repartition操作会聚合成指定分区数,还有一些算子是可配置的。
RDD在计算的时候,
每个分区都会起一个task
,所以rdd的分区数目决定了总的的task数目
。申请的计算节点(Executor)数目和每个计算节点核数,决定了你同一时刻可以并行执行的task。
比如的RDD有100个分区,那么计算的时候就会生成100个task,你的资源配置为10个计算节点,每个两2个核,同一时刻可以并行的task数目为20,计算这个RDD就需要5个轮次。如果计算资源不变,你有101个task的话,就需要6个轮次,在最后一轮中,只有一个task在执行,其余核都在空转。如果资源不变,你的RDD只有2个分区,那么同一时刻只有2个task运行,其余18个核空转,造成资源浪费。这就是在spark调优中,增大RDD分区数目,增大任务并行度的做法。