Spark学习笔记(10)Spark Streaming流数据不断接收全生命周期

来源:互联网 发布:华讯网络 电科软信 编辑:程序博客网 时间:2024/05/22 00:32

本期内容:

1.  数据接收架构设计模式

2.  数据接收源码彻底研究


Spark Streaming应用程序有以下特点:

1. 不断持续接收数据

2.  Receiver和Driver不在同一节点中

Spark Streaming应用程序接收数据、存储数据、汇报数据的metedata给Driver。

数据接收的模式类似于MVC,其中Driver是Model,Receiver是View,ReceiverSupervisorImpl是Controller。

Receiver的启动由ReceiverSupervisorImpl来控制,Receiver接收到数据交给ReceiverSupervisorImpl来存储。

RDD中的元素必须要实现序列化,才能将RDD序列化给Executor端。Receiver就实现了Serializable接口。

ReceiverTracker的代码片段:

      // Create the RDD using the scheduledLocations to run the receiver in a Spark job
      val receiverRDD: RDD[Receiver[_]] =
        if (scheduledLocations.isEmpty) {
          ssc.sc.makeRDD(Seq(receiver), 1)
        } else {
          val preferredLocations = scheduledLocations.map(_.toString).distinct
          ssc.sc.makeRDD(Seq(receiver -> preferredLocations))
        }

Receiver的代码片段:
@DeveloperApi
abstract class Receiver[T](val storageLevel: StorageLevel) extends Serializable {

处理Receiver接收到的数据,存储数据并汇报给Driver,Receiver是一条一条的接收数据的。

/**
 * Concrete implementation of [[org.apache.spark.streaming.receiver.ReceiverSupervisor]]
 * which provides all the necessary functionality for handling the data received by
 * the receiver. Specifically, it creates a [[org.apache.spark.streaming.receiver.BlockGenerator]]
 * object that is used to divide the received data stream into blocks of data.
 */
private[streaming] class ReceiverSupervisorImpl(
    receiver: Receiver[_],
    env: SparkEnv,
    hadoopConf: Configuration,
    checkpointDirOption: Option[String]
  ) extends ReceiverSupervisor(receiver, env.conf) with Logging {

通过限定数据存储速度来实现限流

接收数据,合并成buffer,放入block队列

在ReceiverSupervisorImpl启动会调用BlockGenerator对象的start方法。

  override protected def onStart() {
    registeredBlockGenerators.foreach { _.start() }
  ...
  private val registeredBlockGenerators = new mutable.ArrayBuffer[BlockGenerator]
    with mutable.SynchronizedBuffer[BlockGenerator]

源码注释说明了BlockGenerator把一个Receiver接收到的数据合并到一个Block然后写入到BlockManager中。该类内部有两个线程,一个是周期性把数据生成一批对象,然后把先前的一批数据封装成Block。另一个线程时把Block写入到BlockManager中。
  private val defaultBlockGenerator = createBlockGenerator(defaultBlockGeneratorListener)

BlockGenerator类继承自RateLimiter类,说明我们不能限定接收数据的速度,但是可以限定存储数据的速度,转过来就限定流动的速度。

BlockGenerator类有一个定时器(默认每200ms将接收到的数据合并成block)和一个线程(把block写入到BlockManager),200ms会产生一个Block,即1秒钟生成5个Partition。太小则生成的数据片中数据太小,导致一个Task处理的数据少,性能差。实际经验得到不要低于50ms。

BlockGenerator代码片段:


  private val blockIntervalTimer =
    new RecurringTimer(clock, blockIntervalMs, updateCurrentBuffer, "BlockGenerator")
  ...
  private val blockPushingThread = new Thread() { override def run() { keepPushingBlocks() } }

那BlockGenerator是怎么被创建的?

  private val defaultBlockGenerator = createBlockGenerator(defaultBlockGeneratorListener)
  ...
  override def createBlockGenerator(
      blockGeneratorListener: BlockGeneratorListener): BlockGenerator = {
    // Cleanup BlockGenerators that have already been stopped
    registeredBlockGenerators --= registeredBlockGenerators.filter{ _.isStopped() }

    val newBlockGenerator = new BlockGenerator(blockGeneratorListener, streamId, env.conf)
    registeredBlockGenerators += newBlockGenerator
    newBlockGenerator
  }

BlockGenerator类中的定时器会回调updateCurrentBuffer方法。

Receiver不断的接收数据,BlockGenerator类通过一个定时器,把Receiver接收到的数据,把多条合并成Block,再放入到Block队列中。

  /** Change the buffer to which single records are added to. */
  private def updateCurrentBuffer(time: Long): Unit = {
    try {
      var newBlock: Block = null
      // 不同线程都会访问currentBuffer,故需加锁
      synchronized {
        // 如果缓冲器不为空,则生成StreamBlockId对象,
        // 调用listener的onGenerateBlock来通知Block已生成,
        // 再实例化block对象。
        if (currentBuffer.nonEmpty) {
          val newBlockBuffer = currentBuffer
          currentBuffer = new ArrayBuffer[Any]
          val blockId = StreamBlockId(receiverId, time - blockIntervalMs)
          listener.onGenerateBlock(blockId)
          newBlock = new Block(blockId, newBlockBuffer)
        }
      }

      // 最后,把Block对象放入
      if (newBlock != null) {
        blocksForPushing.put(newBlock)  // put is blocking when queue is full
      }
    } catch {
      case ie: InterruptedException =>
        logInfo("Block updating timer thread was interrupted")
      case e: Exception =>
        reportError("Error in block updating thread", e)
    }
  }

该函数200ms回调一次,可以设置,但不能小于50ms。

运行在Executor端的ReceiverSupervisorImpl需要与Driver端的ReceiverTracker进行通信,传递元数据信息metedata,其中ReceiverSupervisorImpl通过RPC的名称获取到ReceiverTrcker的远程调用。

ReceiverSupervisorImpl代码片段:


  /** Remote RpcEndpointRef for the ReceiverTracker */
  private val trackerEndpoint = RpcUtils.makeDriverRef("ReceiverTracker", env.conf, env.rpcEnv)

在ReceiverTracker调用start方法启动的时候,会以ReceiverTracker的名称创建RPC通信体。ReceiverSupervisorImpl就是和这个RPC通信体进行消息交互的。


  /** Start the endpoint and receiver execution thread. */
  def start(): Unit = synchronized {
    if (isTrackerStarted) {
      throw new SparkException("ReceiverTracker already started")
    }

    if (!receiverInputStreams.isEmpty) {
      endpoint = ssc.env.rpcEnv.setupEndpoint(
        "ReceiverTracker", new ReceiverTrackerEndpoint(ssc.env.rpcEnv))
      if (!skipReceiverLaunch) launchReceivers()
      logInfo("ReceiverTracker started")
      trackerState = Started
    }
  }

在ReceiverTrackerEndpoint接收到ReceiverSupervisorImpl发送的注册消息,把其RpcEndpoint保存起来。

    override def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {
      // Remote messages
      case RegisterReceiver(streamId, typ, host, executorId, receiverEndpoint) =>
        val successful =
          registerReceiver(streamId, typ, host, executorId, receiverEndpoint, context.senderAddress)
        context.reply(successful)
      case AddBlock(receivedBlockInfo) =>
        if (WriteAheadLogUtils.isBatchingEnabled(ssc.conf, isDriver = true)) {
          walBatchingThreadPool.execute(new Runnable {
            override def run(): Unit = Utils.tryLogNonFatalError {
              if (active) {
                context.reply(addBlock(receivedBlockInfo))
              } else {
                throw new IllegalStateException("ReceiverTracker RpcEndpoint shut down.")
              }
            }
          })
        } else {
          context.reply(addBlock(receivedBlockInfo))
        }
      case DeregisterReceiver(streamId, message, error) =>
        deregisterReceiver(streamId, message, error)
        context.reply(true)
      // Local messages
      case AllReceiverIds =>
        context.reply(receiverTrackingInfos.filter(_._2.state != ReceiverState.INACTIVE).keys.toSeq)
      case StopAllReceivers =>
        assert(isTrackerStopping || isTrackerStopped)
        stopReceivers()
        context.reply(true)
    }

对应的Executor端的ReceiverSupervisorImpl也会创建Rpc消息通信体,来接收来自Driver端ReceiverTacker的消息。

  /** RpcEndpointRef for receiving messages from the ReceiverTracker in the driver */
  private val endpoint = env.rpcEnv.setupEndpoint(
    "Receiver-" + streamId + "-" + System.currentTimeMillis(), new ThreadSafeRpcEndpoint {
      override val rpcEnv: RpcEnv = env.rpcEnv

      override def receive: PartialFunction[Any, Unit] = {
        case StopReceiver =>
          logInfo("Received stop signal")
          ReceiverSupervisorImpl.this.stop("Stopped by driver", None)
        case CleanupOldBlocks(threshTime) =>
          logDebug("Received delete old batch signal")
          cleanupOldBlocks(threshTime)
        case UpdateRateLimit(eps) =>
          logInfo(s"Received a new rate limit: $eps.")
          registeredBlockGenerators.foreach { bg =>
            bg.updateRate(eps)
          }
      }
    })


BlockGenerator类中的线程每隔10ms从队列中获取Block,写入到BlockManager中。


  /** Keep pushing blocks to the BlockManager. */
  private def keepPushingBlocks() {
    logInfo("Started block pushing thread")

    def areBlocksBeingGenerated: Boolean = synchronized {
      state != StoppedGeneratingBlocks
    }

    try {
      // While blocks are being generated, keep polling for to-be-pushed blocks and push them.
      while (areBlocksBeingGenerated) {
        Option(blocksForPushing.poll(10, TimeUnit.MILLISECONDS)) match {
          case Some(block) => pushBlock(block)
          case None =>
        }
      }

      // At this point, state is StoppedGeneratingBlock. So drain the queue of to-be-pushed blocks.
      logInfo("Pushing out the last " + blocksForPushing.size() + " blocks")
      while (!blocksForPushing.isEmpty) {
        val block = blocksForPushing.take()
        logDebug(s"Pushing block $block")
        pushBlock(block)
        logInfo("Blocks left to push " + blocksForPushing.size())
      }
      logInfo("Stopped block pushing thread")
    } catch {
      case ie: InterruptedException =>
        logInfo("Block pushing thread was interrupted")
      case e: Exception =>
        reportError("Error in block pushing thread", e)
    }
  }

ReceiverSupervisorImpl代码片段:

  /** Divides received data records into data blocks for pushing in BlockManager. */
  private val defaultBlockGeneratorListener = new BlockGeneratorListener {
    def onAddData(data: Any, metadata: Any): Unit = { }

    def onGenerateBlock(blockId: StreamBlockId): Unit = { }

    def onError(message: String, throwable: Throwable) {
      reportError(message, throwable)
    }

    def onPushBlock(blockId: StreamBlockId, arrayBuffer: ArrayBuffer[_]) {
      pushArrayBuffer(arrayBuffer, None, Some(blockId))
    }
  }
...

  /** Store block and report it to driver */
  def pushAndReportBlock(
      receivedBlock: ReceivedBlock,
      metadataOption: Option[Any],
      blockIdOption: Option[StreamBlockId]
    ) {
    val blockId = blockIdOption.getOrElse(nextBlockId)
    val time = System.currentTimeMillis
    val blockStoreResult = receivedBlockHandler.storeBlock(blockId, receivedBlock)
    logDebug(s"Pushed block $blockId in ${(System.currentTimeMillis - time)} ms")
    val numRecords = blockStoreResult.numRecords
    val blockInfo = ReceivedBlockInfo(streamId, numRecords, metadataOption, blockStoreResult)
    trackerEndpoint.askWithRetry[Boolean](AddBlock(blockInfo))
    logDebug(s"Reported block $blockId")
  }

将数据存储在BlockManager中,并将源数据信息告诉Driver端的ReceiverTracker。

  def storeBlock(blockId: StreamBlockId, block: ReceivedBlock): ReceivedBlockStoreResult = {

    var numRecords = None: Option[Long]

    val putResult: Seq[(BlockId, BlockStatus)] = block match {
      case ArrayBufferBlock(arrayBuffer) =>
        numRecords = Some(arrayBuffer.size.toLong)
        blockManager.putIterator(blockId, arrayBuffer.iterator, storageLevel,
          tellMaster = true)
      case IteratorBlock(iterator) =>
        val countIterator = new CountingIterator(iterator)
        // 把数据写入BlockManager
        val putResult = blockManager.putIterator(blockId, countIterator, storageLevel,
          tellMaster = true)
        numRecords = countIterator.count
        putResult
      case ByteBufferBlock(byteBuffer) =>
        blockManager.putBytes(blockId, byteBuffer, storageLevel, tellMaster = true)
      case o =>
        throw new SparkException(
          s"Could not store $blockId to block manager, unexpected block type ${o.getClass.getName}")
    }
    if (!putResult.map { _._1 }.contains(blockId)) {
      throw new SparkException(
        s"Could not store $blockId to block manager with storage level $storageLevel")
    }
    BlockManagerBasedStoreResult(blockId, numRecords)
  }



阅读全文
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 我想学英语从基础开始要怎么办 政府单位领导给我调岗我该怎么办 领导在单位想捞钱我该怎么办 单位领导是宵小之人我该怎么办 一件事想不明白非得想明白怎么办 在四楼上课时发生地震该怎么办 媳妇要离婚我想要孩子该怎么办 媳妇带了避孕环我想要孩子怎么办 新开的文具店一点生意都没有怎么办 孩子在学校被坏孩子欺负了该怎么办 老师像个傻叉我妈还喷我我怎么办啊 承台上预埋桥墩连接钢筋错了怎么办 冲床油缸螺栓拆不下来怎么办 汇款到账银行写错了怎么办 搜狗输入法数字序号超过20怎么办 苹果手机保存的图片变模糊怎么办 微信视频保存到手机变模糊怎么办 自己的位置被别人取代了怎么办 给工厂做半成品老板跑了怎么办 微信变成英文再恢复汉字怎么办 cad中标注尺寸数字太小怎么办 扣扣的钱包手势密码忘记了怎么办 台式电脑带符号的数字打不出怎么办 情侣之间出现看见对方就烦怎么办 电信卡号和联通卡号怎么办情侣号 电脑能登qq但打不开网页怎么办 想跟朋友聊天但对方不理怎么办 刚进婆家门被婆婆欺负怎么办 支付宝的聊天记录被删了怎么办 彩票站买彩票把钱付了没出票怎么办 与异性朋友聊天没话题了怎么办 快递写错地址但已经发货了怎么办 快递写错电话但已经发货了怎么办 微信添加好友功能被限制怎么办 qq号被冻结了限制解封怎么办 被别人强制拉入qq群怎么办 qq群里的图片过期了怎么办 q附近人不能关注不能发信息怎么办 qq畅聊之火掉了怎么办 打印机打印时上面空白留太多怎么办 发短信一直空格里面写0怎么办