如何阅读源码?
任何一个程序,或者一个框架,无论做什么,多么复杂,都会有唯一的入口。通过这个入口,能够找到一条主线,这条主线就是这个程序或者框架的核心。
围绕这条主线,追溯整个调用链路,就能发掘出框架中的核心抽象,将这些抽象的作用搞懂,同时将它们之间的关系通过uml表示出来,这样,源码的大体结构就一览无余了。
源码会涉及很多包,其实包本身就是对源码的分类和抽象,这些包的功能也要弄清楚。源码分为核心代码和辅助代码,比如io相关,属于辅助部分。core包下面一般都是主流程下核心代码。
理清主线,理清抽象概念,理清抽象之间的关系之后,再去看具体的方法和处理过程,相信,这样阅读起来就顺畅的多。
还有一些比较难理解的点,比如处理某些特定问题时的思路和算法,或者是某种复杂的数据结构,需要着重挖掘。
源码都是水平较高的程序员所写,使用面向对象进行抽象,还会涉及到很多设计模式,所以读源码之前,对各种设计模式要有所了解。
另外最重要的基础,其实是语言基础。比如阅读spark源码,scala基础一定要好,这样在阅读的过程中才不会被各种拦路虎拦住。
寻找入口,有两种方式。
一是找到框架或者程序的二进制程序,然后追溯编译该程序的类。
二是启动之后阅读日志,从日志中追溯主调用链。
以上。
以spark为例子。
通过spark-submit命令提交spark任务,能够找到对应的启动类org.apache.spark.deploy.SparkSubmit,其main方法如下
def main(args: Array[String]): Unit = {
val appArgs = new SparkSubmitArguments(args)
if (appArgs.verbose) {
// scalastyle:off println
printStream.println(appArgs)
// scalastyle:on println
}
appArgs.action match {
case SparkSubmitAction.SUBMIT => submit(appArgs)
case SparkSubmitAction.KILL => kill(appArgs)
case SparkSubmitAction.REQUEST_STATUS => requestStatus(appArgs)
}
}
submit方法分成两步,首先准备环境,设置类加载路径(classpath),环境变量,启动类,其次通过反射调用用户的spark任务
val mainMethod = mainClass.getMethod("main", new Array[String](0).getClass)
try {
mainMethod.invoke(null, childArgs.toArray)
} catch {
case t: Throwable =>
findCause(t) match {
case SparkUserAppException(exitCode) =>
System.exit(exitCode)
case t: Throwable =>
throw t
}
}
用户的任务中首先需要创建sparkSession,创建时候先查看当前线程中是否已有实例
/** The active SparkSession for the current thread. */
private val activeThreadSession = new InheritableThreadLocal[SparkSession]
def getOrCreate(): SparkSession = synchronized {
// Get the session from current thread's active session.
var session = activeThreadSession.get()
if ((session ne null) && !session.sparkContext.isStopped) {
options.foreach { case (k, v) => session.conf.set(k, v) }
if (options.nonEmpty) {
logWarning("Using an existing SparkSession; some configuration may not take effect.")
}
return session
}
}
如果没有,使用默认defaultSession(从哪里来?),如果defaultSession不可用,创建。创建sparkSession前,首先创建sparkContext
// No active nor global default session. Create a new one.
val sparkContext = userSuppliedContext.getOrElse {
// set app name if not given
val randomAppName = java.util.UUID.randomUUID().toString
val sparkConf = new SparkConf()
options.foreach { case (k, v) => sparkConf.set(k, v) }
if (!sparkConf.contains("spark.app.name")) {
sparkConf.setAppName(randomAppName)
}
val sc = SparkContext.getOrCreate(sparkConf)
// maybe this is an existing SparkContext, update its SparkConf which maybe used
// by SparkSession
options.foreach { case (k, v) => sc.conf.set(k, v) }
if (!sc.conf.contains("spark.app.name")) {
sc.conf.setAppName(randomAppName)
}
sc
}
所以关键类是sparkContext,嗯,最核心的类了,先看看它有哪些核心成员变量。
private var _conf: SparkConf = _
private var _eventLogDir: Option[URI] = None
private var _eventLogCodec: Option[String] = None
private var _env: SparkEnv = _ //提供spark任务运行时环境
private var _jobProgressListener: JobProgressListener = _ // 跟踪task级的各种信息,用于展示在spark ui
private var _statusTracker: SparkStatusTracker = _ // 提供监控job和stage的api,有些被应用到spark ui
private var _progressBar: Option[ConsoleProgressBar] = None // 用于在控制台输出stage进程信息
private var _ui: Option[SparkUI] = None
private var _hadoopConfiguration: Configuration = _
private var _executorMemory: Int = _
private var _schedulerBackend: SchedulerBackend = _ // 后台调度进程
private var _taskScheduler: TaskScheduler = _ // 通过SchedulerBackend实现调度的task调度器
private var _heartbeatReceiver: RpcEndpointRef = _
@volatile private var _dagScheduler: DAGScheduler = _ // 面向stage的调度器,核心类,切分stage,构成DAG都在此类中完成
private var _applicationId: String = _
private var _applicationAttemptId: Option[String] = None
private var _eventLogger: Option[EventLoggingListener] = None
private var _executorAllocationManager: Option[ExecutorAllocationManager] = None // 根据负载动态分配或者回收executor
private var _cleaner: Option[ContextCleaner] = None // 异步清理器,清除RDD,shuffle和broadcast的状态
private var _listenerBusStarted: Boolean = false
private var _jars: Seq[String] = _
private var _files: Seq[String] = _
private var _shutdownHookRef: AnyRef = _
逐个过一下核心的成员变量。
sparkEnv提供spark任务的运行时环境,看一类的声明
class SparkEnv (
val executorId: String,
private[spark] val rpcEnv: RpcEnv,
val serializer: Serializer,
val closureSerializer: Serializer,
val serializerManager: SerializerManager,
val mapOutputTracker: MapOutputTracker,
val shuffleManager: ShuffleManager,
val broadcastManager: BroadcastManager,
val blockManager: BlockManager,
val securityManager: SecurityManager,
val metricsSystem: MetricsSystem,
val memoryManager: MemoryManager,
val outputCommitCoordinator: OutputCommitCoordinator,
val conf: SparkConf
)
(未完待续)