文章目录
回顾
cache方法,没有生成新的RDD,也没有触发任务执行,只会标记该RDD分区对应的数据(第一次触发Action时)放入到内存
checkpoint方法,没有生成新的RDD,也是没有触发Action,也是标记以后触发Action时会将数据保存到HDFS中
根据IP地址计算归属地
IP转换成十进制
二分法查找
广播变量(广播出去的内容一旦广播出去,就不能改变了),如果需要实时改变的规则,可以将规则放到Redis
foreach,foreachPartition是Action,会触发任务提交。(Action的特点是会触发任务,有数据计算的结果产生)
collect,take,count(收集到Driver端的Action)
自定义排序
方法一
将数据封装到对象中,通过对象比较大小来进行排序。
package day5
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort1 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort1").setMaster("local[3]")
val sc = new SparkContext(conf)
//排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
val users = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 999", "laoyang 29 99")
//将Driver端的数据并行化变成RDD
val lines: RDD[String] = sc.parallelize(users)
//切分整理数据
val userRDD: RDD[User] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
//(name, age, fv)
new User(name, age, fv)
})
//只是按照颜值进行排序。不满足要求
//tpRDD.sortBy(tp => tp._3, false)
//将RDD里面装的User类型的数据进行排序
val sorted = userRDD.sortBy(u => u)
val r = sorted.collect()
println(r.toBuffer)
sc.stop()
}
}
class User(val name: String, val age: Int, val fv: Int) extends Ordered[User] with Serializable {
override def compare(that: User): Int = {
if(this.fv == that.fv){
this.age - that.age
}else{
-(this.fv -that.fv)
}
}
override def toString: String = s"name: $name, age: $age, fv: $fv"
}
方式二
通过传入排序规则,对元组中的数据进行排序。
package day5
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort2 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort2").setMaster("local[3]")
val sc = new SparkContext(conf)
//排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
val users = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 999", "laoyang 29 99")
//将Driver端的数据并行化变成RDD
val lines: RDD[String] = sc.parallelize(users)
//切分整理数据
val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
(name, age, fv)
})
//排序(传入了一个排序规则,不会改变数据的格式,只会改变顺序)
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => new Boy(tp._2, tp._3))
val r = sorted.collect()
println(r.toBuffer)
sc.stop()
}
}
class Boy(val age: Int, val fv: Int) extends Ordered[Boy] with Serializable {
override def compare(that: Boy): Int = {
if(this.fv == that.fv){
this.age - that.age
}else{
-(this.fv -that.fv)
}
}
}
方法三
通过样例类传递比较规则。
package day5
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort3 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort3").setMaster("local[3]")
val sc = new SparkContext(conf)
//排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
val users = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 999", "laoyang 29 99")
//将Driver端的数据并行化变成RDD
val lines: RDD[String] = sc.parallelize(users)
//切分整理数据
val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
(name, age, fv)
})
//排序(传入了一个排序规则,不会改变数据的格式,只会改变顺序)
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => Man(tp._2, tp._3))
val r = sorted.collect()
println(r.toBuffer)
sc.stop()
}
}
//样例类,默认实现了序列化。
case class Man(age: Int, fv: Int) extends Ordered[Man] {
override def compare(that: Man): Int = {
if(this.fv == that.fv){
this.age - that.age
}else{
-(this.fv -that.fv)
}
}
}
方法四
通过导入隐式转换进行排序。
package day5
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort4 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort4").setMaster("local[3]")
val sc = new SparkContext(conf)
//排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
val users = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 999", "laoyang 29 99")
//将Driver端的数据并行化变成RDD
val lines: RDD[String] = sc.parallelize(users)
//切分整理数据
val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
(name, age, fv)
})
//排序(传入了一个排序规则,不会改变数据的格式,只会改变顺序)
import SortRules.OrderingXianRou //调入隐式转换
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => XianRou(tp._2, tp._3))
val r = sorted.collect()
println(r.toBuffer)
sc.stop()
}
}
//样例类
case class XianRou(age: Int, fv: Int)
object SortRules {
implicit object OrderingXianRou extends Ordering[XianRou]{
override def compare(x: XianRou, y: XianRou): Int = {
if(x.fv == y.fv){
x.age - y.age
}else{
y.fv -x.fv
}
}
}
}
方法五
利用元组的比较规则进行比较。
package day5
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort5 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort5").setMaster("local[3]")
val sc = new SparkContext(conf)
//排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
val users = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 999", "laoyang 29 99")
//将Driver端的数据并行化变成RDD
val lines: RDD[String] = sc.parallelize(users)
//切分整理数据
val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
(name, age, fv)
})
//充分利用元组的比较规则,元组的比较规则:先比第一个,相等再比第二个
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => (-tp._3, tp._2))
val r = sorted.collect()
println(r.toBuffer)
sc.stop()
}
}
方法六
在上下文搜索隐式转换,通过隐式转换,改变元组的数据格式。
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object CustomSort6 {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("CustomSort6").setMaster("local[3]")
val sc = new SparkContext(conf)
//排序规则:首先按照颜值的降序,如果颜值相等,再按照年龄的升序
val users = Array("laoduan 30 99", "laozhao 29 9999", "laozhang 28 999", "laoyang 29 99")
//将Driver端的数据并行化变成RDD
val lines: RDD[String] = sc.parallelize(users)
//切分整理数据
val tpRDD: RDD[(String, Int, Int)] = lines.map(line => {
val fields = line.split(" ")
val name = fields(0)
val age = fields(1).toInt
val fv = fields(2).toInt
(name, age, fv)
})
//Ordering[(Int, Int)]最终比较的规则格式
//on[(String, Int, Int)]未比较之前的数据格式
//(t=>(-t._3, t._2))怎样将规则转换成想要比较的规则。
implicit val rules = Ordering[(Int, Int)].on[(String, Int, Int)](t => (-t._3, t._2))
val sorted: RDD[(String, Int, Int)] = tpRDD.sortBy(tp => tp)
val r = sorted.collect()
println(r.toBuffer)
sc.stop()
}
}
JDBC RDD
hadoop处理方式:关系型数据库==》分布式文件系统==》运用yarn对数据进行mapreduce
JDBC RDD优点:可以直接从关系型数据库中读取数据。不用再使用sqoop数据迁移工具。
package day5
import java.sql.DriverManager
import org.apache.spark.rdd.{JdbcRDD, RDD}
import org.apache.spark.{SparkConf, SparkContext}
object JdbcRDDDemo {
val getConn = () => {
DriverManager.getConnection("jdbc:mysql://localhost:3306/spark_about?characterEncoding=UTF-8","root","123")
}
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setAppName("JDBC_RDD").setMaster("local[3]")
val sc = new SparkContext(conf)
//创建RDD,这个RDD会记录以后从MySql中读取数据
//new RDD后,里面没有真正的数据,而是告诉这个RDD,以后触发runJob时,以后从什么地方读取数据。
val jdbcRDD: RDD[(Int, String, Int)] = new JdbcRDD(
sc,
getConn,
"SELECT * FROM logs WHERE id > ? AND id <= ?",
1,//范围下线
5,//范围上线
2,//分区数量
rs => {
val id = rs.getInt(1)
val name = rs.getString(2)
val age = rs.getInt(3)
(id, name, age)
}
)
//触发Action
val r = jdbcRDD.collect()
println(r.toBuffer)
sc.stop()
}
}
注意事项:当使用SQL语句:SELECT * FROM logs WHERE id >= ? AND id < ?,进行查询时,得到的结果为:ArrayBuffer((1,张三,18), (3,王五,22), (4,赵六,19)),下图可以解释这种结果出现的原因。
Spark的执行过程简介
Spark任务执行的流程,四个步骤
1.构建DAG(调用RDD上的方法)
2.DAGScheduler将切分Stage,切分的依据是Shuffle,将Stage中生成的Task以TaskSet的形式给TaskScheduler
3.TaskScheduler调度Task(根据资源情况将Task调度到相应的Executor中)
4.Executor接收Task,然后将Task丢入到线程池中执行。
DAG有向无环图流程如下图:(有方向,没有闭环)
1.DAG描述多个RDD的转换过程,任务执行时,可以按照DAG的描述,执行正真的计算(数据被操作的一个过程)。
2.DAG是有边界的:开始(通过SparkContext创建的RDD),结束(触发Action,调用runJob就是一个完整的DAG就形成了,一旦触发Action就形成了一个完整的DAG)。
3.一个Spark Application中是有多少个DAG:一到多个(取决于触发了多少次Action)
4.在一个DAG中可能产生多种不同类型和功能的Task,会有不同的阶段。
5.一个RDD只是描述了数据计算过程中的一个环节,而DAG由一到多个RDD组成,描述了数据计算过程中的所有环节(过程)
DAGScheduler
将一个DAG切分成一到多个Stage,DAGScheduler切分的依据是Shuffle(宽依赖)。
为什么要切分Stage?
一个复杂的业务逻辑(将多台机器上具有相同属性的数据,聚合到一台机器上:shuffle)要分阶段进行。如果有Shuffle,那么就意味着前面阶段产生的结果后,才能执行下一个阶段,下一个阶段的计算要依赖上一个阶段的数据。在同一个Stage中,会有多个算子可以合并在一起,我们称其为pipeline(流水线:严格按照流程,顺序执行)。
RDD的依赖关系
RDD和它依赖的父RDD(s)的关系有两种不同的类型,即窄依赖和宽依赖。
shuffle的定义:
shuffle的含义是洗牌,将数据打散,父RDD的一个分区中的数据,如果给了多个子RDD的分区,就是Shuffle;
shuffle会有网络传输数据,但是可有网络传数据并不意味着就是Shuffle(窄依赖也会有网络传输);
join
分区中的数据必须是(k,v)类型才能join。
上图执行过程中:产生了3个Stage,6个Task。是宽依赖。
val rdd1 = sc.parallelize(List((“tom”, 1),(“kitty”, 1),(“tom”, 2),(“kitty”,2)), 2)
val rdd2 = sc.parallelize(List((“tom”, 3),(“kitty”, 3),(“tom”, 4),(“kitty”,4)), 2)
val rdd3 = rdd1.groupByKey()
val rdd4 = rdd2.groupByKey()
val rdd5 = rdd3.join(rdd4)
rdd5.saveAsTextFile(“hdfs://node1:9000/test_join2”)
结果:
上述产生:3个Stage,6个Task;
线程池
package cn.edu360.spark;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
/*
线程池出现的原因:
在频繁创建线程的情况下,线程的创建和销毁占用比较多的CPU资源,造成了一种浪费。
线程池的创建可以减少线程的创建与销毁,而且不影响线程的调用。
*/
public static void main(String[] args){
//创建一个单线程的线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
for(int i = 0; i<=10;i++){
pool.execute(new Runnable() {
@Override
public void run() {
//打印当前线程的名称
System.out.println(Thread.currentThread().getName());
try{
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "is over");
}
});
}
System.out.println("all Task is submited");
pool.shutdownNow();//停止当前线程池的使用
}
}
public class ThreadPoolDemo {
/*
线程池出现的原因:
在频繁创建线程的情况下,线程的创建和销毁占用比较多的CPU资源,造成了一种浪费。
线程池的创建可以减少线程的创建与销毁,而且不影响线程的调用。
*/
public static void main(String[] args){
//固定大小的线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
for(int i = 0; i<=10;i++){
pool.execute(new Runnable() {
@Override
public void run() {
//打印当前线程的名称
System.out.println(Thread.currentThread().getName());
try{
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "is over");
}
});
}
System.out.println("all Task is submited");
}
}
结果:
pool-1-thread-1
all Task is submited
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
pool-1-thread-1is over
pool-1-thread-2is over
pool-1-thread-4is over
pool-1-thread-4
pool-1-thread-3is over
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
pool-1-thread-5is over
pool-1-thread-5
pool-1-thread-1is over
pool-1-thread-2is over
pool-1-thread-3is over
pool-1-thread-4is over
pool-1-thread-1
pool-1-thread-5is over
pool-1-thread-1is over
分析:线程1-5会出现2次,因为线程池只有5个线程。
public class ThreadPoolDemo {
/*
线程池出现的原因:
在频繁创建线程的情况下,线程的创建和销毁占用比较多的CPU资源,造成了一种浪费。
线程池的创建可以减少线程的创建与销毁,而且不影响线程的调用。
*/
public static void main(String[] args){
//创建一个单线程的线程池
//ExecutorService pool = Executors.newSingleThreadExecutor();
//固定大小的线程池
//ExecutorService pool = Executors.newFixedThreadPool(5);
//可缓冲的线程池(可以有任意多个线程)
ExecutorService pool = Executors.newCachedThreadPool();
for(int i = 0; i<=10;i++){
pool.execute(new Runnable() {
@Override
public void run() {
//打印当前线程的名称
System.out.println(Thread.currentThread().getName());
try{
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "is over");
}
});
}
System.out.println("all Task is submited");
}
}
结果:在一瞬间可以给多个线程
pool-1-thread-1
all Task is submited
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
pool-1-thread-6
pool-1-thread-7
pool-1-thread-8
pool-1-thread-9
pool-1-thread-10
pool-1-thread-11
pool-1-thread-5is over
pool-1-thread-9is over
pool-1-thread-10is over
pool-1-thread-11is over
pool-1-thread-4is over
pool-1-thread-8is over
pool-1-thread-7is over
pool-1-thread-6is over
pool-1-thread-2is over
pool-1-thread-3is over
pool-1-thread-1is over
序列化
应用场景:网络传输,对象保存到磁盘。
package cn.edu360
import java.io.{FileInputStream, FileOutputStream, ObjectInputStream, ObjectOutputStream}
class MapTask extends Serializable{
//以后重哪里了读取数据
//以后该如何执行,根据RDD的转换关系(调用那个方法,传入了什么函数)
def m1(path: String): String = {
path.toString
}
def m2(line: String): Array[String] = {
line.split(" ")
}
}
object SerTask {
def main(args: Array[String]): Unit = {
//new一个实例,然后打印她的hashcode值
//在Driver端创建这个实例
//序列化后发生出去,发送到Executor,Executor接收后,反序列化,用一个实现了Runnable接口一个类包装一下,然后丢到线程池中
val t = new MapTask
println(t)
val oos = new ObjectOutputStream(new FileOutputStream("./t"))
oos.writeObject(t)
oos.flush()
oos.close()
val ois1 = new ObjectInputStream(new FileInputStream("./t"))
val o1 = ois1.readObject()
println(o1)
ois1.close()
val ois2 = new ObjectInputStream(new FileInputStream("./t"))
val o2 = ois2.readObject()
println(o2)
ois2.close()
}
}
Task流程:
在Driver端创建这个实例Task,序列化后发送出去,发生个Executor,Executor接收后,反序列化,用一个实现了Runnable接口一个类包装一下,然后丢到线程池中。
Spark运行相关问题
1.SparkContext那一端生成的?
Driver端
2.DAG是在那一端生成的?
Driver端
3.RDD是在那一端生成的?
Driver端
4.广播变量在那一端调用的方法进行广播的?
Driver端
5.要广播的数据应该在那一端创建好再广播呢?
Driver端
6.调用RDD端算子()是在那一端调用的?
Driver端
7.RDD在调用Transformation和Action时需要传入一个函数,函数是在那一端声明和传入的?
Driver端
8.RDD在调用Transformationhe Action时需要传入函数,请问传入的函数是在那一端执行了函数的业务逻辑?
Executor中的Task中调用的
9.自定义的分区器这个类是在那一端实例化的?
Driver端
10.分区器中的getParition方法在那一端调用的呢?
Executor中的Task中调用的
11.Task是在那一端生成的呢?
Driver端
12.DAG是在那一端构建好的并且被切分成一到多个Stage的
Driver端
13.DAG是哪一个类完成的切分Stage的功能?
DAGScheduler
14.DAGScheduler将切分好的Stage以什么样的形式给TaskScheduler
TaskSet
Spark的设计就是基于这个抽象的数据集(RDD),你操作RDD这个抽象的数据集,就像操作一个本地集合一样,Spark包底层的细节都隐藏起来的(任务调度、Task执行,任务失败重试等待),开发者使用起来更加方便简洁。操作RDD,其实是对每个分区进行操作,分区会生成Task,Task会调度Executor上执行相关的计算逻辑,进而对数据进操作。