源码剖析通过Barrier对齐触发Checkpoint流程

1.InputGate接收BufferOrEvent,等待CheckpointBarrier对齐

上游Task实例发送的数据元素和事件,会发送到下游Task节点的InputGate中。将InputGate包装成CheckpointedInputGate,可以接收、处理上游传输的Checkpoint事件。当所有InputChannel都接收到上游节点发送来的所有的CheckpointBarrier后,当前Task实例就会触发当前节点的Checkpoint操作。这是为了让隶属于同一批Checkpoint的数据能够全部进入到当前节点中处理,有效保证了数据一致性。即使出现异常,也能根据上一次的Checkpoint结果恢复当前Task实例的状态数据。

BufferOrEvent数据会传递到CheckpointedInputGate中,CheckpointedInputGate主要负责处理CheckpointBarrier事件,而StreamElement会传递给StreamOperator处理。在所有InputChannel的CheckpointBarrier事件全部到达之前,CheckpointBarrierHandler会block住指定index的InputChannel,并通过BufferStorage将所有的BufferOrEvent缓存起来。一旦所有的CheckpointBarrier到齐,就会处理当前Task实例的Buffer数据,从而保证数据准确性。

/**
 * 从上游节点获取BufferOrent,对CheckpointBarrier事件进行相应处理,
 * 其他类型的事件、数据会全部传递给算子处理
 */
@Override
public Optional<BufferOrEvent> pollNext() throws Exception {
    while (true) {
        Optional<BufferOrEvent> next;
        if (bufferStorage.isEmpty()) {
            next = inputGate.pollNext();
        }
        else {
            next = bufferStorage.pollNext();
            if (!next.isPresent()) {
                return pollNext();
            }
        }

        if (!next.isPresent()) {
            return handleEmptyBuffer();
        }

        // 取出BufferOrEvent类型数据
        BufferOrEvent bufferOrEvent = next.get();
        // CheckpointBarrierHandler会block指定index的Channel
        // CheckpointBarrierHandler作为处理Barrier的主角,有2个实现子类,分别对应“严格一次”和“至少一次”语义
        if (barrierHandler.isBlocked(offsetChannelIndex(bufferOrEvent.getChannelIndex()))) {
            // 如果当前InputChannel被barrierHandler锁定,就先将BufferOrEvent缓冲起来,也就是先Block住。
            // CheckpointBarrierHandler会等所有InputChannel的CheckpointBarrier事件消息全部到达节点后,才会处理该Task实例的Buffer数据,保证数据计算结果的准确性
            bufferStorage.add(bufferOrEvent);
            // 如果缓冲区满了,就会clear + barrier reset
            if (bufferStorage.isFull()) {
                barrierHandler.checkpointSizeLimitExceeded(bufferStorage.getMaxBufferedBytes());
                bufferStorage.rollOver();
            }
        }
        /**
		 * 如果BufferOrEvent的数据类型为Buffer(也就是业务数据),直接留给算子处理
		 */
        else if (bufferOrEvent.isBuffer()) {
            return next;
        }
        /**
		 * 如果BufferOrEvent的数据类型为CheckpointBarrier事件,就对接入的Barrier进行处理
		 */
        else if (bufferOrEvent.getEvent().getClass() == CheckpointBarrier.class) {
            // 从BufferOrEvent中取出CheckpointBarrier事件
            CheckpointBarrier checkpointBarrier = (CheckpointBarrier) bufferOrEvent.getEvent();
            if (!endOfInputGate) {
                // 根据CheckpointBarrier是否对齐,决定是否是否触发当前节点的Checkpoint操作
                // 处理方式有2种:CheckpointBarrierAligner保证严格一次,CheckpointBarrierTracker至少一次
                if (barrierHandler.processBarrier(checkpointBarrier, offsetChannelIndex(bufferOrEvent.getChannelIndex()), bufferStorage.getPendingBytes())) {
                    bufferStorage.rollOver();
                }
            }
        }
        /**
		 * 如果是CancelCheckpointMarker事件,那就取消本次Checkpoint操作
		 */
        else if (bufferOrEvent.getEvent().getClass() == CancelCheckpointMarker.class) {
            // 取消本次Checkpoint操作
            if (barrierHandler.processCancellationBarrier((CancelCheckpointMarker) bufferOrEvent.getEvent())) {
                bufferStorage.rollOver();
            }
        }
        else {
            if (bufferOrEvent.getEvent().getClass() == EndOfPartitionEvent.class) {
                // EndOfPartitionEvent事件表示上游Partition中的数据已经消费完了。最后要clear掉缓冲区里的Buffer
                if (barrierHandler.processEndOfPartition()) {
                    bufferStorage.rollOver();
                }
            }
            return next;
        }
    }
}

CheckpointBarrierHandler能够保证CheckpointBarrier事件对齐,其中CheckpointBarrierHandler有2个实现子类,CheckpointBarrierAligner保证严格一次,CheckpointBarrierTracker至少一次。CheckpointBarrierAligner会对所有InputChannel中的CheckpointBarrier事件严格控制对齐,视情况决定Task实例中的InputChannel的block和打开的时机。

2.以Barrier对齐的方式处理CheckpointBarrier事件

InputChannel开始处于非阻塞状态,收到一个Buffer类型的数据,会正常计算处理。如果收到1个CheckpointBarrier,processBarrier时CheckpointBarrierAligner会将当前InputChannel Block住,后续再来的Buffer类型数据会被缓存起来。

之后另一个InputChannel也收到了一个Checkpoint,CheckpointBarrierAligner同样也会将这个InputChannel Block住。然后判断“接收到的Barrier数量 + Block的InputChannel数量 = 所有InputChannel的数量”,说明当前InputGate内的所有InputChannel的Barrier全都对齐了,那就解除所有InputChannel的Block状态(每个InputChanne又能继续在非阻塞状态下处理BufferOrEvent了),并触发StreamTask开始执行一次Checkpoint操作。

/**
 * 以Barrier对齐的方式处理CheckpointBarrier事件,保证严格一次的语义
 */
@Override
public boolean processBarrier(CheckpointBarrier receivedBarrier, int channelIndex, long bufferedBytes) throws Exception {
    // 获取CheckpointBarrier事件的barrierId
    final long barrierId = receivedBarrier.getId();

    // 如果InputChannel==1,直接触发Checkpoint。因为就它自己,不需要对齐处理
    if (totalNumberOfInputChannels == 1) {
        // 有新的CheckpointBarrier事件发出了
        if (barrierId > currentCheckpointId) {
            // 更新当前的CheckpointId
            currentCheckpointId = barrierId;
            // 触发该Task实例的Checkpoint操作
            notifyCheckpoint(receivedBarrier, bufferedBytes, latestAlignmentDurationNanos);
        }
        return false;
    }

    /**
	 * 能往下走,说明InputChannel的数量 > 1,这就必须得等待Barrier对齐
	 */
    boolean checkpointAborted = false;

    // numBarriersReceived大于0,说明已经开始接收CheckpointBarrier事件
    if (numBarriersReceived > 0) {

        /**
		 * 当前需要处理的正是这个Barrier事件,那就执行“Barrier对齐操作”
		 */
        if (barrierId == currentCheckpointId) {
            // 执行Barrier对齐操作:block住这个Barrier所在的InputChannel
            onBarrier(channelIndex);
        }
        /**
		 * 得到的barrierId大于当前的CheckpointId,说明在完成当前Checkpoint之前又有新的Barrier事件来了。
		 * 这种情况,会忽略当前Checkpoint,并开启新的Checkpoint
		 */
        else if (barrierId > currentCheckpointId) {
            LOG.warn("{}: Received checkpoint barrier for checkpoint {} before completing current checkpoint {}. " +
                     "Skipping current checkpoint.",
                     taskName,
                     barrierId,
                     currentCheckpointId);

            // 通知Task,手头儿上的这个Checkpoint没做完
            notifyAbort(currentCheckpointId,
                        new CheckpointException(
                            "Barrier id: " + barrierId,
                            CheckpointFailureReason.CHECKPOINT_DECLINED_SUBSUMED));

            // 释放Block并重置Barrier:紧急叫停手头儿上的这个Checkpoint
            releaseBlocksAndResetBarriers();
            checkpointAborted = true;

            // 开始做新的Checkpoint
            beginNewAlignment(barrierId, channelIndex);
        }
        /**
		 * 以上条件都不满足,说明当前的Checkpoint操作已被取消,或Barrier信息属于以前的Checkpoint,就此打住,啥也不干
		 */
        else {
            return false;
        }
    }
    else if (barrierId > currentCheckpointId) {
        // 这是一个新Checkpoint的First Barrier
        beginNewAlignment(barrierId, channelIndex);
    }
    else {
        return false;
    }

    /**
	 * 当Barrier接收的数量+Channel关闭的数量=整个InputChannel的数量时,说明所有的Barrier已经对齐,立即触发Checkpoint操作
	 */
    if (numBarriersReceived + numClosedChannels == totalNumberOfInputChannels) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("{}: Received all barriers, triggering checkpoint {} at {}.",
                      taskName,
                      receivedBarrier.getId(),
                      receivedBarrier.getTimestamp());
        }

        // 解除所有InputChannel的Block状态
        releaseBlocksAndResetBarriers();
        // 触发该Task实例的Checkpoint操作
        notifyCheckpoint(receivedBarrier, bufferedBytes, latestAlignmentDurationNanos);
        return true;
    }
    return checkpointAborted;
}

3.StreamTask执行Checkpoint操作

StreamTask提供了“异步执行Checkpoint”的具体实现逻辑

  • 让OperatorChain中的所有StreamOperator都执行“pre-barrier”
  • 将CheckpointBarrier广播给下游节点的所有InputChannel
  • 让OperatorChain的所有StreamOperator,执行状态数据的快照操作。执行成功后给InputGate返回True
/**
 * 开始执行Task实例的Checkpoint操作
 * 该方法的触发方式有2种:
 *    1.CheckpointCoordinator组件周期性的触发Source节点的Checkpoint操作
 *    2.下游算子通过CheckpointBarrier对齐触发本节点算子的Checkpoint操作
 * 不管哪种触发,最终都得调用该方法完成状态数据的持久化。
 * 注意:会先将Barrier广播给下游算子,然后本Task才会做自己的Checkpoint。如此循环...
 */
private boolean performCheckpoint(
      CheckpointMetaData checkpointMetaData,
      CheckpointOptions checkpointOptions,
      CheckpointMetrics checkpointMetrics,
      boolean advanceToEndOfTime) throws Exception {

   LOG.debug("Starting checkpoint ({}) {} on task {}",
      checkpointMetaData.getCheckpointId(), checkpointOptions.getCheckpointType(), getName());

   final long checkpointId = checkpointMetaData.getCheckpointId();

   // 判断当前Task是否正常运行
   if (isRunning) {
      // 核心:使用线程池以异步非阻塞的方式执行Checkpoint操作(不会block数据的正常处理)
      actionExecutor.runThrowing(() -> {

         if (checkpointOptions.getCheckpointType().isSynchronous()) {
            setSynchronousSavepointId(checkpointId);

            if (advanceToEndOfTime) {
               advanceToEndOfEventTime();
            }
         }

         /**
          * step 1:让OperatorChain中的所有Operator执行“pre-barrier”
          */
         operatorChain.prepareSnapshotPreBarrier(checkpointId);

         /**
          * stpe 2:先将CheckpointBarrier事件广播到下游的节点中
          *           广播本质:在Task的输出ResultPartition中,会给下游所有的Channel都发送一个CheckpointBarrier事件
          */
         operatorChain.broadcastCheckpointBarrier(
               checkpointId,
               checkpointMetaData.getTimestamp(),
               checkpointOptions);

         /**
          * step 3(核心):对StreamTask中的OperatorChain内的所有StreamOperator,执行状态快照操作。执行成功后给InputGate返回True
          */
         checkpointState(checkpointMetaData, checkpointOptions, checkpointMetrics);

      });

      return true;
   } else {
      /**
       * 如果isRunning为false,表示Task不在运行状态。这种状态下,需要将“CancelCheckpointMarker消息”发送给OperatorChain中所有的StreamOperator,
       * 并向下游算子进行广播。当且仅当OperatorChain中的算子还没执行完Checkpoint操作时,下游算子收到“CancelCheckpointMarker消息”后,才会立即取消Checkpoint操作。
       */
      actionExecutor.runThrowing(() -> {

         final CancelCheckpointMarker message = new CancelCheckpointMarker(checkpointMetaData.getCheckpointId());
         // 将“CancelCheckpointMarker消息”广播给下游的其他算子
         recordWriter.broadcastEvent(message);
      });

      return false;
   }
}

StreamTask中会遍历OperatorChain的所有StreamOperator,将每个StreamOperator的“状态快照操作”按照“OperatorId:OperatorSnapshotFutures对象”的映射关系,保存到Map中。由线程池中的AsyncCheckpointRunnable来执行这些“状态快照操作”

/**
 * 真正地执行Checkpoint操作:在线程池中执行Runnable任务
 * 核心逻辑:OperatorSnapshotFutures表示为“状态快照持久化操作”,将其保存到Map集合中
 */
public void executeCheckpointing() throws Exception {
   startSyncPartNano = System.nanoTime();

   try {
      // 遍历OperatorChain的所有StreamOperator
      for (StreamOperator<?> op : allOperators) {
         /**
          * 定义每个StreamOperator的状态快照操作,并将其注册给OperatorSnapshotFutures对象等待执行,
          * 按照“OperatorId:OperatorSnapshotFutures对象”的映射关系,保存到Map中。
          * 下面会由线程池中的Runnable任务来异步执行这些“操作”
          */
         checkpointStreamOperator(op);
      }

      省略部分代码...

      // we are transferring ownership over snapshotInProgressList for cleanup to the thread, active on submit
      // 创建Runnable任务:内含“装有每个StreamOperator的执行状态快照操作”所对应的Map集合
      AsyncCheckpointRunnable asyncCheckpointRunnable = new AsyncCheckpointRunnable(
         owner,
         operatorSnapshotsInProgress, // 存放了所有Operator的“状态快照”的结果--OperatorSnapshotFutures对象
         checkpointMetaData,
         checkpointMetrics,
         startAsyncPartNano);

      owner.cancelables.registerCloseable(asyncCheckpointRunnable);
      // 使用StreamTask内的ExecutorService(异步快照工作线程池),执行这个Runnable任务(容纳了每个StreamOperator的“快照持久化”的操作)
      // 目的:不影响数据流的正常处理
      owner.asyncOperationsThreadPool.execute(asyncCheckpointRunnable);

      省略部分代码...
}

StreamOperator将“状态快照操作”封装到OperatorSnapshotFutures的逻辑,就是将算子状态、键控状态的StateBackend的snapshot操作,包装到Future中。

/**
 * StreamTask在执行Checkpoint对状态数据进行snapshot时,如果对Checkpoint过程有特殊逻辑要求,可以在StreamOperator的子类中通过覆写CheckpointedFunction接口定义的钩子方法实现。
 * 然后KeyedStateBackend、OperatorStateBackend的“快照持久化操作”会被set到OperatorSnapshotFutures中等待执行。
 * 然后会按照“OperatorId:OperatorSnapshotFutures”的映射关系,存到Map集合中。Map集合会被添加到Runnable任务的执行逻辑中。
 * 后面会有一个专门的异步快照工作线程池--ExecutorService,执行这个Runnable任务。
 */
@Override
public final OperatorSnapshotFutures snapshotState(long checkpointId, long timestamp, CheckpointOptions checkpointOptions,
      CheckpointStreamFactory factory) throws Exception {

   // KeyGroupRange是状态后端在对键控状态处理时,划定key的索引范围的。
   // 如果keyedStateBackend不为null,就直接获取KeyGroupRange。否则,就new一个KeyGroupRange出来
   KeyGroupRange keyGroupRange = null != keyedStateBackend ?
         keyedStateBackend.getKeyGroupRange() : KeyGroupRange.EMPTY_KEY_GROUP_RANGE;

   // 创建OperatorSnapshotFutures对象
   OperatorSnapshotFutures snapshotInProgress = new OperatorSnapshotFutures();

   // StateSnapshotContextSynchronousImpl用来存储“执行快照”的过程中需要用到的上下文信息
   StateSnapshotContextSynchronousImpl snapshotContext = new StateSnapshotContextSynchronousImpl(
      checkpointId,
      timestamp,
      factory,
      keyGroupRange,
      getContainingTask().getCancelables());

   try {
      // CheckpointedFunction定义了可以覆写的“钩子”方法,在StreamOperator的子类中(通过自定义Function)可以为其提供具体的实现逻辑,
      // 以满足Checkpoint时的特殊逻辑要求,例如:删除状态中的某些数据、添加一些特殊数据等。
      snapshotState(snapshotContext);

      // 包装OperatorSnapshotFutures对象:指定专门用于处理原生状态数据的快照操作
      snapshotInProgress.setKeyedStateRawFuture(snapshotContext.getKeyedStateStreamFuture());
      snapshotInProgress.setOperatorStateRawFuture(snapshotContext.getOperatorStateStreamFuture());

      // 把OperatorStateBackend的“快照持久化”操作包装到OperatorSnapshotFutures中,等待执行
      if (null != operatorStateBackend) {
         snapshotInProgress.setOperatorStateManagedFuture(
            operatorStateBackend.snapshot(checkpointId, timestamp, factory, checkpointOptions));
      }

      // 把KeyedStateBackend的“快照持久化”操作包装到OperatorSnapshotFutures中,等待执行
      if (null != keyedStateBackend) {
         snapshotInProgress.setKeyedStateManagedFuture(
            keyedStateBackend.snapshot(checkpointId, timestamp, factory, checkpointOptions));
      }
   } catch (Exception snapshotException) {
      try {
         snapshotInProgress.cancel();
      } catch (Exception e) {
         snapshotException.addSuppressed(e);
      }

      String snapshotFailMessage = "Could not complete snapshot " + checkpointId + " for operator " +
         getOperatorName() + ".";

      if (!getContainingTask().isCanceled()) {
         LOG.info(snapshotFailMessage, snapshotException);
      }
      try {
         snapshotContext.closeExceptionally();
      } catch (IOException e) {
         snapshotException.addSuppressed(e);
      }
      // 一旦上面做snapshot的过程中出现异常,就会往上层抛。TaskManager接到异常后,会取消所有Task任务并启动Job重启策略
      throw new CheckpointException(snapshotFailMessage, CheckpointFailureReason.CHECKPOINT_DECLINED, snapshotException);
   }

   // 此时,经过包装的OperatorSnapshotFutures对象,拥有一堆的RunnableFuture(等待执行的异步任务)
   return snapshotInProgress;
}

每个StreamOperator的snapshot操作均已包装成Future,通过线程池中的AsyncCheckpointRunnable执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值