以SparkContext为程序运行的总入口,在SparkContext的初始化过程中,Spark会分别创建DAGScheduler作业调度和TaskScheduler任务调度两级调度模块。作业调度模块为每个Spark作业计算具有依赖关系的多个调度阶段(通常根据shuffle来划分),然后为每个阶段构建出一组具体的任务(通常会考虑数据的本地性等),然后以TaskSets(任务组)的形式提交给任务调度模块来具体执行。而任务调度模块则负责具体启动任务、监控和汇报任务运行情况。
Application: 用户编写的应用应用程序。
Driver: Application中运行main函数并创建的SparkContext, 创建SparkContext的目的是和集群的ClusterManager通讯,进行资源的申请、任务的分配和监控等。所以,可以用SparkContext代表Driver
Worker:集群中可以运行Application代码的节点。
Executor: 某个Application在Worker上面的一个进程,该进程负责执行某些Task,并负责把数据存在内存或者磁盘上。每个Application都各自有一批属于自己的Executor。
Task:被送到Executor执行的工作单元,和Hadoop MapReduce中的MapTask和ReduceTask一样,是运行Application的基本单位。多个Task组成一个Stage,而Task的调度和管理由TaskScheduler负责。
Job:包含多个Task组成的并行计算,往往由Spark Action触发产生。一个Application可以产生多个Job。
Stage:每个Job的Task被拆分成很多组Task, 作为一个TaskSet,命名为Stage。Stage的调度和划分由DAGScheduler负责。Stage又分为Shuffle Map Stage和Result Stage两种。Stage的边界就在发生Shuffle的地方。
**RDD:**Spark的基本数据操作抽象,可以通过一系列算子进行操作。RDD是Spark最核心的东西,可以被分区、被序列化、不可变、有容错机制,并且能并行操作的数据集合。存储级别可以是内存,也可以是磁盘。
DAGScheduler:根据Job构建基于Stage的DAG(有向无环任务图),并提交Stage给TaskScheduler
TaskScheduler:将Stage提交给Worker(集群)运行,每个Executor运行什么在此分配。
val spark = SparkSession.builder
.appName(f"log clean:traffic_${beginInput}_$endInput")
.config("spark.executor.memory", "2g")
.config("spark.sql.warehouse.dir", "/user/hive/warehouse")
.enableHiveSupport()
.getOrCreate()
val trafficLog = config.getString("logPath.trafficLog").format(inputDate)
def lineExplain(line: String) = {
try {
val lineArr = line.stripLineEnd.split("\\?t\\?=")
val ngCookieList = lineArr(0).split("\\|\\|")
val ngCookieLen = ngCookieList.length
if (lineArr.length >= 29 && List(2, 4, 9).contains(ngCookieLen)){
//修复url后面加/
val requestUrl = lfb.repairUrl(lineArr(1).replace("\"", ""))
val domain = (new java.net.URL(requestUrl)).getHost
val seoUrl: String = Try(lineArr(29).split(" ")(0)) match {
case Success(seoUrl) => seoUrl
case Failure(ex) => ""
}
//url解析
val tids: Tids = seoUrl match {
case _ if seoUrl.length > 5 => classify(seoUrl)
case _ => classify(requestUrl)
}
if (tids.siteIds.nonEmpty) {
val iploc = lineArr(2)
val randomStr = lineArr(3).split("\\?")(1)
val referUrl = lineArr(7)
val referDomain = Try((new java.net.URL(referUrl)).getHost).toOption.getOrElse("0")
}
val uniqKey = (randomStr, suv, svn, ssid)
val lineObj = TrafficLine(ip, iploc, area.country, area.province, area.city,
tids.siteIds, tids.cnlIds, tids.colIds, tids.gids,addTime, inputDate
)
Some((uniqKey, lineObj))
} else None
} else None
} catch {
case e => {
logger.error(line)
logger.error(e)
None
}
}
}
spark.sparkContext.textFile(trafficLog).repartition(40).flatMap(lineExplain).
reduceByKey((v1, v2) => v1, numPartitions=20).map(_._2).toDF.repartition(4).
write.partitionBy("dt").mode(SaveMode.Append).parquet(f"$saveLogPath/")
spark.sql("msck repair table traffic_log")
spark.stop()
#伪代码
import scala.collection.mutable.{Map, Set}
spark.sparkContext.textFile(trafficLog).map(line => (url, Set(userId))).reduceByKey(_ ++ _).
map(lst => (user, len(userIdSet))).collectAsMap()
spark.sparkContext.textFile(trafficLog).map(line => (url, userId)).distinct().map(lst => (url, 1)).
reduceByKey(_ + _).collectAsMap()
1、repartition 和 coalesce
2、宽依赖和窄依赖
窄依赖指父RDD的每一个分区最多被一个子RDD的分区所用,表现为一个父RDD的分区对应于一个子RDD的分区,或多个父RDD的分区对应于一个子RDD的分区。图中,map/filter和union属于第一类,对输入进行协同划分(cogroup)的join属于第二类。
宽依赖指子RDD的分区依赖于父RDD的多个或所有分区,这是因为shuffle类操作,如图中的groupByKey和未经协同划分的join。
3、RDD 的 Transformation与Action,DAG