def textFile(
path: String,
minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {
assertNotStopped()
hadoopFile(path, classOf[TextInputFormat], classOf[LongWritable], classOf[Text],
minPartitions).map(pair => pair._2.toString).setName(path) //这里由于偏移量没用所以用_2
}
这是一个从左hdfs 向右的过程
hdfs sc.textFile (带偏移量) map 结果(去掉偏移量)生成了一个RDD
HadoopRDD(LongWriteable,Text) MapPartitionsRDD[String]
hello lilei (0,"hello lilei") “hello lilei”
hello lucy (1,"hello lucy") "hello lucy"
上面右接下面左
flatMap(_.split(" ")) map((_,1)) reduceByKey(_+_)
MapPartitionsRDD[String] MapPartitionsRDD[String,Int] ShuffledRDD[String,Int,Int]
"hello,lilei,hello,lucy" ("hello",1),("lilei",1).... 两个过程,分区聚合,全局聚合,中间是shuffle过程,洗牌,分区中 这里会宽依赖
上面右接下面左
saveAsTextFile("hdfs://...")
MapPartitionsRDD[String,Int]
初始设置分区,或者文件分区时,保存默认也是这些分区
整个过程创建了6个RDD
//hdoopFile 返回一个RDD
def hadoopFile[K, V](
path: String,
inputFormatClass: Class[_ <: InputFormat[K, V]],
keyClass: Class[K],
valueClass: Class[V],
minPartitions: Int = defaultMinPartitions): RDD[(K, V)] = withScope {
assertNotStopped()
// A Hadoop configuration can be about 10 KB, which is pretty big, so broadcast it.
val confBroadcast = broadcast(new SerializableConfiguration(hadoopConfiguration))
val setInputPathsFunc = (jobConf: JobConf) => FileInputFormat.setInputPaths(jobConf, path)
new HadoopRDD(
this,
confBroadcast,
Some(setInputPathsFunc),
inputFormatClass,
keyClass,
valueClass,
minPartitions).setName(path)
}
一个项目源码执行流程(主要是sparksubmit与sparkContext)
1、SparkSubmit 执行runmain()方法 根据传入的类名找到jar中的类,执行main方法
2、执行项目(以WC为例),执行WC的main 方法
3、WC的main方法中 一开始是 new 了一个SparkConf 与SparkContext
4、SparkContext 通过createSparkEnv() 调用SparkEnv的createDriverEnv()
5、在SparkEnv中SparkEnv的createDriverEnv()调用create方法
create方法中调用AkkaUtils
6、在AkkaUtils里通过createActorSystem来创建Actor
7、SparkContext 通过creatTaskScheduler 来new 一个TaskSchedulerImpl(任务调度器)和SparkDeploySchedulerBackend(后端调度器)
8、SparkContext 通过new来创建了DAGScheduler(划分stage的)
TaskScheduler 的流程
启动TaskScheduler 是 TaskScheduler 的start方法
在Start方法中:先启动后端调度器 也就是SparkDeploySchedulerBackend (后端调度器的一个实现方法),
在SparkDeploySchedulerBackend的start方法中,调用父类的start方法(super.start())。父类的start方法中创建了driveractor。
然后在SparkDeploySchedulerBackend的start方法中, 创建了Command(准备了执行任务线程的类名)
ApplicationDescription(封装了Excutor需要用的参数,像占用内存大小,核数),接下来创建了一个
Appclient的实例将配置信息(包括appdesc)传进去。然后调用clien.start()。
在client 中创建了client的Actor。
在后端调度器这,一共创建了2个Actor,driverActor(与excutor通信),clientActor(与master通信)
后端调度器是在driver端启动。也就是driver端启动的driverActor和clientActor
通过sparkenv调用scheduler
client端与master端通信
1、在prestart方法中 主要是registerWithMaster,向master注册,这里有高可用集群,它会遍历所有master的url,然后与活跃的master发连接
2、master 端接收到client的注册消息,将client的信息加入到hashset(去重)与ArrayBuffer中(也就是存两份),还要信息持久化。然后master
向client发送响应消息(携带appid,和masterurl)。开始任务调度
3、client 接收到master返回的注册响应消息,先改masterurl,再启动一个监听器,连接appid
接上面2后的任务调度(注册成功,master返回消息后 master启动)
只要新的计算资源加进来时都会发生资源调度。
两种调度方式 spreadOutApps (尽量打散,默认) (尽量集中)
spreadOutApps :
1、从waitingApps(client保存的Arraybuffer)过滤取出合适的apps(活跃,内存是否足够,空闲的cpu核数),用来启动executor
2、尽量打散,把任务用循环的方式调度到尽量多的节点上。(尽量集中,意思是一个节点有多少核数,有限分配多少。尽量少节点)
3、在节点(worker)上启动executor launchExecutor
work启动executor
先在master中
1、将executor的描述信息加入workerinfo中
2、master向worker发送消息,告诉worker启动executor
3、同时向client发送消息,告诉client executor已经启动
再 worker中接收到master的启动executor的消息。
1、worker接收到master消息,
2、内部创建一个空间,new 一个 executorrunner
再的同时 client接收到master的消息 (executor已经启动)
1、client接收到后,监听executor的注册
CoarseGrainedExecutorBackend 中启动的executor进程的类
在main方法中,主要是封装参数,与执行run方法
在run方法中:创建Actorsystem对象 创建actor
在生命周期方法中
prestart: 向driver发送注册消息 ,接收在CoarseGrainedSchedulerBackend 中,
在CoarseGrainedSchedulerBackend中接收到上面发送的消息后,执行makeoffers方法(就是为提交任务做准备)
RDD的生成
先从一个action的算子看起,向saveasfile
1、拿到一个输出流(writer),
2、调用sparkcontext的 runjob方法 传入 writertoFile的函数
3、调用dagscheduler 的runjob方法,进行stage的切分。
4、调用submitjob,开始提交作业
5、submitjob中,首先得到分区数 , 调用eventprocessLoop(任务处理器,循环取出数据)
6、在dagscheduler中切分stage