一:RDD转换算子
RDD根据数据处理方式的不同将算子整体上分为Value类型、双Value类型和Key-Value类型
1、map (def map[U: ClassTag](f: T => U): RDD[U])
TODO 算子 - 转换
所谓的转换算子,其实就是通过调用RDD对象的方法,将旧的RDD转换为新的RDD通过转换,将多个功能组合在一起.将处理的数据逐条进行映射转换,这里的转换可以是类型的转换,也可以是值的转换。
object Spark01_RDD_OperTransform {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
// TODO 算子 - 转换
// 所谓的转换算子,其实就是通过调用RDD对象的方法,将旧的RDD转换为新的RDD
// 通过转换,将多个功能组合在一起.
// TODO map -> 转换,映射 (Key => Value)
// rdd的map算子可以将数据集中的每一条数据进行转换后返回
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4))
val newRDD: RDD[Int] = rdd.map(_ * 2)
// newRDD.collect().foreach(println)
// map算子需要传递一个参数,参数类型为 : Int => U(不确定)
/*def mapFunction(num : Int) : Int = {
num * 2
}
val newRDD: RDD[Int] = rdd.map(mapFunction)*/
//至简原则
/*val newRDD: RDD[Int] = rdd.map(
num => num * 2
)*/
newRDD.collect().foreach(println)
sc.stop()
}
}
map案例
map VS flatmap()
flatmap()将整体拆分成个体,个体需要保留
map()将一个长的字符串拆分成短的,只保留需要的部分
object Spark01_RDD_OperTransform_map_demo {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[String] = sc.textFile("datas/apache.log")
val newRDD: RDD[String] = rdd.map(line => {
val datas: Array[String] = line.split(" ")
datas(6)
})
newRDD.collect().foreach(println)
sc.stop()
}
}
2、mappartitions()入参和出参都是迭代器
函数签名
将待处理的数据以分区为单位发送到计算节点进行处理
def mapPartitions[U: ClassTag](
f: Iterator[T] => Iterator[U],
preservesPartitioning: Boolean = false): RDD[U]
map VS mappartitions() 类比
Map算子是分区内一个数据一个数据的执行,类似于串行操作。而mapPartitions算子是以分区为单位进行批处理操作。
Map算子主要目的将数据源中的数据进行转换和改变。但是不会减少或增多数据。MapPartitions算子需要传递一个迭代器,返回一个迭代器,没有要求的元素的个数保持不变,所以可以增加或减少数据
public class Test2 { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); // A for ( String s : list ) { map(s); } System.out.println("***********************"); // B tests(list); } public static void map( String s ) { System.out.println("获取数据库连接"); System.out.println(s); System.out.println("关闭数据库连接"); } public static void tests( List<String> list ) { System.out.println("获取数据库连接"); for ( String s : list ) { // 49 System.out.println(s); // 50 } System.out.println("关闭数据库连接"); } }
案例一:求每个分区的最大值,注意结果需要包装称为迭代器
map()不行因为map只能拿到数据,并不知道数据来自哪个分区
object Spark01_RDD_OperTransform_map {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
val newRDD: RDD[Int] = rdd.mapPartitions(
// mapPartitions算子需要传递一个参数,类型为函数类型: Iterator => Iterator
list => {
List(list.max).iterator
}
)
newRDD.collect().foreach(println)
sc.stop()
}
}
3、mapPartitionsWithIndex()
案例:只保留一个分区
map()不行因为map只能拿到数据,并不知道数据来自哪个分区
将待处理的数据以分区为单位发送到计算节点进行处理,这里的处理是指可以进行任意的处理,哪怕是过滤数据,在处理时同时可以获取当前分区索引。
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
// mapPartitionsWithIndex算子需要传递参数,参数的一个值为分区索引,从0开始
val newRDD: RDD[Int] = rdd.mapPartitionsWithIndex(
//入参是一个(int,Iterator) 返回一个Iterator
(index, list) => {
if (index == 1) {
list
} else {
Nil.iterator
}
}
)
newRDD.collect().foreach(println)
sc.stop()
}
}
4、groupBy()
将数据根据指定的规则进行分组, 分区默认不变,但是数据会被打乱重新组合,我们将这样的操作称之为shuffle。极限情况下,数据可能被分在同一个分区中
一个组的数据在一个分区中,但是并不是说一个分区中只有一个组
def groupBy[K](f: T => K)(implicit kt: ClassTag[K]): RDD[(K, Iterable[T])]
groupBy算子需要传递一个参数,参数类型为函数类型:Int => K
groupBy算子返回的结果类型为元组,第一个元素就是分组的标记,第二个元素就是相同标记的数据集合
groupBy算子的第二个参数可以改变分区数量
object Spark01_RDD_OperTransform_map {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
//groupBy()需要传递一个参数,参数类型为函数类型 : Int => K
//RDD[(String, Iterable[Int])]表示相同数据的集合放到一块
/*val groupRdd: RDD[(String, Iterable[Int])] = rdd.groupBy(
num => {
if (num % 2 == 0) {
"偶数"
} else {
"奇数"
}
}
)*/
/*val groupRdd: RDD[(Int, Iterable[Int])] = rdd.groupBy(
num => {
if (num % 2 == 0) {
0
} else {
1
}
}
)*/
/*val groupRdd: RDD[(Boolean, Iterable[Int])] = rdd.groupBy(
num => {
num % 2 == 0
}
)*/
//num只用了一次 可以只看结果
val groupRdd: RDD[(Boolean, Iterable[Int])] = rdd.groupBy(_ % 2 == 0)
groupRdd.collect().foreach(println)
sc.stop()
}
}
shuffle会将完整的计算流程一分为二,变成两个阶段,一个写的阶段,一个读的阶段 如果数据只有一条数据 只要分组打乱分区,则一定会落盘
所有含有shuffle的算子,都有改变分区的能力因此groupBy()第二个参数可以传分区的个数。一个RDD计算的过程很大程度上取决于shuffle(),shuffle()快就快,shuffle()慢就慢
reduceByKey()也有shuffle()和改变分区的能力
案例:将List("Hello", "hive", "hbase", "Hadoop")根据单词首写字母进行分组。
在Scala中可以通过 List.apply(1, 2, 3) 创建一个List对象, apply方法定义在List类的伴生对象中, 像之前所说的, 我们可以简化apply方法, 直接通过 List(1, 2, 3) 创建一个List实例.
默认创建对象都调用了apply()
object Spark01_RDD_OperTransform_map {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
//val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4), 2)
val rdd: RDD[String] = sc.makeRDD(List("Hello", "hive", "hbase", "Hadoop"))
/*
方式一
val groupRdd: RDD[(String, Iterable[String])] = rdd.groupBy(
// 传递一个参数,参数类型为函数类型:String => K
//返回的结果类型为元组,第一个元素就是分组的标记,第二个元素就是相同标记的数据集合
word => {
word.substring(0, 1)
}
)*/
val groupRdd: RDD[(Char, Iterable[String])] = rdd.groupBy(
word => {
//scala中没有字符串
//apply()可以省略 隐式转换
// word.apply(0)
word(0)
}
)
groupRdd.collect().foreach(println)
sc.stop()
}
}
案例:从服务器日志数据apache.log中获取每个时间段访问量(类似于wordcount)
案例三:wordcount
groupBy算子可以实现 WordCount 功能 ( 1 / 10 ) 但是不能独立实现,因为没有聚合的功能
object Spark01_RDD_OperTransform_map {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
//textFile是一行一行读 因此 是String
val groupRdd: RDD[String] = sc.textFile("datas/apache.log")
val newGoup: RDD[(String, Int)] = groupRdd.map(
line => {
val datas: Array[String] = line.split(" ")
val time: String = datas(3)
val times: Array[String] = time.split(":")
val hour: String = times(1)
(hour, 1)
}
//((16,1),CompactBuffer(143.233.204.28 - - 17/05/2015:16:05:20 +0000 GET /blog/geekery/ssl-latency.html, 143.233.204.28 - - 17/05/2015:16:05:54 +0000 GET /blog/geekery/ssl-latency.html
//对于每一个传入的元素获取时间段
)
// 返回的结果类型为元组,第一个元素就是分组的标记,第二个元素就是相同标记的数据集合
//类型的转换,也可以是值的转换。
val hourGroup: RDD[(String, Iterable[(String, Int)])] = newGoup.groupBy(_._1)
//mapValues心生成的集合 key不变 values 求和
val result: RDD[(String, Int)] = hourGroup.mapValues(_.size)
result.collect().foreach(println)
sc.stop()
}
}
5、flatMap
def flatMap[U: ClassTag](f: T => TraversableOnce[U]): RDD[U]
将处理的数据进行扁平化后再进行映射处理,所以算子也称之为扁平映射
TODO 将整体数据拆分成个体数据的操作,称之为扁平化。spark中只有flatmap()没有flatten()
案例:将List(List(1,2)List(3,4))进行扁平化操作
object Spark01_RDD_OperTransform_wordcount {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[List[Int]] = sc.makeRDD(
List(
List(1, 2),
List(3, 4))
)
rdd.flatMap(list => list).collect().foreach(println)
}
}
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
/*val rdd: RDD[List[Int]] = sc.makeRDD(
List(
List(1, 2),
List(3, 4))
)*/
// TODO 将整体数据拆分成个体数据的操作,称之为扁平化。
// flatMap算子需要传递一个参数,这个参数得类型未函数类型:item => Container
val rdd: RDD[Int] = sc.makeRDD(List(
1, 2, 3, 4
))
rdd.flatMap(num => List(num)).collect().foreach(println)
// rdd.flatMap(list => list).map(_*2)
}
}
案例:将List(List(1,2),3,List(4,5))进行扁平化操作
object Spark01_RDD_OperTransform_flatmap {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[Any] = sc.makeRDD(
List(
List(1, 2),
3,
List(4, 5))
)
rdd.flatMap{
//模式匹配不考虑泛型
//即如果是list的集合 则直接返回list 然后flatmap()会对其进行扁平化
case list: List[_] => {
list
}
case num => {
//即如果是单独的数值 则包装成集合 然后flatmap()会对其进行扁平化
List(num)
}
}.collect().foreach(println)
sc.stop()
// newRDD.collect().foreach(println)
}
}
6、glom()
不管是将整体转成个体还是将个体转成整体,都是为了方便计算。
def glom(): RDD[Array[T]]
将同一个分区的数据直接转换为相同类型的内存数组进行处理,分区不变
glom算子可以将分区内的个体数据转换为分区的整体数据
补充:
泛型和super都是只在编译时有效
object Spark01_RDD_OperTransform_glom {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
val newRDD: RDD[Array[Int]] = rdd.glom()
//求每个分区的最大值 给分区内排序
val maxRDD: RDD[Int] = newRDD.map(_.max)
val d: Double = maxRDD.sum()
println(d)
// maxRDD.collect().foreach(println)
// maxRDD.collect().foreach(println)
sc.stop()
}
}
每个分区都是都是存的是Array[Int]类型的RDD对象
val newRDD: RDD[Array[Int]] = rdd.glom()
7、filter
函数签名 def filter(f: T => Boolean): RDD[T]
将数据根据指定的规则进行筛选过滤,符合规则的数据保留,不符合规则的数据丢弃。当数据进行筛选过滤后,分区不变,但是分区内的数据可能不均衡,生产环境下,可能会出现数据倾斜。
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
val newRDD: RDD[Int] = rdd.filter(
_ % 2 == 0
)
newRDD.collect().foreach(println)
sc.stop()
}
案例:从服务器日志数据apache.log中获取2015年5月17日的请求路径
object Spark01_RDD_OperTransform_filter {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
/*val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
val newRDD: RDD[Int] = rdd.filter(
_ % 2 == 0
)*/
val fileRDD: RDD[String] = sc.textFile("datas/apache.log")
fileRDD.filter(
line => {
val datas: Array[String] = line.split(" ")
val time: String = datas(3)
//给一个前缀 直接获取时间
//包含17/05/2015 的行返回
time.startsWith("17/05/2015")
}
//重新转换集合
).map(
line => {
val words: Array[String] = line.split(" ")
words(6)
}
).collect().foreach(println)
sc.stop()
}
}
8、sample()
def sample(
withReplacement: Boolean,
fraction: Double,
seed: Long = Utils.random.nextLong): RDD[T]
根据指定的规则从数据集中抽取数据
抽取(采样)数据:从指定的数据集中随机抽取部分数据
sample算子可以传递多个参数:
第一个参数表示抽取的方式
抽取放回 : true, 抽取不放回 : false,第二个参数表示每一条数据被抽取的概率
第三个参数表示随机数种子 : 随机数不随机(随机算法)
object Spark01_RDD_OperTransform {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[Int] = sc.makeRDD(1 to 10)
val newRDD: RDD[Int] = rdd.sample(true, 0.5, 1)
newRDD.collect().foreach(println)
sc.stop()
}
}
类似于hbase中的布隆过滤器(Hah碰撞) => hash()
9、distinct()分布式去重
def distinct()(implicit ord: Ordering[T] = null): RDD[T]
def distinct(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]
TODO scala 中的单点 去重 val seen = mutable.HashSet[A]()
TODO distinct算子将数据集中重复数据去除 分布式去重原理
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
val newRDD: RDD[Int] = sc.makeRDD(
List(1, 1, 1), 2
)
List(123,12).distinct
newRDD.distinct(2).collect().foreach(println)
//TODO scala 中的单点 去重 val seen = mutable.HashSet[A]()
// TODO distinct算子将数据集中重复数据去除 分布式去重原理
//map(x => (x, null)).reduceByKey((x, _) => x, numPartitions).map(_._1)
//(1, null),(1, null),(1, null)
//(1, (null, null, null))
// (1, (null, null))
// (1, null)
// 1
sc.stop()
}
10、coalesce() VS repartition()
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
: RDD[T]
数据量缩减分区,用于大数据集过滤后,提高小数据集的执行效率
todo 缩减分区 默认情况下不适用shuffle,数据不会被打乱重新组合(只考虑了分区的多和少,不关心数据是否倾斜,只关注分区数量的多少)
可能会导致数据不均衡,出现数据倾斜,为了防止数据倾斜,可采用shuffle,传递第二个参数。
coalesce算子主要应用于缩减分区,默认不使用shuffle
所以如果扩大分区,不使用shuffle是不可以的。
repartition算子主要应用于扩大分区,肯定有shuffle
11、sortBy()
... 错误: 隐式转换未找到
sortBy算子用于将数据集中的每一条数据增加排序的标记(维度),默认为升序
rdd.sortBy(num=>num).collect().foreach(println)
scala中的元组的排序
Tuple元组排序,默认排序第一个,如果第一个相同,默认排序第二个 ...
object Test { def main(args: Array[String]): Unit = { val list = List( (20,3000), (30,1000), (30,2000), (20,2000), ) // list.sortBy(t => t)(Ordering.Tuple2[Int,Int].reverse).foreach(println) //Tuple元组排序,默认排序第一个,如果第一个相同,默认排序第二个 ... list.sortBy(t =>t)(Ordering.Tuple2[Int,Int]( Ordering.Int, Ordering.Int.reverse )).foreach(println) } }
12、双值类型
交集 并集 差集 zip
def main(args: Array[String]): Unit = { val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000") val sc = new SparkContext(spakConf) val rdd1: RDD[Int] = sc.makeRDD( List(1, 2, 3, 4), 2 ) /*val rdd2: RDD[Int] = sc.makeRDD( List(3, 4, 5, 6), 2 )*/ val rdd2: RDD[Int] = sc.makeRDD( //Can only zip RDDs with same number of elements in each partition //Can't zip RDDs with unequal numbers of partitions List(3, 4, 5, 6,7,8), 3 ) val rdd3 = sc.makeRDD( List("3","4","5","6"),2 ) //交集 [3,4] rdd1.intersection(rdd2).collect().foreach(println) // 并集 [1,2,3,4,3,4,5,6] rdd1.union(rdd2).collect().foreach(println) // 差集 [1,2] rdd1.subtract(rdd2).collect().foreach(println) //拉链[(1,3),(2,4),(3,5),(4,6)] rdd1.zip(rdd2).collect().foreach(println) //双值类型 必须保证两个集合中的类型要一样 // rdd1.union(rdd3) //zip对类型没有要求 rdd1.zip(rdd3) // newRDD.collect().foreach(println) sc.stop() }
13、partitionBy()
TODO partitionBy算子表示将数据按照指定的分区规则进行重分区
repartition : 重分区(分区数量)
partitionBy : 重分区(数据的位置), 数据路由(Hash定位),分区器
如果想要让数据重新进行分区,那么需要传递分区规则对象(分区器)
Spark中默认常用的分区器:RangePartitioner & HashPartitioner(默认)
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
val rdd1: RDD[(String, Int)] = sc.makeRDD(
List(
("a", 1), ("b", 2),
("c", 3), ("d", 4)
), 2
)
// TODO partitionBy算子表示将数据按照指定的分区规则进行重分区
// repartition : 重分区(分区数量)
// partitionBy : 重分区(数据的位置), 数据路由(Hash定位),分区器
// 如果想要让数据重新进行分区,那么需要传递分区规则对象(分区器)
// Spark中默认常用的分区器:RangePartitioner & HashPartitioner(默认)
rdd1.partitionBy(new HashPartitioner(2)).saveAsTextFile("output")
sc.stop()
}
如果重分区的分区器和当前RDD的分区器一样怎么办?
14、reduceByKey()
可以将数据按照相同的Key对Value进行聚合
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
// TODO reduceByKey 算子用于将数据集中相同的key的value数据聚合在一起实现两两聚合
val rdd: RDD[(String, Int)] = sc.makeRDD(
List(
("a", 1), ("a", 2), ("a", 3)
)
)
//spark 的算子有很多是以byKey结尾的,就是说将K-V独立使用
rdd.reduceByKey(_+_).collect().foreach(println)
sc.stop()
}
15、groupByKey()
将数据源的数据根据key对value进行分组
def main(args: Array[String]): Unit = { val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000") val sc = new SparkContext(spakConf) val rdd1: RDD[(String, Int)] = sc.makeRDD( List( ("a", 1), ("a", 2), ("a", 3) ), 2 ) // groupByKey算子会将数据按照key对value进行分组 //list中存储的元素是一个个元组 // rdd1.groupBy(_._1).collect().foreach(println) // TODO groupByKey算子也可以实现 WordCount ( 3 / 10 ) rdd1.groupByKey().collect().foreach(println) // TODO groupByKey算子也可以实现 WordCount ( 3 / 10 ) rdd1.groupByKey().mapValues(_.sum).collect().foreach(println) sc.stop() }
groupByKey VS reduceByKey
从shuffle的角度:reduceByKey和groupByKey都存在shuffle的操作,但是reduceByKey可以在shuffle前对分区内相同key的数据进行预聚合(combine)功能,这样会减少落盘的数据量,而groupByKey只是进行分组,不存在数据量减少的问题,reduceByKey性能比较高。
从功能的角度:reduceByKey其实包含分组和聚合的功能。groupByKey只能分组,不能聚合,所以在分组聚合的场合下,推荐使用reduceByKey,如果仅仅是分组而不需要聚合。那么还是只能使用groupByKey
16、aggregateByKey() VS foldByKey()
将数据根据不同的规则进行分区内计算和分区间计算
当分区内计算规则和分区间计算规则相同时,aggregateByKey就可以简化为foldByKey
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[(String, Int)] = sc.makeRDD(
List(
("a", 1), ("b", 2), ("a", 3),
("b", 4), ("b", 5), ("a", 6)
), 2
)
//TODO 数据集中相同Key的分区内的最大值, 分区间求和
// TODO 如果分区内和分区间的计算规则不一定相同时,Spark提供了一个算子来实现。
//分区内求最大值 分区间求和
//aggregateByKey 存在参数柯里化, 有多个参数列表
//第一个参数列表中有一个参数
// 计算初始值
//第二个参数列表中有两个参数
//第一个参数表示:分区内计算规则
//第二个参数表示 :分区间计算规则
rdd.aggregateByKey(0)(
//需要有初始值 用于比较确定比较大的值 两两迭代进行比较
(x,y) => math.max(x,y),
(x,y) => x + y
)
rdd.groupByKey().mapValues(_.sum).collect().foreach(println)
sc.stop()
}
初始值不一样
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[(String, Int)] = sc.makeRDD(
List(
("a", 1), ("b", 2), ("a", 3),
("b", 4), ("b", 5), ("a", 6)
), 2
)
//TODO 数据集中相同Key的分区内的最大值, 分区间求和
// TODO 如果分区内和分区间的计算规则不一定相同时,Spark提供了一个算子来实现。
//分区内求最大值 分区间求和
//aggregateByKey 存在参数柯里化, 有多个参数列表
//第一个参数列表中有一个参数
// 计算初始值
//第二个参数列表中有两个参数
//第一个参数表示:分区内计算规则
//第二个参数表示 :分区间计算规则
rdd.aggregateByKey(5)(
//需要有初始值 用于比较确定比较大的值 两两迭代进行比较
(x,y) => math.max(x,y),
(x,y) => x + y
).collect().foreach(println)
sc.stop()
}
//aggregateByKey实现wordCount
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[(String, Int)] = sc.makeRDD(
List(
("a", 1), ("b", 2), ("a", 3),
("b", 4), ("b", 5), ("a", 6)
), 2
)
//TODO 数据集中相同Key的分区内的最大值, 分区间求和
// TODO 如果分区内和分区间的计算规则不一定相同时,Spark提供了一个算子来实现。
//分区内求最大值 分区间求和
//aggregateByKey 存在参数柯里化, 有多个参数列表
//第一个参数列表中有一个参数
// 计算初始值
//第二个参数列表中有两个参数
//第一个参数表示:分区内计算规则
//第二个参数表示 :分区间计算规则
//不需要看key 底层默认识别按照key进行统计
rdd.aggregateByKey(0)(
//需要有初始值 用于比较确定比较大的值 两两迭代进行比较
//相同key的求最大值 分区内的相同的key的相加
(x,y) => x + y,
//分区间的 相同的key的相加
(x,y) => x + y
).collect().foreach(println)
sc.stop()
}
17、combineByKey
需求:求相同key的平均值 GroupByKey() ReduceByKey()
flatmap() map() mapPartition()等方法用的比较多,因为可以转换数据的格式
def main(args: Array[String]): Unit = { val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000") val sc = new SparkContext(spakConf) val rdd: RDD[(String, Int)] = sc.makeRDD( List( ("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6) ), 2 ) // TODO 求相同key的平均值 /*val groupRDD: RDD[(String, Iterable[Int])] = rdd.groupByKey() val finalRDD: RDD[(String, Int)] = groupRDD.mapValues( list => list.sum / list.size )*/ // ("a", (1,1))), ("a", 2)), ("b", (3,1)),("b", 3) //("a", (1,1))), ("a", (2,1))), ("b", (3,1)),("b", (3,1)) //使变成元组,可以结合元组求出相同key的长度 val newRDD: RDD[(String, (Int, Int))] = rdd.mapValues((_, 1)) // newRDD.collect().foreach(println) //两两聚合 key是String V是tuple //数据格式的改变 newRDD.reduceByKey( (t1,t2) => { (t1._1 + t2._1 ,t1._2 + t2._2) } ).mapValues{ //处理求平均值 case (total , cnt) => { total / cnt } } .collect().foreach(println) // groupRDD.partitionBy(new HashPartitioner(2)).saveAsTextFile("output") sc.stop() }
def main(args: Array[String]): Unit = { val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000") val sc = new SparkContext(spakConf) val rdd: RDD[(String, Int)] = sc.makeRDD( List( ("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6) ), 2 ) // TODO 如果在计算过程中,只需要对相同key的第一条数据进行处理的功能,可以采用特殊的算子 // combineByKey算子需要传递三个参数: // 第一个参数表示 : 第一个V的处理逻辑 // 第二个参数表示 : 分区内计算规则 // 第三个参数表示 : 分区间计算规则 rdd.combineByKey( //第一个V的逻辑 (v) => (v,1), //除了第一个V的逻辑 t:(Int,Int) 为第一个v的类型为tuple // 因为第一个参数可以进行转变 //("a",(3,2)) ("b",3) //("b",(9,1)) ("a",6) (t:(Int,Int) , v) => { (t._1 + v , t._2 + 1) }, //分区内计算完之后 是两个tuple //("a",(3,2)) ("b",(3,1)) //("b",(9,2)) ("a",(6.1)) (t1:(Int,Int),t2:(Int,Int)) =>{ (t1._1 + t2._1 , t1._2 + t2._2) } ).collect().foreach(println)
wordCount (combineByKey)
def main(args: Array[String]): Unit = { val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000") val sc = new SparkContext(spakConf) val rdd: RDD[(String, Int)] = sc.makeRDD( List( ("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6) ), 2 ) // TODO 如果在计算过程中,只需要对相同key的第一条数据进行处理的功能,可以采用特殊的算子 // combineByKey算子需要传递三个参数: // 第一个参数表示 : 第一个V的处理逻辑 // 第二个参数表示 : 分区内计算规则 // 第三个参数表示 : 分区间计算规则 val finalRDD: RDD[(String, Int)] = rdd.combineByKey( v => v, (num1: (Int), num2) => { num1 + num2 }, (t1: (Int), t2: (Int)) => { t1 + t2 } ) finalRDD.collect().foreach(println) sc.stop() }
def main(args: Array[String]): Unit = { val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000") val sc = new SparkContext(spakConf) val rdd: RDD[(String, Int)] = sc.makeRDD( List( ("a", 1), ("a", 2), ("b", 3), ("b", 4), ("b", 5), ("a", 6) ), 2 ) //相同的key进行聚合 rdd.reduceByKey(_ + _).collect().foreach(println) //aggregateByKey() rdd.aggregateByKey(0)( (_+_),(_+_) ).collect().foreach(println) //foldByKey rdd.foldByKey(0)(_+_).collect().foreach(println) //combineByKey rdd.combineByKey(v => v,(num :(Int),v) => num + v ,(v1:(Int),v2:(Int)) => v1 + v2).collect().foreach(println) sc.stop() }
18、sortByKey()
在一个(K,V)的RDD上调用,K必须实现Ordered接口(特质),返回一个按照key进行排序的 只是对key进行排序 和v没有关系
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
/*val rdd: RDD[(String, Int)] = sc.makeRDD(
List(
("a", 1), ("a", 2), ("b", 3),
("b", 4), ("b", 5), ("a", 6)
), 2
)*/
val rdd: RDD[(User, Int)] = sc.makeRDD(
List(
(new User(), 1), (new User(), 6), (new User(), 3),
(new User(), 4), (new User(), 5), (new User(), 2)
), 2
)
//sortByKey算子是根据key进行排序 //没有指定排序规则 sortByKey 会报红
rdd.sortByKey().collect().foreach(println)
sc.stop()
}
class User extends Ordered[User]{
var age = 20
override def compare(that: User): Int = {
-1
}
}
18、双Value 算子 join()
Join 为双Value类型操作
join算子体现的是数据的关系
join算子将两个数据集中相同的key的数据,连接在一起 不同key之间没有关系join算子可能存在笛卡尔乘积 join算子可能存在shuffle操作 如果能够使用其他算子实现的功能,那么不推荐使用joinleftOuterJoin rightOuterJoin fullOuterJoin
cogroup() 多数据集进行相连
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory", "2147480000")
val sc = new SparkContext(spakConf)
val rdd1: RDD[(String, Int)] = sc.makeRDD(
List(
("a", 1), ("b", 2), ("c", 3)
)
)
val rdd2: RDD[(String, Int)] = sc.makeRDD(
List(
("d", 5), ("c", 6),("a", 4)
)
)
//join算子体现的是数据的关系
// join算子将两个数据集中相同的key的数据,连接在一起
rdd1.join(rdd2).collect().foreach(println)
rdd1.leftOuterJoin(rdd2).collect().foreach(println)
rdd1.rightOuterJoin(rdd2).collect().foreach(println)
rdd1.fullOuterJoin(rdd2).collect().foreach(println)
sc.stop()
}
案例:
agent.log:时间戳,省份,城市,用户,广告,中间字段使用空格分隔。
先分组后统计 优化之后先统计在分组 工作中 一定要先统计在分组 先聚合在分组
性能会大大提升
行动算子
所谓的行动(Action)算子,其实就是RDD用于触发作业执行的方法,类似于IO中的read方法,转换算子的返回值为RDD,行动算子的返回值为具体的结果
行动算子和作业的关系 :1 对 1
1、reduce()
没有key的概念 行动算子的返回值为具体的结果
def main(args: Array[String]): Unit = { val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000") val sc = new SparkContext(spakConf) val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2) // TODO reduce算子可以触发作业的执行,分区内先两两计算,分区间再两两计算 // 分区内计算都是在Executor。 // 分区间计算都是在Driver val result1: Int = rdd.reduce( (x, y) => { println(Thread.currentThread().getName+ x + " + " + y) x + y } ) println(result1) sc.stop() }
2、
count() 返回RDD中元素的个数
first() 返回RDD中的第一个元素
take() 返回一个由RDD的前n个元素组成的数组
takeOrdered()返回该RDD排序后的前n个元素组成的数组
3、aggregate()
aggregateByKey算子的初始值只能参与分区内的计算,而aggregate算子的初始值还可以参与分区间的计算
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4),2)
//结果10
val result: Int = rdd.aggregate(0)(
_ + _,
_ + _
)
// aggregateByKey算子的初始值只能参与分区内的计算,而aggregate算子的初始值还可以参与分区间的计算
//结果40
val result1: Int = rdd.aggregate(10)(_ + _, _ + _)
println(result1)
sc.stop()
}
4、countByValue VS countByKey
countByValue算子可以实现 WordCount
countByKey算子可以实现 WordCount
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
/*val rdd: RDD[(String,Int)] = sc.makeRDD(
List(
("a", 1), ("a", 1), ("a",1 )
), 2
)
val stringToLong: collection.Map[String, Long] = rdd.countByKey()
println(stringToLong)*/
//单Value(单一数据集)数据操作,双Value(多个数据集)数据操作
val rdd: RDD[(String, Int)] = sc.makeRDD(
List(
("a", 1), ("b", 1), ("c", 1)
), 2
)
println(rdd.countByValue())
sc.stop()
}
5、save
rdd.saveAsTextFile("output1")
rdd.saveAsObjectFile("output2")
rdd.saveAsSequenceFile("output3")
6、foreach()
def main(args: Array[String]): Unit = { val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000") val sc = new SparkContext(spakConf) // 单Value(单一数据集)数据操作,双Value(多个数据集)数据操作 val rdd: RDD[Int] = sc.makeRDD( List( //tuple也属于对象 1,2,3,4 ),2 ) // collect方法用于按照分区顺序采集数据 // foreach方法 rdd.collect().foreach(println) println("********************************") rdd.foreach(println) sc.stop() }
7、序列化
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local[*]").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
// 单Value(单一数据集)数据操作,双Value(多个数据集)数据操作
val rdd: RDD[Int] = sc.makeRDD(
List(
//tuple也属于对象
1,2,3,4
),2
)
//new User() 是在主线程中被创建的 而主线程的main方法是在Driver端被调用的
//user实在Driver端
val user: User = new User()
// foreach算子表示对数据集进行分布式循环遍历,遍历的逻辑是在不同的节点(Executor)上执行
// 算子内部的执行逻辑在Executor端执行的
// 如果Executor端使用到了Driver端的数据,那么必须保证使用的数据能够序列化
rdd.foreach(
num => {
println(user.age + num)
}
)
sc.stop()
}
// scala语言就是一个代码生成器
// 样例类自动实现可序列化接口
case class User(){
val age = 30
}
9、闭包检测
Java中的异常打印到控制台什么意思?为了程序的健壮性
object Spark01_RDD_Serial {
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[Int] = sc.makeRDD(List[Int]())
// Driver
val user = new User()
// TODO 闭包
// Spark在运行作业之前,会判断程序逻辑中是否包含闭包
// 如果存在闭包操作,那么需要检测闭包中使用的数据是否能够序列化,如果不能,那么发生错误
// 这个操作称之为闭包检测
rdd.foreach(
num => {
//Executor
println(user.age + num)
}
)
sc.stop()
}
// scala语言就是一个代码生成器
// 样例类自动实现可序列化接口
class User(){
val age = 30
}
}
def main(args: Array[String]): Unit = {
val spakConf = new SparkConf().setMaster("local").setAppName("WordCount").set("spark.testing.memory","2147480000")
val sc = new SparkContext(spakConf)
val rdd: RDD[String] = sc.makeRDD(
List(
"Hello", "Spark", "Hive", "Scala"
)
)
rdd.filter(_.startsWith("H")).collect().foreach(println)
new Search("H").matchData(rdd)
sc.stop()
}
class Search(q :String) extends Serializable {
def matchData(rdd : RDD[String]) : Unit = {
rdd.filter(_.startsWith(this.q)).collect().foreach(println)
}
}
class VS object
成员变量一般声明在class中,静态一般声明在object中
shuffle()