文章目录
简介
SparkStreaming为每一个数据源启动对应的Reciver(接收器),接收器以任务的形式运行在应用的Executor进程中,从输入源接收数据,把数据分组为小的批次(batch),并保存为RDD,最后提交Spark Job执行。流计算执行过程流程图如下所示,主要包含三大步骤:
- 启动流计算引擎(启动JobScheduler和JobGenerator);
- 接收并存储数据(启动Receiver,接收数据,生成Block);
- 生成Batch Job处理数据(流数据转换为RDD,生成并提交Job);
下面以以spark streaming文档中创建Socket Stream的例子来描述任务的执行流程。
val lines = ssc.socketTextStream("localhost", 9999)
val words = lines.flatMap(_.split(" "))
val pairs = words.map(word => (word, 1))
val wordCounts = pairs.reduceByKey(_ + _)
wordCounts.print()
启动流计算引擎
流计算任务的主要入口是创建并启动StreamingContext对象,其中维护了任务配置信息、DStreamGraph(用来定义DStream,并管理他们的依赖关系)和JobScheduler(用来生成和调度job,其中维护了ReceiverTracker和JobGenerator实例)实例。
StreamingContext.start启动流计算
通过调用StreamingContext的start方法启动流计算,其主要包括以下几个过程:
-
校验启动StreamingContext的合法性;
-
启动JobScheduler任务调度器;
-
将当前StreamingContext对象设置为活跃单例对象;
-
在指标体系中注册流计算相关指标;
-
在Spark UI界面中添加StreamingTab页面;
StreamingContext.scala /** * 启动流计算 * * @throws IllegalStateException 如果StreamingContext已经被停止。 */ def start(): Unit = synchronized { // 加同步锁,避免多个StreamingContext同时被启动 state match { case INITIALIZED => startSite.set(DStream.getCreationSite()) // 该锁用于保护StreamingContext的激活以及在getActiveOrCreate()方法中对StreamingContext单例的访问 StreamingContext.ACTIVATION_LOCK.synchronized { StreamingContext.assertNoOtherContextIsActive() try { validate() // 在新线程中启动流调度器,以便可以重置线程本地属性(如call sites和job groups), // 而不会影响当前线程中的这些属性值。 ThreadUtils.runInNewThread("streaming-start") { sparkContext.setCallSite(startSite.get) sparkContext.clearJobGroup() sparkContext.setLocalProperty(SparkContext.SPARK_JOB_INTERRUPT_ON_CANCEL, "false") savedProperties.set(SerializationUtils.clone(sparkContext.localProperties.get())) // 启动JobScheduler scheduler.start() } state = StreamingContextState.ACTIVE scheduler.listenerBus.post( StreamingListenerStreamingStarted(System.currentTimeMillis())) } catch { case NonFatal(e) => logError("Error starting the context, marking it as stopped", e) scheduler.stop(false) state = StreamingContextState.STOPPED throw e } StreamingContext.setActiveContext(this) } logDebug("Adding shutdown hook") // force eager creation of logger shutdownHookRef = ShutdownHookManager.addShutdownHook( StreamingContext.SHUTDOWN_HOOK_PRIORITY)(stopOnShutdown) // 在StreamingContext开始时注册流指标 assert(env.metricsSystem != null) env.metricsSystem.registerSource(streamingSource) uiTab.foreach(_.attach()) logInfo("StreamingContext started") case ACTIVE => logWarning("StreamingContext has already been started") case STOPPED => throw new IllegalStateException("StreamingContext has already been stopped") } }
其中JobScheduler的启动是关键,下面我们重点来进行分析。
JobScheduler.start启动Job调度器
JobScheduler的启动由以下步骤组成:
-
创建EventLoop的匿名实现类,主要用于处理各类JobSchedulerEvent事件,如JobStarted和JobCompleted事件。
-
启动StreamingListenerBus,实现原理与LiveListenerBus相同,主要用于更新Spark UI中StreamTab的内容。
-
创建并启动ReceiverTracker,用于处理数据接收、数据缓存、Block生成等工作。
-
启动JobGenerator,负责对DstreamGraph的初始化、Dstream与RDD的转换、生成Job、提交执行等工作。
// JobScheduler.scala def start(): Unit = synchronized { // 加同步锁保证多线程安全,不会被多个线程同时调用启动 if (eventLoop != null) return // 调度器已经被启动 logDebug("Starting JobScheduler") eventLoop = new EventLoop[JobSchedulerEvent]("JobScheduler") { // 处理JobStarted、JobCompleted事件 override protected def onReceive(event: JobSchedulerEvent): Unit = processEvent(event) override protected def onError(e: Throwable): Unit = reportError("Error in job scheduler", e) } eventLoop.start() // 将各输入流的rate controllers监听器连接到接收batch完成的更新操作上 for { inputDStream <- ssc.graph.